views:

138

answers:

5

I have a Windows box that is running three applications. When the applications are started each application creates a borderless window which is positioned such that they overlap in a particular way.

At the moment, when I click on a control on the bottom window, it comes to the top of the window stack.

I need to ensure that each window keeps its order in the window stack even if a window receives input.

I am thinking I need to write some sort of simple window manager that will maintain the correct Z-order of the windows.

The problem is, I need to know if a particular window has changed position. I have found that there is a WM_WINDOWPOSCHANGING message but my understanding is that this message is send to the window whose position has changed.

I need my window manager application to be somehow notified that the Z-order has changed.

Is there some way I can catch all WM_ messages and determine if the message applies to one of the windows I wish to control?

A: 

you can use SetWindowPos to position your windows in the Z order you want. I suggest you intercept the WM_FOCUS message (this is sent to your window when it receives focus)

In your wndProc function, maybe you can try something like this:

LRESULT wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
    // other stuff..

    switch (msg){
        case WM_FOCUS:
        {
            HWND firstWindow; // get the first window here
            HWND secondWindow; // this would be the second window
            HWND thirdWindow; // this would be the third window
            // TODO: initialize the windows correctly, based on your priority
            SetWindowPos(firstWindow, secondWindow, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE); // position the second window below the first window
            SetWindowPos(secondWindow, thirdWindow, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE); // position the third window below the second window
        }
        return 0;
    }
    // other messages..
}

I'm not quite sure of the ordering of the SetWindowPos arguments since I can't test the code right now, but maybe this can get you going?


If you need to intercept all WM_ messages, I would suggest a Window class that the applications call instead (which I guess) of calling CreateWindowEx themselves. For instance:

class Window {
public
    Window(){
        ...
        WNDCLASSEX wc;
        ZeroMemory(&wc, sizeof(WNDCLASSEX));
        wc.cbSize        = sizeof(WNDCLASSEX);
        wc.lpfnWndProc   = wndProc;            // <- Note this one
        ...
    }

    static LRESULT WINAPI wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
        // reference: http://www.gamedev.net/community/forums/topic.asp?topic_id=303854 - Evil Steve  [Moderator]
        Window* parent;

        // Get pointer to window
        if(msg == WM_CREATE){
            parent = (Window*)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLongPtr(hwnd,GWL_USERDATA,(LONG_PTR)parent);
        }
        else{
            parent = (Window*)GetWindowLongPtr(hwnd,GWL_USERDATA);
            if(!parent) return DefWindowProc(hwnd,msg,wParam,lParam);
        }
        HWND prev = parent->mWin;
        parent->mWin = hwnd;
        LRESULT ret = parent->wndProc(msg,wParam,lParam);
        parent->mWin = prev;
        return ret;
    }

    virtual LRESULT wndProc(UINT msg, WPARAM wParam, LPARAM lParam){
    }
};

In this example your applications would inherit from Window, basically providing a slightly modified wndProc function (it would be missing the HWND so this would need to be stored somewhere, unless you pick it up from the Userdata).

  • Everytime you receive a message the Window::wndProc(HWND, UINT, WPARAM, LPARAM) function would pick it up. In here you could do your checks of any messages, including (but not limited to) WM_WINDOWPOSCHANGING.

  • The other thing to do would be that:
    In the wndProc(UINT, WPARAM, LPARAM), instead of calling DefWindowProc(..)
    you call Window::wndProc(UINT, WPARAM, LPARAM). Then you could do your checks in there instead (as to not cludge the first wndProc function) :)

The downside of this would be that if the applications are written by someone else, they would need to comply to your windowclass. As you explain it the user shouldn't need to interact with your window manager, however, with this approach the only interaction would be to let your window manager create the window for the user.
Otherwise I think that you would have to go with the hook explained in the other answers

Default
I would prefer it if the user should not be required to interact with the 'window manager' application.I was hoping that there would be some way to detect when the window order changed and then have the 'window manager' application respond automatically.For example, a kludgey way to do it would be to set a timer so that every X period of time the 'window manager' application would check the window order and reorder if necessary. I was hoping there would be some other message that could be used to determine when to check if reordering is necessary.
JRT
A: 

The easiest way is probably to inject a DLL into each of the three applications. This ensures that you only have to deal with the subset of window messages that you actually care about.

Next, find the main window in each application (not entirely trivial, in theory there could be more) by calling EnumWindows() to find all windows, and calling GetWindowThreadProcessId() on each to determine whether it belongs to the current process (i.e. the one your DLL is injected in).

Now that you have the correct HWND, you can hook the associated WndProc and catch any WM_WINDOWPOSCHANGING sent to it.

MSalters
A: 

When you create the two windows that you want to be on top, Give the window that you want to be on bottom as the hWndParent value to CreateWindow. Windows will then always bring those windows forward when the bottom window is brought forward so that they always stay in front of it.

So, if your bottom window is window 1. Create it first, then when you create windows 2 & 3, give window 1's handle as the hWndParent value. The window manager does the rest.

John Knoeller
A: 

Instead of MSalter's approach trying to DLL injection into each of the running the application, consider installing a WH_CBT Windows hook. In your CBTProc, return 0 when you get a HCBT_MOVESIZE for the three application window handles you care about.

Read MSDN for the docs on CBTProc and SetWindowsHookEx.

Ants
A: 

I think I will agree with John Knoeller's answer. If you want the windows to stay in a particular z order, then determine what this order is and create your windows with the appropriate parent-child relationships.

::SetWindowLong(hwnd_child, GWL_HWNDPARENT, hwnd_parent);

When you do that the child windows will always stay on top of the parent.

If you still insist on catching messages you can try catching WM_ACTIVATE in each window and then forwarding that message to your window manager which will have access to the hwnds of all the windows and z-order them correctly using SetWindowPos. And instead of SetWindowPos you can use DeferWindowPos to change z-order all at once for the windows and avoid flicker.

crazyx