Show a window from a modal dialog

I have some custom controls showing a sort of “popup”. Take for example a date picker where clicking on a button shows a month view where the user can pick a date. More controls works in this way.

Sometimes I need to show such custom controls on a modal dialog, and this simply don’t work, because the “popup” view will never get focus due the modal window prevent this.

Is there a way (maybe with declares) to tell those popup windows are sort of “children” of the modal dialog and therefore are allowed to get focus?

I need a solution for both Mac and Windows, due they are cross platform.
On Mac, if I use a Sheet window everything works and I can solve in this way mostly, but on Windows I don’t know how to workaround this problem.

Any suggestions?

Yep, use a Sheet window on Mac OS. Then SetParent should do the trick on Windows.

It is tricky, but you may be able to obtain what you are looking for by displaying a modal window.

Thanks for the hint Isaac. I sorted out with this

Public Sub SetParent(extends w as Window, parent as Window)
  #if targetWindows
    
    declare function GetWindowLongW lib "User32" (hWnd as integer, nIndex as integer) as integer
    declare function SetWindowLongW lib "User32" (hWnd as integer, nIndex as integer, dwNewLong as integer) as integer
    declare function SetParent lib "User32" (hWndChild as integer, hWndNewParent as integer) as integer
    declare function SetWindowPos Lib "User32" (hWnd as integer, hWndInstertAfter as integer, x as integer, y as integer, cx as integer, cy as integer, flags as integer) as boolean
    declare function ShowWindow Lib "User32" (hWnd as integer, Command as integer) as boolean
    
    const GWL_STYLE = -16
    const WS_CHILD = &h40000000
    const WS_POPUP = &h80000000
    const WS_POPUP_FLIPPED = &hF7FFFFFF
    const SWP_FRAMECHANGED = &h0020
    const SWP_ASYNCWINDOWPOS = &h4000
    const SW_SHOW = 5
    
    // get current style
    dim style as integer = GetWindowLongW(w.handle, GWL_STYLE)
    
    // set new style
    style = (style or WS_CHILD) and not WS_POPUP // WS_POPUP_FLIPPED
    call SetWindowLongW(w.handle, GWL_STYLE, style)
    
    // set parent
    call SetParent(w.handle, parent.handle)
    
    // set window position
    call SetWindowPos(w.handle, 0, 0, 0, w.Width, w.Height, SWP_ASYNCWINDOWPOS or SWP_FRAMECHANGED)
    
    // show window
    call ShowWindow(w.handle, SW_SHOW)
    
  #endif
End Sub

which only partly works.

Problem number 1 is the child window is clipped on the modal dialog, that is it can’t draw the part outside the modal dialog bounds.
Problem number 2 is the child window doesn’t accept mouse input. This can only be solved making the child window a modal window, though this would spoil the whole approach of having a popup window that closes when goes in background (deactivate).

Maybe playing with the window style attributes this can be solved, but so far I had no success.

Any additional hint would be appreciated.

@Massimo Valle — You came from having troubles with controls in a modal dialog to creating a new window from it. This is a completely different problem.

A modal dialog blocks all interactions with the other windows, so your child window will be blocked. It looks more like a design problem to me.

So my first guess is to try something with SetCapture to see if you can get mouse events from the inner popup modal window to know when the mouse is clicked outside its bounds, but I’m starting to think that this might get really complex. However if SetCapture works you may be able to just do a Xojo modal instead of using SetParent at all.

And yeah as a parented window, the popup will get clipped. This would have to be solved with design, e.g. making sure that it doesn’t become an issue by keeping enough blank space at the bottom of the dialog and constraining the size of the popup, or keeping such popups near the top of the dialog, etc.

One other thought, have you looked into using a ContainerControl instead of a true popup for the popup contents?

One other much harder possibility would be to create a true owner-drawn combo box. This might be acceptable for relatively simple controls or if you were going to draw the control in a Canvas anyway. I’m pretty sure you could just blit to the owner-drawn window from a Graphics object after drawing your interface in Xojo. You’ll then have to process windows events manually to get mouse events, not sure if a declare can do that (not something I’ve tried to do but in theory it would be possible).

Not sure this would be worth the effort to be honest, but it’s a possibility.

