Click here to Skip to main content
Click here to Skip to main content

Your Desktop and Microsoft's SetWindowsHookEx()

, 31 Mar 2014
Rate this:
Please Sign up or sign in to vote.
How to detect Desktop mouse (double) clicks using MH_MOUSE_LL.

Introduction

Since one of my applications I wrote recently needed a way to detect left mouse button double clicks on the Desktop, I accepted this challenge and started to investigate. What I found on the internet and by reading the usual docs was a heap of information that I finally got sorted out, and so I'm now able to present an easy step-by-step guide for those who are about to set out on the same quest. Fear no more!

Background

Installing and using a hook to monitor messages from all around the system sounded quite complicated to me, but in the end, it (naturally) turned out to be plain simple. It was all a matter of putting the right pieces together, so to help you spare your valuable time, I present all those pieces you'll need to accomplish your task.

Please be aware, I'll present some code fragments you'll probably won't need in the first place; I included them anyway to show certain techniques. The majority of the code came out of my head, but some snippets presented here I found on the internet (or elsewhere). It's not my intention to pass them along as my own ideas, so, if you see anything that came out of your head, drop me a line and I'll throw in appropriate remarks.

Using the code

Since I wanted to avoid code injection using a DLL, I decided to install a system wide hook that would not only deliver Desktop messages, but events from other applications too. This may seem like an overkill, but getting the Desktop sorted out is easy, once you have the HWND to its corresponding ListView. And here's how to obtain this valuable handle:

// Handle to Desktop ListView, global declaration
HWND  g_hFolderView;
 ////////////////////////////////
// Find Desktop ListView, Part 1
////////////////////////////////

BOOL CALLBACK FindDLV(HWND hWndPM, LPARAM lParam)
{
   HWND hWnd = FindWindowEx(hWndPM, NULL, _T("SHELLDLL_DefView"), NULL);

   if(hWnd)
   {
      // Gotcha!
      HWND *phWnd = (HWND *)lParam;
      *phWnd      = hWnd;

      return false;
   }

   return true;
}

////////////////////////////////
// Find Desktop ListView, Part 2
////////////////////////////////

HWND FindDesktopListView()
{
    HWND hWndPM = FindWindowEx(NULL, NULL, _T("Progman"), NULL);

    if(!hWndPM)
        return NULL;

   HWND hWnd = FindWindowEx(hWndPM, NULL, _T("SHELLDLL_DefView"), NULL);

   if(!hWnd)
   {
       EnumWindows(FindDLV, LPARAM((HWND *)&hWnd));
       
       // Strange, no Desktop ListView found!?
       if(!hWnd)
           return NULL;
   }

   HWND hWndLV = FindWindowEx(hWnd, NULL, _T("SysListView32"), NULL);

   return hWndLV;
}

If all goes well, a call to FindDesktopListView() at the beginning of your application should return the HWND corresponding to the desktop's ListView. It's likely you'll use this handle more than once, so just declare a global variable (like g_hFolderView in my case) representing the HWND.

The next thing to do is to install our hook, which is simple:

HHOOK g_hDesktopHook; // Also declared global
if((g_hDesktopHook = SetWindowsHookEx(WH_MOUSE_LL, OnDTMouseEvent, NULL, 0)) == NULL)
{
    // Sorry, no hook for you...
}

We're using WH_MOUSE_LL, so we don't have to use an additional DLL for the callback function, etc. But there's a disadvantage: a (left) mouse button double click will never be reported at this low level, simply because the system doesn't even know what a double-click is down here. But we'll deal with that in our callback function:

DWORD g_tcLastLeftButtonClickTime = 0; // Global declaration
/////////////////////////////////////
// Callback-Function for desktop hook
/////////////////////////////////////

LRESULT CALLBACK OnDTMouseEvent(int nCode, WPARAM wParam, LPARAM lParam)
{
    // Doesn't concern us
    if(nCode < 0)
        return CallNextHookEx(g_hDesktopHook, nCode, wParam, lParam);

    if(nCode == HC_ACTION)
    {
        // Left button pressed somewhere
        if(wParam == WM_LBUTTONDOWN)
        {
            // Check for left mouse button double click
            if(GetTickCount() < g_tcLastLeftButtonClickTime + GetDoubleClickTime())
            {
                // Event occured on desktop
                if(WindowFromPoint(((MSLLHOOKSTRUCT *)lParam)->pt) == g_hFolderView)
                {
                    // Do something here
                }
            }
            // Save timestamp of this mouse click (maybe another one will occur)
            else
                g_tcLastLeftButtonClickTime = GetTickCount();
        }
    }    

    return CallNextHookEx(g_hDesktopHook, nCode, wParam, lParam);
}

And that's all there is to it! What the callback function does is to check for (mouse) events and filter out left mouse button presses. As I mentioned above, we will never receive WM_LMBUTTONDBLCLK style events at this low level, so we have to roll our own.

As you can see, the function time stamps any left mouse button press and checks if the next click occurs within the system wide double click time, which can be adjusted using the mouse control panel. This is a reliable and convenient way to check for a double click, without using any additional timer functions, etc.

Finally, if the callback function decides a double click has occurred, it uses our g_hFolderView handle to check whether the event occurred on the desktop. (In my application, I also ensure the mouse does not hover above any desktop icon before I further process the event. This is easy when you use the ListView_GetHotItem() macro.)

And since we do things well, do not forget to unhook everything when your application exits; you can add the following to your OnDestroy() handler:

if(g_hDesktopHook)
        UnhookWindowsHookEx(g_hDesktopHook);

This will release the hook and free all used resources, etc.

Final words

I spent quite some time to gather all the information and putting the code together since I couldn't find any article presenting an example like this. So I hope it'll save you some time!

History

There's no article history (yet).

License

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

About the Author

Jörg Anslik
Software Developer (Senior)
Germany Germany
The first thing I did when I was born in 1966 was to start crying. For years, this was my favorite hobby, until I got my first computer in 1981. I then continued crying until I finally mastered 65xx assembler. I learned to smile then, and that's what I've been doing ever since.

Comments and Discussions

 
Questiondoesn't work Pinmembersb13626-Jul-14 16:58 
AnswerRe: doesn't work PinmemberJörg Anslik6-Jul-14 23:56 
GeneralRe: doesn't work Pinmembersb13627-Jul-14 8:30 
GeneralRe: doesn't work PinmemberJörg Anslik8-Jul-14 1:42 
GeneralRe: doesn't work Pinmembersb13628-Jul-14 8:35 
GeneralRe: doesn't work PinmemberJörg Anslik9-Jul-14 3:14 
QuestionDesktop clicks Pinmembersb13624-Jun-14 10:40 
Questionerrata Pinmemberconstm31-Mar-14 12:04 
AnswerRe: errata PinmemberJörg Anslik31-Mar-14 13:16 
Generaluseful PinmemberJinhuang Dai21-Nov-13 3:59 
GeneralMy vote of 5 PinmemberDharmateja Challa10-Dec-12 21:23 
GeneralMy vote of 5 Pinmemberboychester30-Aug-12 23:36 
QuestionNice! Pinmemberboychester30-Aug-12 23:34 
GeneralMy vote of 5 PinmemberBharat Chandak 1005-Jun-12 5:16 
GeneralReally useful API... Superb ! PinmemberBharat Chandak 1005-Jun-12 5:15 
GeneralMy vote of 5 Pinmemberprashu10012-May-11 20:40 
GeneralRe: My vote of 5 PinmemberJörg Anslik15-May-11 12:44 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 1 Apr 2014
Article Copyright 2011 by Jörg Anslik
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid