Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Manipulating Windows using messages and simple CBT hooking

0.00/5 (No votes)
7 Aug 2003 1  
Demonstrates techniques using windows messages and hooks, which allow us to automate a windows properties dialog or even custom applications

Introduction

Windows is essentially a message driven Operating System in the sense that, the majority of actions that take place are responses to messages sent to the main window procedure of an application. Whether you press a key, or move the mouse or drag a window the application receives messages through it's message queue and reacts accordingly. Now this results in a rather interesting corollary that can be taken advantage of by us, developers; by sending the correct messages to a window or it's child windows in the proper order, we can actually simulate human actions on an application. And this has it's uses in various scenarios. Obviously the first one that comes to mind is the ability to automate a task, like for example opening a document in word, left justifying the entire text and taking a print out.

But for me a more interesting usage of this technique is when it's applied to take advantage of the Windows user interface to quickly do tasks, which might otherwise require a lot of programming calls and access to undocumented information. This includes changing various system properties, making changes through the control panel applets or even changing display properties for the desktop. In this article I will randomly select one such scenario ( a test case scenario ) and see how to go about automating the task by using some simple windows techniques like posting messages to a window, enumerating child windows and elementary CBT hooking.

Test scenario

I am going to be using Windows XP Professional as my test platform and therefore my example scenario is only meaningful in an XP context. Users of other Operating Systems might have to make suitable changes to my example code snippets to get the same end result as in this article.

By default, the keyboard navigation short cuts for menus are not shown in the XP operating system ( something that both puzzled and annoyed a lot of users when they first encountered this in Windows 2000 ). Having long abandoned Windows 2000, I do not remember if there was a documented way of changing this setting in 2000, other than by editing the registry or using some tweaking application, but in XP this setting can be easily changed by using the Display Properties control panel applet. All you need to do is select the Appearances tab from the Display Properties dialog box, bring up the Effects sub-window and uncheck the check box that says "Hide underlined letters for keyboard navigation until I press the Alt key". Now I am wholly sure that this setting can probably be changed by modifying a trivial registry entry; but for the sake of this test case scenario and the article, let's assume that we do not know how to achieve the same programmatically.

The human approach

Let's see how we'd do this had we done this manually sitting in front of the machine. We'd probably have to follow these steps ( or something very similar ) :-

  1. Right click on the desktop and bring up the Display Properties control panel applet
  2. Chose the Appearances tab
  3. Bring up the Effects sub-window by clicking on the Effects button
  4. Check/Uncheck the corresponding check box depending on what we are trying to do
  5. Click OK to dismiss the Effects sub-window
  6. Click OK to dismiss the Display Properties and apply our changes globally

The solution in code

