Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Your Desktop and Microsoft's SetWindowsHookEx()

4.80/5 (23 votes)
31 Mar 2014CPOL3 min read 71.1K  
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:

C++
// 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:

C++
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:

C++
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 occurred 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 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:

C++
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

  • 28th April, 2011: Initial version

License

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