Click here to Skip to main content
15,867,756 members
Please Sign up or sign in to vote.
5.00/5 (3 votes)
See more:
Hi!

I am struggling for a few days now to show a simple window without causing my main window to lose focus and become inactive.

Basically I am trying to emulate a context menu (I cannot use owner drawn menus for various reasons) but when being shown it will go ahead and become the active window.

What I have tried so far:
-Create window with WS_EX_NOACTIVATE style
-Showing the window with SW_SHOWNA or SW_SHOWNOACTIVATE
-Handle WM_ACTIVATE and WM_NCACTIVATE messages in the 'menu' window to stop them going towards DefWindowProc and returning 0
-Display the 'menu' window using SetWindowPos with SWP_SHOWWINDOW | SWP_NOACTIVATE
-Setting back manually the main window as the active window (but it's not very elegant there's some flicker involved).
-Searching endlessly on Google, MSDN and CP

To show an insight here's some code.

This is how I am creating the window:
C++
HWND hMenuWnd = CreateWindowEx(WS_EX_NOACTIVATE, L"MyCustomMenu", L"", WS_POPUP | WS_BORDER, 0, 0, 1, 1, hMainWindow, 0, hInstance, 0);


Then in the window procedure for the main window, when handling WM_COMMAND to display the 'menu'
C++
case IDC_MAINMENUBUTTON:
    ShowWindow(hMenuWnd, SW_SHOWNOACTIVATE);
    break;


After the statement above executes you can clearly see the main window caption is redrawn in the inactive state. Besides what I've mentioned there is absolutely nothing special going on in both windows procedures.

Any suggestion or solution will be highly appreciated, don't let me down CP!

Thanks,
Alex
Posted
Comments
enhzflep 27-Mar-14 23:51pm    
Have you tried to make the context-menu a child of the main window? (i.e use the WS_CHILD flag) If you don't, it seems entirely reasonable to me that the new window would become active and its 'parent' would be de-activated.
Alex Culea 28-Mar-14 0:37am    
Thanks for your comment.

Yes. But the contents of my window gets drawn over by the other child controls and calling SetWindowPos(hMenuWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW );

But it did not change the Z-order in any way, still came behind the other controls.
Sergey Alexandrovich Kryukov 28-Mar-14 14:38pm    
This is an interesting question of considerable practical importance. Voted 5 for the question.
—SA

The problem is: if you show a window which cannot be activated, is is goes behind another window; in your case, it could be your main window.

The solution is to combine "cannot-activate" with "always-on-top" extended style: WS_EX_TOPMOST | WS_EX_NOACTIVATE:
http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543%28v=vs.85%29.aspx[^],
http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx[^],
http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591%28v=vs.85%29.aspx[^].

—SA
 
Share this answer
 
Comments
Alex Culea 28-Mar-14 13:52pm    
Thanks, almost worked. However this has created another set of issues. Since the window is no longer active, it no longer receives WM_KILLFOCUS either so it will not get hidden when the user clicks something else. I am currently working to get it to behave itself and once I come up with a complete solution, I'll mark your answer as accepted and provide more details, maybe even writing a tip on CP.
Sergey Alexandrovich Kryukov 28-Mar-14 14:37pm    
Oh, first of all, you can dynamically set/clear bits of extended style. That's why I referenced a MSDN article on SetWindowLong (to be used with GWL_EXSTYLE).
And yes, it won't receive this message. You have to find different ways to remove it. First approach would be: you can also capture mouse and hence handle all mouse clicks. That is, you will handle clicks even outside of your non-activated window, but then detect was the click inside the window or not. Some more thinking is required. For a worst case alternative, you can also create a local (process-wide) Windows Hook. If the hook is local (which indicated by passing some actual threadId for the thread you need to capture the messages for, see http://msdn.microsoft.com/en-us/library/windows/desktop/ms644990%28v=vs.85%29.aspx).

The problem is certainly solvable. If you collect right combination of options to put together the whole technique of dealing with such window, it will be a great idea to write an article on it, Tips&Tricks or bigger one. Such techniques are practically important, in particular, for creation of something like virtual keyboards, the topic often appearing in question posts.

If some more ideas comes to my mind, I'll add another comment. In turn, if you have something of concern (or if anything interesting comes up), please comment on this post, to give me some notification.

—SA
Alex Culea 28-Mar-14 15:41pm    
In theory, showing a popup window without getting activated is simple, here I fell in a pitfall in the positioning code when SetWindowPos'ing the menu window and not specifying each time SWP_NOACTIVATE. It seems that if you do not add the flag each time you are calling this function, the API will gracefully remove your WX_EX_SHOWNOACTIVATE without remorse. Lesson learned.

