Click here to Skip to main content
15,861,168 members
Articles / Programming Languages / C++
Article

Manipulating Windows using messages and simple CBT hooking

Rate me:
Please Sign up or sign in to vote.
4.68/5 (33 votes)
7 Aug 2003Ms-PL8 min read 178.3K   2.1K   72   26
Demonstrates techniques using windows messages and hooks, which allow us to automate a windows properties dialog or even custom applications

Image 1

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, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
QuestionWhat about textboxes, listviews, etc...? Pin
peepsicola18-Dec-07 5:08
peepsicola18-Dec-07 5:08 
QuestionIs completely hidden possible? Pin
leochou072919-Dec-06 21:53
leochou072919-Dec-06 21:53 
QuestionWokring under Windows CE Pin
Firas19-Nov-06 1:20
Firas19-Nov-06 1:20 
GeneralQuestion on Windows Pin
Gabriyel12-May-06 4:19
Gabriyel12-May-06 4:19 
GeneralRemote Control Pin
kissmoses3-Jan-06 9:24
kissmoses3-Jan-06 9:24 
Generaldll compile error Pin
peter ho9-Mar-05 13:38
peter ho9-Mar-05 13:38 
GeneralHooking to push buttons Pin
HarishB24-Nov-04 17:59
HarishB24-Nov-04 17:59 
GeneralReally a very nice article. Pin
Digvijay Chauhan4-Jul-04 0:43
Digvijay Chauhan4-Jul-04 0:43 
GeneralMove the mouse Pin
Timbit12-Nov-03 1:12
Timbit12-Nov-03 1:12 
GeneralRe: Move the mouse Pin
grv5754-Aug-04 4:59
grv5754-Aug-04 4:59 
If you want to simulate mouse presses send the WM_LBUTTONDOWN message to the window. the wparam should be set to 1 I believe and the lparam parameter determines the x-y coordinates of the button click. To get this parameter use something like spy++ or winspector (maybe?) to get the right code when you click the button yourself (in other words use the message monitor mode).
GeneralGetting handle to window just created but not active Pin
«_Superman_»16-Sep-03 19:10
professional«_Superman_»16-Sep-03 19:10 
GeneralSuggestion, alternative Pin
Saikat Sen18-Aug-03 21:09
Saikat Sen18-Aug-03 21:09 
GeneralRe: Suggestion, alternative Pin
Nish Nishant25-Aug-03 18:16
sitebuilderNish Nishant25-Aug-03 18:16 
GeneralAlso relevant... Pin
Shog910-Aug-03 13:43
sitebuilderShog910-Aug-03 13:43 
GeneralRe: Also relevant... Pin
Nish Nishant25-Aug-03 18:15
sitebuilderNish Nishant25-Aug-03 18:15 
GeneralRe: Also relevant... Pin
Shog926-Aug-03 4:51
sitebuilderShog926-Aug-03 4:51 
GeneralLocalized Windows versions Pin
barto9-Aug-03 3:15
barto9-Aug-03 3:15 
GeneralRe: Localized Windows versions Pin
Nish Nishant9-Aug-03 3:32
sitebuilderNish Nishant9-Aug-03 3:32 
GeneralWindows Scripting Might Be More Flexible/Robust Pin
Mike O'Neill8-Aug-03 15:42
Mike O'Neill8-Aug-03 15:42 
GeneralRe: Windows Scripting Might Be More Flexible/Robust Pin
Nish Nishant25-Aug-03 18:14
sitebuilderNish Nishant25-Aug-03 18:14 
GeneralThere's a catch here... Pin
Andy Marks8-Aug-03 4:58
Andy Marks8-Aug-03 4:58 
GeneralRe: There's a catch here... Pin
Nish Nishant8-Aug-03 5:01
sitebuilderNish Nishant8-Aug-03 5:01 
GeneralRe: There's a catch here... Pin
.:floyd:.22-Aug-03 15:21
.:floyd:.22-Aug-03 15:21 
GeneralRe: There's a catch here... Pin
Anonymous23-Aug-03 1:06
Anonymous23-Aug-03 1:06 
GeneralRe: There's a catch here... Pin
.:floyd:.23-Aug-03 5:21
.:floyd:.23-Aug-03 5:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.