Thanks Isaac for your efforts.
Things are unfortunately more complicate. We have tenths of custom controls opening such “popup” frameless windows. The date picker was just an example. And drawing it inside the dialog is not something I can really consider without spoiling completely the idea of a popup view. Until now these were shown in modeless windows and this was not a problem. But now I have to find another solution.
I wonder how Microsoft do that for native controls like menus, etc. (a menu is basically a window).
Still I can’t believe the Windows API are so badly designed.

SetCapture seems to work at least partially.
It does indeed allow the window to get the mouse events, however after every click the window don’t get mouse events anymore.
A bad approach would be like a timer to call SetCapture and/or calling it on MouseMove. This works, but I don’t like that much.

If I can solve this SetCapture behavior, I can make it work.

Or just fake your own cross-platform sheet window …

I thought to this, but it’s gonna create more problems than it solves due it must be modal in any case. And since I can’t use a modal window for it, the only way would be to use a modeless window and disabling everything on the parent window. Plus code to avoid the fake to go to background. Plus handle blocking the program flow like a modal window does…

But thanks for your suggestion :smiley:

I spent some time trying to figure out how the internals of Win32 combo boxes and menus work but so far haven’t really reached a solid conclusion. My guess is that they actually do something like SetCapture (but maybe not actually that available public API), since it does seem to be a system-global thing.

I‘m confused. You wrote

so that would mean you DON‘T need it to be modal but just “modal for the window”. And THAT you can do by rolling your own fake but cross-platform sheetwindow (using Canvases or ContainerControls).

A sheet window is not constrained by the bounds of its parent, but a Canvas or ContainerControl is, so I’m guessing that’s the issue.

So, poking around the Wine source code I think it might be possible to simulate what CreatePopupMenu and TrackPopupMenu do to accomplish showing a window above a modal.

This should at least give you an idea of the flags involved.

First, creating the HWND that is used (these flags should be settable on an existing window with SetWindowLong):

menu->hWnd = CreateWindowExW( ex_style, (LPCWSTR)POPUPMENU_CLASS_ATOM, NULL,
                            WS_POPUP, 0, 0, 0, 0,
                            hwndOwner, 0, (HINSTANCE)GetWindowLongPtrW(hwndOwner, GWLP_HINSTANCE),
(LPVOID)hmenu );

The styles referenced by the POPUPMENU_CLASS_ATOM (which are CS_DROPSHADOW | CS_SAVEBITS | CS_DBLCLKS) do not seem to be relevant, though CS_SAVEBITS does have a small optimization when hiding small windows. Hopefully we don’t need those class styles since I am not aware of a way to make a Xojo window with a custom class.

So, then the window is actually shown:

SetWindowPos( menu->hWnd, HWND_TOPMOST, x, y, menu->Width, menu->Height,
              SWP_SHOWWINDOW | SWP_NOACTIVATE );
UpdateWindow( menu->hWnd );

This seems to be mainly a simple combination of HWND_TOPMOST and the SetWindowPos flags SWP_SHOWWINDOW and SWP_NOACTIVATE. SetWindowPos should be able to do this for you pretty much as shown in the actual Wine code.

Source, MENU_InitPopup and MENU_ShowPopup. Hopefully that’s enough to go on, I wish I could get this together into a working example but other duties call.

What I don’t understand is why you need to keep the dialog on the screen when you pop your window up ?

By definition, a dialog is supposed to close as soon as the user makes his choice.

It then seems simple enough to close the dialog before you pop the window up, avoiding the conflict.

[quote=427974:@Michel Bujardet]What I don’t understand is why you need to keep the dialog on the screen when you pop your window up ?

By definition, a dialog is supposed to close as soon as the user makes his choice.

It then seems simple enough to close the dialog before you pop the window up, avoiding the conflict.[/quote]

Example: the dialog has some custom controls on it where the user has to make a choice before initiating some other action. Like a date field accepting a date and optionally with a button to open a date picker as a popup small window. This date picker field is a reusable container control.
Of course can’t close the dialog because this is where the user make a choice.

[quote=427918:@Isaac Raway]So, poking around the Wine source code I think it might be possible to simulate what CreatePopupMenu and TrackPopupMenu do to accomplish showing a window above a modal.

This should at least give you an idea of the flags involved.

First, creating the HWND that is used (these flags should be settable on an existing window with SetWindowLong):