Now we need to decide what we need to do to achieve the same sequence of events through code.

  1. Bringing up the Display Properties window and choosing the Appearances tab can be done in just one step because we know that the Display Properties control panel applet is called desk.cpl and that it takes command line arguments that can be used to dictate which tab comes up by default. In fact we need to call it like this :-
    control.exe desk.cpl Display,@Appearance

    Control.exe is used to bring up the control panel applet passed to it as first argument, and the additional arguments are used to force it to start with the Appearance tab selected.

  2. Now we need to enumerate the child windows ( controls ) on the Appearance tab till we find the Effects button and this can be achieved using EnumChildWindows. Once we obtain the Effects button's handle we can send a button click message to it and bring up the Effects sub-window.
  3. To locate the required check box on the Effects sub-window we would need to first get the handle to the sub-window that just popped up. We achieve this by setting up a global CBT hook ( this means we'd need to put all put code into a DLL ), and watching for all newly activated windows. We know the title text for the Effect sub-window and thus we obtain the handle to the window the moment it gets activated. Now we do the same as previous, i.e. we use EnumChildWindows to retrieve the handle to the check box, and then send a button click message to it.
  4. We post a WM_COMMAND message to the Effects sub-window with a command ID of IDOK which is the equivalent of closing the window by clicking on the OK button.
  5. We do the same for the main Display Properties window.

Implementation details

Bringing up the Display Properties window

BOOL BringUpDisplayAppearance()
{
    return reinterpret_cast<int>(ShellExecute(GetDesktopWindow(),
        "open","control.exe","desk.cpl Display,@Appearance",
        "",SW_SHOW )) > 32 ? TRUE : FALSE; 
}

The code is quite simple and straightforward, we simply use ShellExecute to bring up the Display Properties applet window with the default tab set to the Appearances tab. I have used SW_SHOW here because using SW_HIDE will have no effect on the display properties window ( I believe the control.exe program or perhaps desk.cpl itself will later call ShowWindow(hWnd, SW_SHOW) somewhere in the code ). We do our window hiding in the hook procedure ( but even this is not fully effective and there is a short flash on screen, but then our aim is not really to hide what we are doing from our end user, but to try and make things as lucid as possible, which we achieve by reducing the time the window remains visible to a few milliseconds ).

Getting the handle to the Effects button

HWND GetEffectsButton(HWND hWndParent)
{
    HWND hWnd = NULL;

    EnumChildWindows(hWndParent, EnumAppearanceChildProc, 
        (LPARAM)&hWnd);
    return hWnd;
}

BOOL CALLBACK EnumAppearanceChildProc(HWND hwnd, LPARAM lParam)
{
    TCHAR buff[512];
    GetWindowText(hwnd,buff,512); 
    if(_tcscmp(buff,_T("&Effects...")) == 0)
    { 
        *reinterpret_cast<HWND*>(lParam) = hwnd;
        return FALSE;
    }
    return TRUE;
}

Using Spy++ we extract the exact text associated with the Effects button which happens to be "&Effects..." and we use this knowledge to compare the text of each child control with this text repeatedly till we get the button control we want.

Getting the check box handle

HWND GetMenuUnderlineCheck(HWND hWndParent)
{
    HWND hWnd = NULL; 
    EnumChildWindows(hWndParent, EnumEffectsChildProc, 
        (LPARAM)&hWnd);
    return hWnd;
}

BOOL CALLBACK EnumEffectsChildProc(HWND hwnd, LPARAM lParam)
{
    TCHAR buff[512];
    GetWindowText(hwnd,buff,512); 
    if(_tcsstr(buff,_T("&Hide underlined")))
    { 
        *reinterpret_cast<HWND*>(lParam) = hwnd;
        return FALSE;
    }
    return TRUE;
}

This is very similar to how we obtained the handle to the Effects button.

The CBT hook procedure

LRESULT CALLBACK CBTProc(int nCode, 
                         WPARAM wParam, LPARAM lParam)
{
    if(nCode == HCBT_ACTIVATE)
    {
        HWND hWnd = (HWND) wParam;
        TCHAR buff[512];
        GetWindowText(hWnd,buff,512);
        if(_tcscmp(buff,_T("Effects")) == 0)
        { 
            ShowWindow(hWnd,SW_HIDE);
            g_hWndEffects = hWnd; 
            UnhookWindowsHookEx(g_hook); 
        }
        if(_tcscmp(buff,_T("Display Properties")) == 0)
        { 
            ShowWindow(hWnd,SW_HIDE); 
        }
    }

    return 0;
}

The HCBT_ACTIVATE code indicates that a window is about to be activated. We compare the title text of this window with "Effects" and if they match, we know that this is the window we were searching for. If you are wondering why we had to install a CBT hook, instead of using FindWindow using the title text; this is to make sure that even if there was already an existing window with the same title text, it won't interfere with our search because we are only checking newly activated windows. We set the CBT hook quite late into the code and uninstall it the moment we get the window we want. The duration the hook is active is from the time we bring up the Display Properties window till the Effects sub-window is just about to be activated, and under most circumstances this shouldn't be more than a few milliseconds.

We also use the hook procedure to hide the windows that pop up, which include both the main Display Properties window as well as the Effects sub-window. The infinitesimal flash still exists and if anyone has any ideas on further reducing this, they are welcome to make suggestions.

The main function ( exported )

DEMODLL_API BOOL ToggleMenuUnderline(void)
{
    HWND hWndAppearance = NULL;
    BOOL ret = TRUE;

    g_hook = SetWindowsHookEx(WH_CBT, 
        CBTProc, g_hModule, 0); 

    ret = BringUpDisplayAppearance();
    Sleep(500);//Wait for the window to come up

    if(ret)
    {
        hWndAppearance = FindWindow(NULL,
            _T("Display Properties"));
        ret = hWndAppearance != NULL; 

        if(ret)
        {
            HWND hWndEffectsButton = GetEffectsButton(
                hWndAppearance);
            ret = hWndEffectsButton != NULL;

            if(ret)
            {
                PostMessage(hWndEffectsButton,BM_CLICK,0,0); 

                //Wait for the Effects window to come up 

                while(!IsWindow(g_hWndEffects))
                    Sleep(100); 

                HWND hWndCheck = GetMenuUnderlineCheck(
                    g_hWndEffects);

                ret = hWndCheck != NULL;

                if(ret)
                { 
                    PostMessage(hWndCheck,BM_CLICK,0,0); 
                    PostMessage(g_hWndEffects,WM_COMMAND,
                        IDOK,NULL);
                    PostMessage(hWndAppearance,WM_COMMAND,
                        IDOK,NULL);

                    //Wait for the window to be dismissed

                    while(IsWindow(hWndAppearance))
                        Sleep(100); 
                }
            }
        }
    }

    //Final checks in case of error conditions

    if(IsWindow(g_hWndEffects))
        PostMessage(g_hWndEffects,WM_CLOSE,0,0);
    if(IsWindow(hWndAppearance))
        PostMessage(hWndAppearance,WM_CLOSE,0,0);

    return ret;
}

We first set up our CBT hook and then call the BringUpDisplayAppearance function to bring up the Display Properties window with the Appearance tab selected. Once the window comes up we obtain the handle to the Effects button using the GetEffectsButton function, and then post a BM_CLICK message to the Effects button using the handle we just obtained. Almost instantly the Effect sub-window pops up and we retrieve it's handle through our CBT hook procedure which also uninstalls the hook since it's no longer of any use to us. We wait for the Effects window to come up, using the following while loop :-

while(!IsWindow(g_hWndEffects))
    Sleep(100);

This way we avoid sleeping for too long or for too less. Now we obtain the handle to the check box using the GetMenuUnderlineCheck function and post a BM_CLICK message to the check box which effectively toggles it's state which is just what we are trying to do. Now we simply post WM_COMMAND messages with wParam set to IDOK to the Effects sub-window as well as to the Display Properties main window. That's all; we have now successfully toggled the state of the "Underline keyboard navigation shortcuts for menus" system-wide property.

Conclusion

The test scenario we considered was perhaps too simplistic to reveal the actual power of this technique, but when you consider that you can now do anything from your program that a user can do manually using the Windows GUI, you'll be slowly impressed by the awesome possibilities of the technique.  You can use this technique to enumerate Windows themes, change the current theme, change display settings, change system settings, automate your own applications etc. The only tool you'd need in addition to Everett is Spy++ or some such similar application. Good luck with your own message based Windows automation attempts.

History

  • August 8th 2003 - First written

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here