Thread: WM_ACTIVATE woes...

  1. #1
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547

    WM_ACTIVATE woes...

    (Note: Also posted on Pelles C forums)

    The problems with WM_ACTIVATE for setting keyboard focus are well known. This is one solution but it's still not right.

    Can someone please suggest a better way...

    I've just spent a whole day struggling with WM_ACTIVATE... I have a window with a treeview and a listview in the more or less standard explorer configuration... The goal is to have it place the keyboard focus back where it came from after the main window loses then regains forgeround status... It's insane! Half the time GetFocus() returns NULL. When the window is minimized it returns different values than if you just click on the desktop... worse still, if you don't handle the message at all, it comes back to foreground with *nothing* selected and you have to click it to get it going again...

    Here's the handler code I came up with. It mostly works but there remains a problem that if you launch the program and immediately minimize it... or if you minimize it before it loses focus some other way, it resets back to the treeview control... (As you can see from my notes, I'm convinced there has to be a better way)

    Code:
    // set keyboard focus
    BOOL DoWMActivate(WPARAM Action)                // note this is a horrible Kludge
      { static HWND Focus;                          // it seriously needs fixing
        switch (Action)                             // Microsoft seriously needs fixing
         { case WA_INACTIVE : 
            Focus = GetFocus();
            break;
          case WA_ACTIVE :
          case WA_CLICKACTIVE :
            SetFocus( Focus ? Focus : gTreeView );
            break;
          default : 
            SetFocus(gTreeView); }
        return FALSE; }
    Last edited by CommonTater; 09-29-2011 at 01:16 AM.

  2. #2
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    >> The goal is to have it place the keyboard focus back where it came from after the main window loses then regains forgeround status...
    The OS already does this automatically. So the question is, why doesn't this occur in your case.

    This may provide some insight: http://blogs.msdn.com/b/jfoscoding/a...02/686141.aspx

    You can spy on your message Q, like the article does, with this: WinSpy++ 1.7 | www.catch22.net

    gg

  3. #3
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    No it doesn't...

    When a top level window loses focus, then regains it, the DefWindowProc() assigns focus to the first child with the WS_TABSTOP style, in my case, that's a toolbar. It does not return to the actual control that had focus. If you intercept WM_ACTIVATE you will discover the only window handle that ever occurs in the LPARAM of the message is that of your main window... not the child window that had focus.

    GetFocus() returns NULL for most cases. The only one that's reliable is in the WA_INACTIVE case, you can trap the currently focused window... *except* when minimizing the parent. Then it returns NULL... wiping out the saved focus handle.

    Trust me... I lost a whole day messing with this... been up one side of it and down the other 20 times, at least... every diagnostic told me the same thing...

  4. #4
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    WinSpy++ doesn't spy on messages. But here is Spy++ MDB Blog: Microsoft Spy++ or Spyxx for download

    A minimal, complete console example that demonstrates the problem is a good way to get folks to tackle the problem. But I already had a template:
    Code:
    #include <windows.h>
    #include <iostream>
    #include <iomanip>
    #include <string>
    using namespace std;
    
    #define CONTROL_ID1  41
    #define CONTROL_ID2  42
    
    LRESULT CALLBACK WinProc(HWND, UINT, WPARAM, LPARAM);
    
    int main()
    {
        const char *className = "WindowsApp";
    
        WNDCLASSA wincl = {0};
        wincl.hInstance = GetModuleHandle(0);
        wincl.lpszClassName = className;
        wincl.lpfnWndProc = WinProc;
        wincl.style = CS_DBLCLKS;    
        wincl.hIcon = LoadIcon(0, IDI_APPLICATION);
        wincl.hCursor = LoadCursor(0, IDC_ARROW);
        wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
    
        if (!RegisterClassA(&wincl))
            return 1;
    
        HWND hwndParent;
        hwndParent = CreateWindowA(className, "Windows App", 
                                   WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                                   CW_USEDEFAULT, CW_USEDEFAULT,
                                   200, 110, HWND_DESKTOP, 0, 
                                   GetModuleHandle(0), 0);
        if (!hwndParent)
            return 1;
    
        HWND hwndChild1;
        hwndChild1 = CreateWindowExA(WS_EX_CLIENTEDGE, "Edit", "Text1", 
                                     WS_CHILD | WS_VISIBLE | WS_TABSTOP,
                                     10, 10,  170, 20,
                                     hwndParent, (HMENU)CONTROL_ID1, 
                                     GetModuleHandle(0), 0);
        if (!hwndChild1)
            return 1;
        
        HWND hwndChild2;
        hwndChild2 = CreateWindowExA(WS_EX_CLIENTEDGE, "Edit", "Text2", 
                                     WS_CHILD | WS_VISIBLE | WS_TABSTOP,
                                     10, 45,  170, 20,
                                     hwndParent, (HMENU)CONTROL_ID2, 
                                     GetModuleHandle(0), 0);
        if (!hwndChild2)
            return 1;
    
        MSG msg;
        BOOL bRet;
        for (;;)
        {
            bRet = GetMessageA(&msg, hwndParent, 0, 0);
            if ((bRet == 0) || (bRet == -1))
                break;
    
            if (!IsWindow(hwndParent) || !IsDialogMessage(hwndParent, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }//if    
        }//while
    
        return (int)msg.wParam;
    }//main
    
    
    LRESULT CALLBACK WinProc(HWND hwnd, UINT msg, 
                             WPARAM wParam, LPARAM lParam)
    {
        // debug log of messages - Spy++ is better though
        if (msg != WM_SETCURSOR && 
            msg != WM_MOUSEFIRST && 
            msg != WM_NCHITTEST &&
            msg != WM_NCMOUSEMOVE)
        {
            cout << "Got msg 0x" << hex << setw(4) << setfill('0') << msg 
                 << dec << ", " << int(wParam)
                 << ", " << int(lParam) << endl;
        }//if
    
        static HWND s_lastFocus = 0;
    
        switch(msg)
        {
            case WM_DESTROY: 
            {
                PostQuitMessage(0);
                return 0;
            }//case
    
            case WM_SYSCOMMAND:
            {
                if (wParam == SC_MINIMIZE)
                {
                    // capture the focus before minimizing - must do this here
                    // instead of in WA_INACTIVE when minimizing
                    HWND prev = s_lastFocus;
                    s_lastFocus = GetFocus();
                    cout << "Minimize: " << int(prev) << "->" 
                         << int(s_lastFocus) << endl;
                }//if
                break;
            }//case
    
            case WM_ACTIVATE:
            {
                if (LOWORD(wParam) == WA_INACTIVE)
                {
                    // do not capture who has focus on minimize, the SC_MINIMIZE 
                    // handler will do that
                    const bool bIsMinimizing = HIWORD(wParam) != 0;
                    if (!bIsMinimizing) 
                    {
                        HWND prev = s_lastFocus;
                        s_lastFocus = GetFocus();
                        cout << "Inactive: " << int(prev) << "->" 
                             << int(s_lastFocus) << endl;
                    }//if
                }//if
                else if (s_lastFocus)
                {
                    cout << "Restore: " << int(s_lastFocus) << endl;
                    SetFocus(s_lastFocus);
                    // don't let DefWindowProc() handle it, or it will change focus 
                    return 0; 
                }//else if
                else
                {
                    cout << "Activate(" << LOWORD(wParam) << "," << HIWORD(wParam) 
                         << ")" << endl;
                }//else
            }//case
        }//switch
    
        return DefWindowProcA(hwnd, msg, wParam, lParam);
    }//WinProc
    I had to use SC_MINIMIZE to capture who has focus before the minimize - since the focus is already lost by the time WM_ACTIVATE comes in that case.
    Don't let DefWindowProc() handle an activate, since it sets focus.

    gg

  5. #5
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Thanks... I'll have to C-ify that and give it a try. Catching focus on SC_MINIMIZE is an intriguing idea that I hadn't considered.

    One thing I did try that works more reliably in all but the minimize case is to block WM_ACTIVATE totally...
    Code:
    case WM_ACTIVATE :
      return 0;
    and then use WM_ACTIVATEAPP...
    Code:
    case WM_ACTIVATEAPP :
       // wparam = true for get focus, false for lose focus
       if (wParam)
         SetFocus( gFocusWin );
       else 
         gFocusWin = GetFocus();   // returns NULL on minimize
      return 0;
    If I combine that with SC_MINIMIZE to prime the gFocusWin variable I may be able to modify the handler to get what I need...
    Code:
    case WM_ACTIVATEAPP:
      { int tfocus;
         if (wParam)
          SetFocus( gFocusWin );
        else
          { tfocus = GetFocus();
             gFocusWin = tfocus ? tfocus : gFocusWin; } 
        return 0; }
    It want's testing, of course, but that should be pretty close if I use SC_MINIMIZE to prime the gFocusWin.

    Another solution I plan to test was proposed here...

    using the WM_NOTIFY / NM_SETFOCUS messages... This is also interesting, but not quite as simple...
    Last edited by CommonTater; 09-29-2011 at 12:28 PM. Reason: Clean up link

  6. #6
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Ok... been messing with this all day again...

    Here's my code...

    In the message tosser...
    Code:
            case WM_ACTIVATE :
              return 0;
            case WM_ACTIVATEAPP :
              return DoWMActivateApp(wParm);
            case WM_SYSCOMMAND :
              switch(LOWORD(wParm & 0xFFF0))
                { case SC_MINIMIZE :
                    DoSCMinimize();
                    return DefWindowProc(hWind,Msg,wParm,lParm);
                  default :
                    return DefWindowProc(hWind,Msg,wParm,lParm); }
    The functions...

    Code:
    // catch focus on minimize
    VOID DoSCMinimize( void )
      { FocusWin = GetFocus(); }
    
    
    // set keyboard focus
    BOOL DoWMActivateApp(WPARAM Action)               
      { HWND ft = GetFocus(); 
    
        if(Action)
          SetFocus(FocusWin);
        else
          FocusWin = ft ? ft : FocusWin; 
    
    // debug log
    fprintf(f,"Action = %llX\tft = %P\tFocusWin = %P\n",Action,ft,FocusWin);
    fflush(f);
    
        return FALSE; }
    And the debug log...
    PHP Code:
    Pass 1 launch then exit.

    Action 0    ft 0000000000110184    FocusWin 0000000000110184 <-- tree

    Pass 2 
    launchlose focusget focus

    Action 
    0    ft 00000000001702D2    FocusWin 00000000001702D2 <-- tree
    Action 
    1    ft 0000000000000000    FocusWin 00000000001702D2
    Action 
    0    ft 00000000001702D2    FocusWin 00000000001702D2

    Pass 3 
    launchlose focusget focusselect list, lose focusget focus

    Action 
    0    ft 00000000001001E2    FocusWin 00000000001001E2 <-- tree
    Action 
    1    ft 0000000000000000    FocusWin 00000000001001E2
    Action 
    0    ft 00000000001802EA    FocusWin 00000000001802EA <-- list 
    Action 1    ft 0000000000000000    FocusWin 00000000001802EA
    Action 
    0    ft 00000000001802EA    FocusWin 00000000001802EA

    Pass 4 
    launchlose focusget focusminimizerestore

    Action 
    0    ft 00000000001401C2    FocusWin 00000000001401C2 <-- tree
    Action 
    1    ft 0000000000000000    FocusWin 00000000001401C2
    Action 
    0    ft 0000000000000000    FocusWin 00000000001401C2
    Action 
    1    ft 0000000000000000    FocusWin 00000000001401C2
    Action 
    0    ft 0000000000220118    FocusWin 0000000000220118 <-- main window 
    I have to admit, I'm at something of a loss... For ordinary stuff like opening a different window or clicking on the window frame, it works fine but there seems to be no way to restore focus to the last control on return from minimize. As you can see in that last sequence, the system itself sabotages me.

    Any suggestions?
    Last edited by CommonTater; 09-29-2011 at 07:55 PM.

  7. #7
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    >> the system itself sabotages me
    Just like WM_ACTIVATE does, except WM_ACTIVATEAPP doesn't tell you when it's a deactivate-via-minimize-sabotage case. So you end up storing "main window" into FocusWin.

    WM_ACTIVATE sabotages with "GetFocus() = 0".
    WM_ACTIVATEAPP sabotages with "GetFocus() = main window".

    So I would just stick with WM_ACTIVATE, since it tells you when it's minimizing - so you can ignore whatever GetFocus() has to say at that point. (I wouldn't rely on it only returning 0 in the sabotage case.)

    Code:
    #include <windows.h>
    #include <stdio.h>
    
    #define CONTROL_ID1  41
    #define CONTROL_ID2  42
    
    LRESULT CALLBACK WinProc(HWND, UINT, WPARAM, LPARAM);
    
    int main()
    {
        const char *className = "WindowsApp";
    
        WNDCLASSA wincl = {0};
        wincl.hInstance = GetModuleHandle(0);
        wincl.lpszClassName = className;
        wincl.lpfnWndProc = WinProc;
        wincl.style = CS_DBLCLKS;    
        wincl.hIcon = LoadIcon(0, IDI_APPLICATION);
        wincl.hCursor = LoadCursor(0, IDC_ARROW);
        wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
    
        if (!RegisterClassA(&wincl))
            return 1;
    
        HWND hwndParent;
        hwndParent = CreateWindowA(className, "Windows App", 
                                   WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                                   CW_USEDEFAULT, CW_USEDEFAULT,
                                   200, 110, HWND_DESKTOP, 0, 
                                   GetModuleHandle(0), 0);
        if (!hwndParent)
            return 1;
    
        HWND hwndChild1;
        hwndChild1 = CreateWindowExA(WS_EX_CLIENTEDGE, "Edit", "Text1", 
                                     WS_CHILD | WS_VISIBLE | WS_TABSTOP,
                                     10, 10,  170, 20,
                                     hwndParent, (HMENU)CONTROL_ID1, 
                                     GetModuleHandle(0), 0);
        if (!hwndChild1)
            return 1;
        
        HWND hwndChild2;
        hwndChild2 = CreateWindowExA(WS_EX_CLIENTEDGE, "Edit", "Text2", 
                                     WS_CHILD | WS_VISIBLE | WS_TABSTOP,
                                     10, 45,  170, 20,
                                     hwndParent, (HMENU)CONTROL_ID2, 
                                     GetModuleHandle(0), 0);
        if (!hwndChild2)
            return 1;
    
        MSG msg;
        BOOL bRet;
        for (;;)
        {
            bRet = GetMessageA(&msg, hwndParent, 0, 0);
            if ((bRet == 0) || (bRet == -1))
                break;
    
            if (!IsWindow(hwndParent) || !IsDialogMessage(hwndParent, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }//if    
        }//while
    
        return (int)msg.wParam;
    }//main
    
    
    LRESULT CALLBACK WinProc(HWND hwnd, UINT msg, 
                             WPARAM wParam, LPARAM lParam)
    {
        static HWND s_lastFocus = 0;
    
        switch(msg)
        {
            case WM_DESTROY: 
            {
                PostQuitMessage(0);
                return 0;
            }//case
    
            case WM_SYSCOMMAND:
            {
                if (wParam == SC_MINIMIZE)
                {
                    // capture the focus before minimizing - must do this here
                    // instead of in WA_INACTIVE when minimizing
                    HWND prev = s_lastFocus;
                    s_lastFocus = GetFocus();
                    
                    printf("Minimize: %p -> %p\n", prev, s_lastFocus);
                }//if
                break;
            }//case
    
            case WM_ACTIVATE:
            {
                if (LOWORD(wParam) == WA_INACTIVE)
                {
                    // do not capture who has focus on minimize, the SC_MINIMIZE 
                    // handler will do that
                    const bool bIsMinimizing = HIWORD(wParam) != 0;
                    if (!bIsMinimizing) 
                    {
                        HWND prev = s_lastFocus;
                        s_lastFocus = GetFocus();
    
                        printf("Inactive: %p -> %p\n", prev, s_lastFocus);
                    }//if
                    else
                    {
                        printf("Inactive: Ignoring minimize\n");
                    }//else
                }//if
                else if (s_lastFocus)
                {
                    printf("Restore: %p\n", s_lastFocus);
    
                    SetFocus(s_lastFocus);
                    // don't let DefWindowProc() handle it, or it will change focus 
                    return 0; 
                }//else if
                else
                {
                    printf("Activate: wParam = %u | %u\n", 
                           LOWORD(wParam), HIWORD(wParam));
                }//else
            }//case
        }//switch
    
        return DefWindowProcA(hwnd, msg, wParam, lParam);
    }//WinProc
    Code:
    * launch, set focus, minimize, restore
    Activate: wParam = 1 | 0
    Minimize: 00000000 -> 00080A26
    Inactive: Ignoring minimize
    Restore: 00080A26
    Restore: 00080A26
    gg

  8. #8
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Quote Originally Posted by Codeplug View Post
    >> the system itself sabotages me
    Just like WM_ACTIVATE does, except WM_ACTIVATEAPP doesn't tell you when it's a deactivate-via-minimize-sabotage case. So you end up storing "main window" into FocusWin.

    WM_ACTIVATE sabotages with "GetFocus() = 0".
    WM_ACTIVATEAPP sabotages with "GetFocus() = main window".

    So I would just stick with WM_ACTIVATE, since it tells you when it's minimizing - so you can ignore whatever GetFocus() has to say at that point. (I wouldn't rely on it only returning 0 in the sabotage case.)
    Exactly... I've seen the GetFocus() return both 0s and the main window handle in both functions during that "sabotage period"... If I stick with a very simple implementation in either message I can get it to do foreground/background focus correctly... it's just that cursed minimize case that won't step into line for me...

    Oddly enough if you open Windows Explorer and minimize then restore... it does what I'm after... But how?

    I just can't get past the notion there's something extremely simple that I'm missing...

    Google has quite a few links to "wm_activate problem" and similar searches but I haven't seen any real answers other than to simply restate how the messages work...

    (And people wonder why I have gray hair!)

  9. #9
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    Does the code in post #4 and #7 not work for you?

    gg

  10. #10
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Quote Originally Posted by Codeplug View Post
    Does the code in post #4 and #7 not work for you?
    Yes, the cut and paste versions do appear to mostly work but the complication is that I am using other WM_SYSCOMMAND functions to add to menus and manipulate the standy state (auto disconnect from server, rejoin at resume, etc.) and when tested with my real code they became somewhat unreliable in that, I could no longer minimized the window... probably because the DefWindowProc() was not being called after SC_MINIMIZE... It's nothing I couldn't work out but I would not be able to use it as-is... The concept, however is brilliant... I wouldn't have thought of that on my own.

    My latest experiment is with a combination of WM_ACTIVATE and WM_SIZE.... using essentially the same code in both places, capturing the SIZE_MINIMIZED and SIZE_RESTORED in much the same way as WA_INACTIVE and WA_ACTIVE... I just have to figure out how to work it gracefully with the window resizing code... There's always something...

    I'm almost to the point of resignation... Take what I can get and hope there's no problem with what I can't... This is only one very small corner of a large project and I do need to get on with the rest...

    In case you're curious here's a little screen shot... The task bar is still pretty rough, but it's on the to-do list...

    WM_ACTIVATE woes...-bmb_screenshot-jpg

  11. #11
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    GOT IT! Finally...

    This will set the focus window (fwin) for when the window regains focus. Because it ignores anything but the three specific values the crap during minimize never happens... and it works fine when clicking onto and off of the window, even works if you open another window on top of it.

    This is the WM_ACTIVATE handler...
    Code:
    // focus manager
    BOOL DoWMActivate(WPARAM wParm)
     { static HWND fwin; // focus window
       switch(wParm)
        { case WA_INACTIVE :
            fwin = GetFocus();
            return 0;
          case WA_ACTIVE :
          case WA_CLICKACTIVE :
            SetFocus(fwin);
            return 0; 
          default :
            return 0; } }
    The problem --finally understood-- was that the value of fwin was not being reliably initialized...
    This problem I fixed like this...

    In my message proc...
    Code:
            case WM_ACTIVATE :
              return DoWMActivate(wParm);
    ...
            case WM_NOTIFY :
              switch(((LPNMHDR) lParm)->code)
                { // general
                  case NM_SETFOCUS :
                    return DoWMActivate(WA_INACTIVE);
                  case NM_CLICK :
    Calling the focus handler each time a control sends NM_SETFOCUS initializes the fwin value for me...

    It Works!
    Last edited by CommonTater; 09-30-2011 at 04:16 AM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Wm_activate
    By Hawkin in forum Windows Programming
    Replies: 3
    Last Post: 04-01-2007, 06:28 PM
  2. .NET woes
    By VirtualAce in forum A Brief History of Cprogramming.com
    Replies: 49
    Last Post: 07-18-2006, 02:41 PM
  3. MD2 woes
    By psychopath in forum Game Programming
    Replies: 9
    Last Post: 07-02-2005, 07:46 PM
  4. ASP.NET woes
    By nickname_changed in forum C# Programming
    Replies: 0
    Last Post: 03-20-2004, 04:34 PM
  5. Dll woes
    By Abiotic in forum Windows Programming
    Replies: 3
    Last Post: 11-09-2003, 11:32 AM