To capture mouse I have tried handling WM_WINDOWSPOSCHANGED checking for SWP_SHOWWINDOW and calling SetCapture(hMenuWnd). Then checking SWP_HIDEWINDOW to release the capture but this will deprive the children windows from getting WM_MOUSEMOVE (i'm having some nice mouse over effects there) and forwarding the message as with mouse presses does not return the correct results.

For now I will look into implementing a local hook as you correctly advised and see how that works. Will post a complete solution when I get all the pieces together.
First I would like to thank everybody that has contributed with ideas to this question, here's the solution I found to fit best my needs.

For those jumping straight to the solution, I am displaying a custom context menu that is hosted by a popup window. The point is to have the menu window not interfere with activation and focus of the owner window (short, make it behave like a context menu).

The window is created like so:
C++
HWND hMenuWnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_NOACTIVATE, "MyCustomMenu", NULL, WS_POPUP | WS_BORDER, 0, 0, 1, 1, hMainWindow, 0, hInstance, 0);


Notice the WS_EX_NOACTIVATE style which instructs Windows to skip window activation that normally happens when a new top level window is shown.

Later on the menu window will be positioned and shown by it's owner as a result from clicking a button. When calling SetWindowPos to set the menu in the correct location make sure you are adding SWP_NOACTIVATE flag otherwise this function will remove WS_EX_NOACTIVATE from the window.

This is a simple usage example:
C++
case IDC_MYMENUBUTTON:
   SetWindowPos(hMenuWnd, 0, x, y, wdith, height, SWP_NOZORDER | SWP_NOACTIVATE);
   ShowWindow(hMenuWnd, SW_SHOWNOACTIVATE);

   break;


Again, to keep the menu window from being activated you need to handle WM_MOUSEACTIVATE message in the menu window procedure and return MA_NOACTIVATE.
C++
case WM_MOUSEACTIVATE:
    return MA_NOACTIVATE;


Now that the window is ready to become visible, you will need to monitor mouse clicks especially those that go outside it's area to cause the menu to hide. This will be achieved by setting a local mouse hook (will only watch the current thread message queue) that will catch mouse clicks and decide if it's time to hide the menu.

Also, you will want this hook to be active only when the menu is visible (no need to filter messages when no action is required) so for this you will handle WM_WINDOWPOSCHANGED message like so:

C++
case WM_WINDOWPOSCHANGED:
    WINDOWPOS* wp = (WINDOWPOS*) lParam;
    
    // if the window is shown
    if (wp->flags & SWP_SHOWWINDOW)
    {
        // set our hook and store the handle in the global variable
        g_hMouseHook = SetWindowsHookEx(WH_MOUSE, MyMenuMouseHook, 0, GetCurrentThreadId());
        
        if (!g_hMouseHook)
        {
            // handle your error!
        }
     } else if (wp->flags & SWP_HIDEWINDOW) // the menu has been hidden
     {
        UnhookWindowsHookEx(g_hMouseHook); // unhook
     }

     // pass on the message to DefWindowProc
     return DefWindowProc(hWnd, Msg, wParam, lParam); 


Now the hook procedure that intercepts all mouse button actions (down, up, etc) and decides when the menu should be hidden.

C++
LRESULT CALLBACK MyMenuMouseHook(int Code, WPARAM wParam, LPARAM lParam)
{
    // messages are defined in a linear way the first being WM_LBUTTONUP up to WM_MBUTTONDBLCLK
    // this subset does not include WM_MOUSEMOVE, WM_MOUSEWHEEL and a few others
    if (wParam >= WM_LBUTTONUP && wParam <= WM_MBUTTONDBLCLK)
    {
        // it's a fair assumption to say that you should have only one menu displayed at a certain point in the owner window
        HWND hMenuWnd = FindWindow(L"MyCustomMenu", 0);


        if (hMenuWnd)
        {
            POINT pt; RECT rcWindow;
        
            GetCursorPos(&pt);
            GetWindowRect(hMenuWnd, &rcWindow);
            
            // if the mouse action is outside the menu, hide it. the window procedure will also unset this hook 
            if (!PtInRect(&rcWindow, pt))
                ShowWindow(hMenuWnd, SW_HIDE);
        }   
    }

    return CallNextHookEx(NULL, Code, wParam, lParam);
}


Later Edit: There are mouse action that the hook will not intercept such as clicks outside the owner window as they are not posted on the hook thread queue. Handle WM_KILLFOCUS message in the owner procedure and check if the menu is visible then hide it.

C++
case WM_KILLFOCUS:
    if (IsWindowVisible(hMenuWnd))
        ShowWindow(hMenuWnd, SW_HIDE);
    
    break;

You will also have to hide the menu yourself when one of it's internal controls are clicked.

-Alex
 
Share this answer
 
v2
Comments
[no name] 28-Mar-14 18:07pm    
You should publish this as a Tip.
C3D1 8-May-14 6:00am    
Thank you so much!
This helped me a lot in my current project!
When I've done this, I had to do three things.

1. Owner window calls ShowWindow(SW_SHOWNA) to display the popup window.
2. If by chance the popup did receive a WM_ACTIVATE, it would promptly activate it's owner window.
3. Forward keypress events from the owner window to the popup.

I did not use WS_EX_NOACTIVATE or WS_EX_TOPMOST.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900