menu->hWnd = CreateWindowExW( ex_style, (LPCWSTR)POPUPMENU_CLASS_ATOM, NULL,
                            WS_POPUP, 0, 0, 0, 0,
                            hwndOwner, 0, (HINSTANCE)GetWindowLongPtrW(hwndOwner, GWLP_HINSTANCE),
(LPVOID)hmenu );

The styles referenced by the POPUPMENU_CLASS_ATOM (which are CS_DROPSHADOW | CS_SAVEBITS | CS_DBLCLKS) do not seem to be relevant, though CS_SAVEBITS does have a small optimization when hiding small windows. Hopefully we don’t need those class styles since I am not aware of a way to make a Xojo window with a custom class.

So, then the window is actually shown:

SetWindowPos( menu->hWnd, HWND_TOPMOST, x, y, menu->Width, menu->Height,
              SWP_SHOWWINDOW | SWP_NOACTIVATE );
UpdateWindow( menu->hWnd );

This seems to be mainly a simple combination of HWND_TOPMOST and the SetWindowPos flags SWP_SHOWWINDOW and SWP_NOACTIVATE. SetWindowPos should be able to do this for you pretty much as shown in the actual Wine code.

Source, MENU_InitPopup and MENU_ShowPopup. Hopefully that’s enough to go on, I wish I could get this together into a working example but other duties call.[/quote]

Thank you very much Isaac for your thoughtful investigation. Once I have some spare time I will try to implement it and see how it works.

Again, thanks!

[quote=427918:@Isaac Raway]So, poking around the Wine source code I think it might be possible to simulate what CreatePopupMenu and TrackPopupMenu do to accomplish showing a window above a modal.

This should at least give you an idea of the flags involved.

First, creating the HWND that is used (these flags should be settable on an existing window with SetWindowLong):

menu->hWnd = CreateWindowExW( ex_style, (LPCWSTR)POPUPMENU_CLASS_ATOM, NULL,
                            WS_POPUP, 0, 0, 0, 0,
                            hwndOwner, 0, (HINSTANCE)GetWindowLongPtrW(hwndOwner, GWLP_HINSTANCE),
(LPVOID)hmenu );

The styles referenced by the POPUPMENU_CLASS_ATOM (which are CS_DROPSHADOW | CS_SAVEBITS | CS_DBLCLKS) do not seem to be relevant, though CS_SAVEBITS does have a small optimization when hiding small windows. Hopefully we don’t need those class styles since I am not aware of a way to make a Xojo window with a custom class.

So, then the window is actually shown:

SetWindowPos( menu->hWnd, HWND_TOPMOST, x, y, menu->Width, menu->Height,
              SWP_SHOWWINDOW | SWP_NOACTIVATE );
UpdateWindow( menu->hWnd );

This seems to be mainly a simple combination of HWND_TOPMOST and the SetWindowPos flags SWP_SHOWWINDOW and SWP_NOACTIVATE. SetWindowPos should be able to do this for you pretty much as shown in the actual Wine code.

Source, MENU_InitPopup and MENU_ShowPopup. Hopefully that’s enough to go on, I wish I could get this together into a working example but other duties call.[/quote]

I tried to replicate the above, and wrote this:

const GWL_STYLE = -16
const WS_POPUP = &h80000000
const HWND_TOPMOST = -1
const SWP_SHOWWINDOW = &h0040
const SWP_NOACTIVATE = &h0010

declare function SetWindowPos lib "User32" (hWnd as integer, hWndInstertAfter as integer, x as integer, y as integer, cx as integer, cy as integer, flags as integer) as boolean
declare function SetWindowLongW lib "User32" (hWnd as integer, nIndex as integer, dwNewLong as integer) as integer
declare function UpdateWindow lib "User32" (hWnd as integer) as boolean

call SetWindowLongW(w.handle, GWL_STYLE, WS_POPUP)
call SetWindowPos(w.handle, HWND_TOPMOST, w.left, w.top, w.width, w.height, SWP_NOACTIVATE or SWP_SHOWWINDOW)
call UpdateWindow(w.handle)

but seems to do nothing. Worse, the SWP_NOACTIVATE completely prevent the window to get keyboard focus. Removing it, the above code behave like if it’s not called, that is, the window is set to front, the keyboard input is active, only the mouse is not captured.

If you have the MBS plugins and are willing to draw the content as a graphic and handle hit testing take a look at OverlayMBS.