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

Tray Me!

, 1 Aug 2008
Rate this:
Please Sign up or sign in to vote.
A Beginner's Guide to Windows Hooks
Tray_Me.JPG

Introduction

The article covers the introduction of the Windows Hooks; what they are, and how to use them. "Tray Me" is a simple tool to minimize any application to the system tray, demonstrates you the use of Windows Hooks. So, this can be a start-up for a newbie to Windows hooks. It will also give you a detailed explanation of how to build an application that works from the system tray, i.e. a tray application.

Background

I assume that you have a basic knowledge of working with menus and MFC applications, as I'm not going to explain much about that in this article.

Windows Hooks

Microsoft has provided a mechanism called hooks, by which one can intercept events before they reach an application. By using this mechanism, any application can install a sub-routine or a function (technically called "filters") to monitor/process systems message traffic. The system supports many different types of hooks. Around 15 types of hooks are supported. These different types of hooks give access to different message mechanisms. Sample application demonstrates the installation and the use of the WH_CALLWNDPROC (Windows procedure) and WH_MOUSE (mouse events) hooks.

SetWindowsHookEx() and UnHookWindowsHookEx() install and uninstall the filters, respectively. The system maintains the chain (as a stack) of these installed hooks to not to rely on the filter functions to maintain/store the address of the next filter function in the chain. In the older versions of the windows (before 3.1), the filter functions have to maitain the address of the next filter function. The callback to the installed filters falls through the last installed filter to the first installed filter. The system has a separate chain for each type of message-mechanism. Windows responsibility is just to call the first filter function form the chain, the called filter function (hook procedure) determines whether to pass the event to the next filter in the chain or not. The filter function can pass the event to the next filter by calling CallNextHookEx(). SetWindowsHookEx() returns a handle to the installed hook of type HHOOK, which must be used to identify the same hook later when it calls the UnHookWindowsHookEx() function.

Filters (Filter Functions)

The sub-routine of a function attached to the hook are technically called as Filters or Filter functions. They are also reffered as Callback functions as because they are called only by OS , not by any application. The prototype of the hook procedure (callback function) remains the same for all hook types, i.e.

LRESULT CALLBACK FilterFunction(int nCode, WPARAM wParam, LPARAM lParam)

Only the interpretation of the last two parameters changes according to the hook type. The first parameter nCode specifies whether the filter function should process the message or not. If nCode is less than zero, the filter function should not process the message, rather it should call the CallNextHookEx() function by passing all the three parameter it has received (withoud modifying them).

Hook Types

A hook can be a global hook or a thread-specific hook. A global hook monitors messages for all threads in the current desktop of the calling thread. A thread-specific hook monitors messages only for an individual thread. The global hook procedures code must be kept into separate DLLs, because they get mapped into the process whenever the hook event is hit for that process. This is all I can say about Windows hooks in short. One can find more technical stuff about Windows hooks on Microsoft's website. Let us move on to the usage of the hooks.

I've used two types of Windows global hooks in our sample application to satisfy our needs. The hook functions are put in a separate DLL project (as I'm using global hooks) called Event Hooker DLL and added them into the same solution. By setting a mouse event filter, I'm tracking a right-click event on the Minimize button (non-client area) of the window.

What to Do?

As I said, in this article I'm going to explain how to minimize any application into the system tray. Let me split up the task into a few sub-tasks so that I can explain it to you as best I can (if you think I should put some more explaination about any of the topic/code, please let me know). To minimize the application, what we are going to do is:

  1. Build a tray application that resides in the system tray.
  2. Hook the Windows events to make other applications listen to our commands.
  3. Tray/Untray the selected application into the system tray using Windows hooks.

How to Do?

Let's go step-by-step.

1. Build a tray application

Here we are building a dialog-based application, which has a modeless dialog. So, create a new dialog-based MFC application (I've used Visual Studio 2005) with dialog class name CTrayMeDlg and application class name CTrayMeApp. Add a pointer variable *m_pApplicationDialog of CTrayMeDlg class in the application...

class CTrayMeApp: public CWinApp
{
    /* class code goes here */
    
    private:        
        CTrayMeDlg * m_pApplicationDialog;

};

...and replace the traditional DoModal code with the following in the InitInstance() of the application class, as shown below.

BOOL CTrayMeApp::InitInstance()
{
    /* Function's default code goes here */

    //Create a invisible modless dialog.
    m_pApplicationDialog = new CTrayMeDlg();
    m_pApplicationDialog->Create(IDD_TRAYME_DIALOG,NULL);
    m_pMainWnd = m_pApplicationDialog;
      ShowWindow(m_pMainWnd->GetSafeHwnd(),SW_HIDE);

    return TRUE;

    /*
    CTrayMeDlg dlg;
    m_pMainWnd = &dlg;
    INT_PTR nResponse = dlg.DoModal();

    if (nResponse == IDOK)
    {
        // TODO: Place code here to handle when the dialog is
        //  dismissed with OK
    }
    else if (nResponse == IDCANCEL)
    {
        // TODO: Place code here to handle when the dialog is
        //  dismissed with Cancel
    }

    // Since the dialog has been closed, return FALSE so that we exit the
    //  application, rather than start the application's message pump.
     return FALSE;
  */
}

You might have noticed the SW_HIDE parameter of ShowWindow(). We aren't going to show any dialog as we are building a tray application. All the UI we have for the application is just the context menu of the tray icon. Shell_NotifyIcon() is the API to deal with the application's system tray icon. It sends the message to the system tray to add, modify or delete the icon from the taskbar status area. The following code from OnInitDialog() of CTrayMeDlg makes the system add an application notification icon into the taskbar's status area.

BOOL CTrayMeDlg::OnInitDialog()
{
    /* Function's default code goes here*/
    // TODO: Add extra initialization here
    
    //Add a Tray Notification Icon for the application.

    NOTIFYICONDATA niData;
    niData.cbSize = sizeof(NOTIFYICONDATA);
    niData.hWnd = m_hWnd;
    niData.uID = 1;
    niData.uCallbackMessage = WM_TRAY_ICON_NOTIFY_MESSAGE;
    niData.hIcon = m_hIcon;
    niData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
    wcscpy_s(niData.szTip,127,_T("Tray Me !"));

    Shell_NotifyIcon(NIM_ADD,&niData);

    return TRUE;  
}

The notification message (WM_TRAY_ICON_NOTIFY_MESSAGE) is sent to the application window (specified by m_hWnd) whenever any mouse event occurs in the bonding rectangle of the ICON. Add the message handler OnTrayNotify() to handle the registered message WM_TRAY_ICON_NOTIFY_MESSAGE. Display the context menu using menu APIs like CreatePopuMenu(), AppendMenu() and TrackPopupMenu() on right-click events. When exiting the application, we need to remove the notification icon from the system tray. For that, put the following code into the OnDestroy() event of the dialog box.

void CTrayMeDlg::OnDestroy()
{
    CDialog::OnDestroy();

    // TODO: Add your message handler code here

    //Delete the tray icon while exiting the application.

    NOTIFYICONDATA niData;
    niData.cbSize = sizeof(NOTIFYICONDATA);
    niData.hWnd = m_hWnd;
    niData.uID = 1;

    Shell_NotifyIcon(NIM_DELETE,&niData);
 
    /*Some other code goes here*/

}

Also while exiting the application, we need to destroy the application dialog explicitly. In ExitInstance() of the CTrayMeApp class, call DestroyWindow() of the dialog as shown below.

int CTrayMeApp::ExitInstance()
{
    /*Some other code goes here*/

    //Delete the object to avoid memory leak.

    if(m_pApplicationDialog)
    {
      m_pApplicationDialog->DestroyWindow();
      delete m_pApplicationDialog;
    }

    return CWinApp::ExitInstance();
}

2. Use Windows hooks

So, coming back to Windows Hooks, all the hook related functionalities are wrapped up into the "Event Hooker Dll" DLL project. All the function are put into the "Global Functions.cpp" file of the project, and only required functions are exported through Def File. SetWindowsHookEx() will add the hook procedure into the system chain. The function is then ready to receive the call back from the system. SetWindowsHookEx() returns the handle to the hook procedure. This returned handle can be use to pass the event to the next filter in the chain. As I've already specified, the filter function can pass the event to the next filter in chain by calling CallNextHookEx(). The following code snippet gives an idea of setting a mouse event filter.

bool InstallMouseHook()
{
    bool bReturn = false;

    try
    {
        OutputDebugString(_T("Mouse Event hooked.\n"));
        g_hPreviousMouseHook = 
            SetWindowsHookEx(WH_MOUSE,&MouseHookProcedure,g_hInstance,0);

        if(NULL == g_hPreviousMouseHook)
        {
            TCHAR szError[ARRAY_SIZE] = {0};
            wsprintf(szError,_T("Last Error: %d"),GetLastError());
            OutputDebugString(_T("Failed to hook Mouse Event.\n"));
            OutputDebugString(szError);
            bReturn = false;
        }
        else
        {
            OutputDebugString(_T("Mouse Event hooked.\n"));
            bReturn = true;
        }
    }
     catch(...)
    {
        bReturn = false;
    }

    return bReturn;
}

In the filter, I'm showing a context menu on the right-click event of the Minimize button. The popped-up context menu will provide an option to minimize the application to the tray. Whenever user right clicks on Minimize Button of any application, the window gets Mouser Message WM_NCRBUTTONUP with HitTest Code as HTMINBUTTON. We'll be dealing with this message in our Mouse Hook Procedure.

LRESULT CALLBACK MouseHookProcedure(int nCode, WPARAM wParam,LPARAM lParam)
{
    if(0 > nCode)
        return CallNextHookEx(g_hPreviousMouseHook, nCode, wParam, lParam);

    //OutputDebugString(_T("Mouse Hooked Event...\n"));

    MOUSEHOOKSTRUCT *pMouseHooksStruct = (MOUSEHOOKSTRUCT *) lParam;
    switch(wParam)
    {
        case WM_NCRBUTTONUP:
        case VK_LMENU:
            {
                if(HTMINBUTTON == pMouseHooksStruct->wHitTestCode)
                {
                    OutputDebugString(_T(
                        "\nMouseHookProcedure:\tMouse Clicked on Min Button...\n"));
                    HMENU  hPopupMenu = ::CreatePopupMenu();
                    ::AppendMenu(hPopupMenu, MF_STRING | MF_ENABLED, TRAY_ME_MENU_ID,
                        _T("&Try Me !")); 

                    SetForegroundWindow(pMouseHooksStruct->hwnd);
                    int iSelectedMenuId = ::TrackPopupMenu(hPopupMenu, 
                                TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON |
                                 TPM_RETURNCMD ,
                                 pMouseHooksStruct->pt.x, pMouseHooksStruct->pt.y, 0, 
                                 pMouseHooksStruct->hwnd, NULL);

                    switch(iSelectedMenuId)
                    {
                        case TRAY_ME_MENU_ID:
                            TrayMe(pMouseHooksStruct->hwnd);
                            break;
                    }

                    DestroyMenu(hPopupMenu);
                    return -1;
                }
            }
            break;
    }

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

The TrayMe() function is going to perform the core functionality required by the application, i.e. tray the application. It just hides the selected application window (as we did for our application window) and adds the window handle to the globally maintained window handler list. The selected application's file icon is retrieved using SHGetFileInfo(), which returns information about the specified file in the SHFILEINFO structure. If the icon in the returned structure is NULL, I'm loading the default icon (IDI_QUESTION) with LoadIcon(). Using this icon, a notification icon will be added for that application window into the system tray.

bool TrayMe(HWND hWnd)
{
    bool bReturn = false;
    try
    {
        if(g_iMinimizedWindowCount >= ARRAY_SIZE)
        {
            MessageBox(NULL,
                _T("Can not Tray application. \n\t Reached the Tray limit."),
                _T("Tray Me !"),0);
            return bReturn;
        }

        TCHAR szModuleFileName[ARRAY_SIZE] = {0};
        HMODULE hModule = 
            (HMODULE) OpenProcess(0,FALSE,GetWindowThreadProcessId(hWnd,0));
        GetModuleFileName(hModule,szModuleFileName,ARRAY_SIZE);
        OutputDebugString(szModuleFileName);

        TCHAR szCaption[128] = {0};

        //HICON hIcon = (HICON) SendMessage(pMouseHooksStruct->hwnd,
        //    WM_GETICON,ICON_SMALL,0);

        SHFILEINFO shFileInfo;
        shFileInfo.hIcon = NULL;
        SHGetFileInfo(szModuleFileName,
            FILE_ATTRIBUTE_NORMAL,&shFileInfo,sizeof(SHFILEINFO),
            SHGFI_ICON | SHGFI_USEFILEATTRIBUTES | SHGFI_SMALLICON);

        if(!shFileInfo.hIcon)
            OutputDebugString(_T("NULL Icon handle."));

        NOTIFYICONDATA objNotifyIcon;
        objNotifyIcon.cbSize = sizeof(NOTIFYICONDATA);
        objNotifyIcon.hWnd = hWnd;
        objNotifyIcon.uID = 1;
        objNotifyIcon.uCallbackMessage = WM_NOTIFY_CALLBACK_MESSAGE;
        objNotifyIcon.hIcon = 
            (shFileInfo.hIcon == NULL)? LoadIcon(NULL,
            IDI_QUESTION): shFileInfo.hIcon;
        objNotifyIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;

        GetWindowText(hWnd,szCaption,127);
        wcscpy_s(objNotifyIcon.szTip,127,szCaption);

        Shell_NotifyIcon(NIM_ADD,&objNotifyIcon);
        ShowWindow(hWnd,SW_HIDE);
        g_hMinimizedWindowList[g_iMinimizedWindowCount++] = hWnd;
        bReturn = true;
    }
    catch(...)
    {
        OutputDebugString(_T("Caught Exception in TrayMe !"));
        bReturn = false;
    }

    return bReturn;
}

The global hook for the window procedure is set by the following code, the same procedure as mouse event hook setting. The callback message WM_NOTIFY_CALLBACK_MESSAGE set to the tray icon is handled by intercepting (setting a filter for) a Windows procedure call where I'm untraying the window.

bool InstallWinProcHook(void)
{
    bool bReturn = false;
    try
    {
        OutputDebugString(_T("WinProc Event hooked.\n"));
        if(NULL == g_hInstance)
            OutputDebugString(_T("NULL g_hInstance\n"));

        g_hPreviousWinProcHook = 
            SetWindowsHookEx(WH_CALLWNDPROC,
            &WinProcHookProcedure,g_hInstance,0);

        if(NULL == g_hPreviousWinProcHook)
        {
            TCHAR szErrorMsg[ARRAY_SIZE] = {0};
            wsprintf(szErrorMsg,
                _T("Failed to hook WinProc Event.\nLast Error:%d\n"),
                GetLastError());
            OutputDebugString(szErrorMsg);
            bReturn = false;
        }
        else
        {
            OutputDebugString(_T("WinProc Event hooked.\n"));
            bReturn = true;
        }
    }
    catch(...)
    {
        bReturn = false;
    }

    return bReturn;
}

LRESULT CALLBACK WinProcHookProcedure(int nCode, 
    WPARAM wParam, LPARAM lParam)
{
    /* Function code goes here */

}

In WinProcHookProcedure specifically, I'm handling the double click event of the notify icon to restore the application window.

LRESULT CALLBACK WinProcHookProcedure(int nCode, 
    WPARAM wParam, LPARAM lParam)
{
    if(0 > nCode)
        return CallNextHookEx(g_hPreviousWinProcHook ,nCode,wParam,lParam);

    CWPSTRUCT *pwsStruct = (CWPSTRUCT *) lParam;
    
    if(WM_NOTIFY_CALLBACK_MESSAGE == pwsStruct->message)
    {
        if(WM_LBUTTONDBLCLK == pwsStruct->lParam)
        {
            OutputDebugString(_T(
                "WM_NOTIFY_CALLBACK_MESSAGE WM_LBUTTONDBLCLK Fired.\n"));
            UnTrayApplication(pwsStruct->hwnd);
        }
        return -1;
    }
    
    /*Some other code goes here*/

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

bool UnTrayApplication(HWND hWnd)
{
    bool bReturn = false;
    try
    {
        ::ShowWindow(hWnd,SW_SHOW);
        ::SetForegroundWindow(hWnd);

        NOTIFYICONDATA objNotifyIcon;
        objNotifyIcon.cbSize = sizeof(NOTIFYICONDATA);
        objNotifyIcon.hWnd = hWnd;
        objNotifyIcon.uID = 1;
        Shell_NotifyIcon(NIM_DELETE,&objNotifyIcon);
        
        for(int i = 0; i < g_iMinimizedWindowCount; i++)
            if(hWnd == g_hMinimizedWindowList[i])
            {
                g_hMinimizedWindowList[i] = 
                    g_hMinimizedWindowList[--g_iMinimizedWindowCount];
                g_hMinimizedWindowList[g_iMinimizedWindowCount] = 0;
                break;
            }

        bReturn = true;
    }
    catch(...)
    {
        OutputDebugString(_T("Caught Exception in UnTrayMe !"));
        bReturn = false;
    }

    return bReturn;
}

UnTrayApplication() will restore the application to its original state, delete the window's notify icon and update the minimized windows list by removing the window handle from the list. The minimized windows list is maintained just to restore all the minimized windows before exiting our application. This was all about achieving our goal using Windows hooks. The DLL has got few more exported functions, used as and where required, and they are self-explanatory.

When to Do?

The only thing which I've yet to explain is when to call and which functions to call. So, as I wrapped up all the hook functionalities in the separate DLL, I'm dynamically loading the DLL as and when required to call any function from the DLL. I'm setting all the required Windows hooks when my application starts, i.e. CTrayMeApp's InitInstance(). So, the full InitInstance() function will be:

BOOL CTrayMeApp::InitInstance()
 {
    /* Function's default code goes here*/
    //Load the Hooker dll


    HMODULE hModule = LoadLibrary(_T("Event Hooker Dll.dll"));

    try
    {
        if(NULL == hModule)
        {
            MessageBox(NULL,_T("Failed to Load Hooker Dll."),_T("Hooking Dlg"),
                MB_TOPMOST | MB_ICONERROR);
            throw FALSE;
        }

        //Get the function address which installs the windows procedure filter.
        InstallHook fpInstallHook = (InstallHook) GetProcAddress(hModule,
           "InstallWinProcHook");
        if(NULL == fpInstallHook)
        {
            MessageBox(NULL,_T("Failed to get InstallWinProcHook function address."),
                _T("Tray Me !"),0);
            throw FALSE;
        }

        //Install the Windows Procedure hook (Filter).

        if(!fpInstallHook())
        {
            MessageBox(NULL,_T("Failed to install hook."),_T("Tray Me !"),MB_TOPMOST |
                MB_ICONERROR);
            throw FALSE;
        }

        //Get the function address which installs the mouse events filter.
        InstallHook fpMouseHook = (InstallHook) GetProcAddress(hModule,
            "InstallMouseHook");

        if(NULL == fpMouseHook)
        {
            MessageBox(NULL,_T("Failed to get InstallMouseHook function address."),
                _T("Tray Me !"),MB_TOPMOST | MB_ICONERROR);
            throw FALSE;
        }

        //Install the Mouse Event Hook (Filter).
        if(!fpMouseHook())
        {
            MessageBox(NULL,_T("Failed to install Mouse Hook."),_T("Tray Me !"),
                MB_TOPMOST | MB_ICONERROR);
            throw FALSE;
        }

        GetTrayMessageID fpGetTrayMessageID = (GetTrayMessageID) GetProcAddress(
            hModule,"GetTrayMessageID");

        if(NULL == fpGetTrayMessageID)
        {
            MessageBox(NULL,_T("Failed to get GetTrayMessageID function address."),
                _T("Tray Me !"),MB_TOPMOST | MB_ICONERROR);
            throw FALSE;
        }

        //Install the Mouse Event Hook (Filter).
        if(0 == (g_uiTrayMessageID = fpGetTrayMessageID()))
        {
            MessageBox(NULL,_T("Failed to retrieve TrayMessage Id from dll."),_T(
                "Tray Me !"),MB_TOPMOST | MB_ICONERROR);
            throw FALSE;
        }

        //Create a invisible modless dialog.

        m_pApplicationDialog = new CTrayMeDlg();
        m_pApplicationDialog->Create(IDD_TRAYME_DIALOG,NULL);
        m_pMainWnd = m_pApplicationDialog;
        ShowWindow(m_pMainWnd->GetSafeHwnd(),SW_HIDE);

        bReturn = TRUE;
    }
    catch(BOOL bThrownVal)
    {
        bReturn = bThrownVal;
        OutputDebugString(_T("\nCTrayMeApp::InitInstance:\tCustom Exception Caught."));
        DebugPrintErrorMessage();
    }
    catch(...)
    {
        bReturn = FALSE;
        DebugPrintErrorMessage(_T("\nCTrayMeApp::InitInstance:\tException Caught."));
    }

    if(hModule)
        FreeLibrary(hModule);

    return bReturn;

    /* Function's commented code goes here*/
}

While exiting the application, it is untraying all of the minimized windows (if any).

int CTrayMeApp::ExitInstance()
 {
    HMODULE hModule = NULL;
    try
    {
        // TODO: Add your specialized code here and/or call the base class

        //while exiting untray all the trayed application.
        hModule = LoadLibrary(_T("Event Hooker Dll.dll"));
        
        if(NULL == hModule)
        {
            MessageBox(NULL,_T("Failed to Load Hooker Dll."),_T("Hooking Dlg"),
                MB_TOPMOST | MB_ICONERROR);
            throw _T("Failed to Load Hooker Dll.");
        }
        
        //Get the function address which untrays all the minimzed applications.
        UnTrayAll fpUnTrayAll = (UnTrayAll) GetProcAddress(hModule,"UnTrayAll");
        
        if(NULL == fpUnTrayAll)
        {
            MessageBox(NULL,_T("Failed to get function pointer from Hooker Dll."),
                _T("Hooking Dlg"),MB_TOPMOST | MB_ICONERROR);
            throw _T("Failed to get function pointer from Hooker Dll.");
        }

        //Untray the applications

        if(NULL != fpUnTrayAll)
            fpUnTrayAll();

        //Get the function address which uninstalls the windows procedure filter.
        UnInstallHook fpUnInstallHook = (UnInstallHook) GetProcAddress(hModule,
            "UnInstallWinProcHook");
        
        if(NULL == fpUnInstallHook)
            MessageBox(NULL,_T("Failed to get UnInstallWinProcHook function address."),
                _T("Tray Me !"),0);
        else if    (!fpUnInstallHook()) //Uninstall the Windows Procedure hook (Filter).
            MessageBox(NULL,_T("Failed to Uninstall hook."),_T("Tray Me !"),
                MB_TOPMOST | MB_ICONERROR);

        //Get the function address which uninstalls the mouse events filter.
        UnInstallHook fpMouseHook = (UnInstallHook) GetProcAddress(hModule,
            "UnInstallMouseHook");

        if(NULL == fpMouseHook)
            MessageBox(NULL,_T("Failed to get UnInstallMouseHook function address."),
                _T("Tray Me !"),MB_TOPMOST | MB_ICONERROR);
        else if(!fpMouseHook()) //unInstall the Mouse Event Hook (Filter).
            MessageBox(NULL,_T("Failed to Uninstall Mouse Hook."),_T("Tray Me !"),
                MB_TOPMOST | MB_ICONERROR);

    }
    catch(TCHAR *pszError)
    {
        OutputDebugString(_T("\nCTrayMeApp::ExitInstance:\tCustom Exception Cought."));
        DebugPrintErrorMessage(pszError);
    }
    catch(...)
    {
        DebugPrintErrorMessage(_T("\nCTrayMeApp::ExitInstance:\tException Cought."));
    }

    if(hModule)
        FreeLibrary(hModule);

    //Delete the object to avoid memory leak.
    if(m_pApplicationDialog)
    {
        m_pApplicationDialog->DestroyWindow();
        delete m_pApplicationDialog;
    }

    return CWinApp::ExitInstance();
}

On the context menu of the application's notification icon, I'm adding minimized applications' names as menu items so that the user can be more specific while untraying any application. UnTray All will untray all the minimized applications.

LRESULT CTrayMeDlg::OnTrayNotify(WPARAM wParam,LPARAM lParam)
{
    UINT uMsg = (UINT) lParam;
    //Handle the tray notification messages here.

    HMODULE hModule = NULL;

    try
    {
        //Entertain only Right click event.
        if(WM_RBUTTONDOWN == uMsg || WM_CONTEXTMENU == uMsg)
        {
            //Build a context menu with minimised application captios as menu items
            //and pop it up.

            CPoint pt;
            GetCursorPos(&pt);

            hModule = LoadLibrary(_T("Event Hooker Dll.dll"));

            if(NULL == hModule)
            {
                MessageBox(_T("Failed to Load Hooker Dll."), _T("Hooking Dlg"), 0);
                throw _T("Failed to Load Hooker Dll.");
            }

            GetMinimizedWinodwList fpGetMinimizedWinodwList = 
                (GetMinimizedWinodwList) GetProcAddress(hModule,
                "GetMinimizedWinodwList");

            if(NULL == fpGetMinimizedWinodwList)
            {
                MessageBox(_T(
               "Failed to Get GetMinimizedWinodwList Function pointer from Hooker Dll."),
                _T("Hooking Dlg"), 0);
                throw _T(
                "Failed to Get GetMinimizedWinodwList Function pointer from Hooker Dll.");
            }

            //Retrive the list of caption of the minimized windows
            TCHAR szWindowCaptions[ARRAY_SIZE][100] = {0};
            fpGetMinimizedWinodwList(szWindowCaptions);

            HMENU hContextMenu = CreatePopupMenu();
            HMENU hSubMenu = CreatePopupMenu();

            int iMinimizedAppCount = 0;

            //Add the list items into the context menu.
            for(iMinimizedAppCount = 0;iMinimizedAppCount < 1024; iMinimizedAppCount++,
                OutputDebugString(_T("\n")))
            {
                if(wcslen(szWindowCaptions[iMinimizedAppCount]))
                {
                    ::AppendMenu(hSubMenu, MF_STRING, MENUID_APPNAMES +
                        iMinimizedAppCount , szWindowCaptions[iMinimizedAppCount]);
                    OutputDebugString(szWindowCaptions[iMinimizedAppCount]);
                }
                else
                    break;
            }

            long lMenuProperties = MF_STRING ;

            if(0 == iMinimizedAppCount)
                lMenuProperties |= MF_GRAYED;

            ::AppendMenu(hContextMenu, lMenuProperties | MF_POPUP, (UINT_PTR) hSubMenu ,
                _T("UnTray"));
            ::AppendMenu(hContextMenu, lMenuProperties, MENUID_UNTRAY_ALL,
                _T("UnTray All"));
            ::AppendMenu(hContextMenu, MF_SEPARATOR, MENUID_SEPARATOR,
                _T("Tray Me Separator"));
            ::AppendMenu(hContextMenu, MF_STRING, MENUID_ABOUT_DLG,
                _T("About Tray Me !"));
            ::AppendMenu(hContextMenu, MF_SEPARATOR, MENUID_SEPARATOR,
                _T("Tray Me Separator"));
            ::AppendMenu(hContextMenu, MF_STRING, MENUID_EXIT, _T("Exit"));

            SetForegroundWindow();
            //Popup the context menu.

            BOOL iSelectedIndex = TrackPopupMenu(hContextMenu, TPM_LEFTALIGN |
                TPM_HORPOSANIMATION | TPM_RETURNCMD | TPM_LEFTBUTTON, pt.x, pt.y, 0,
                m_hWnd, NULL);
            //PostMessage(WM_NULL, NULL, NULL);

            //Perform the user selected task.
            switch(iSelectedIndex)
            {
            case MENUID_EXIT:
                //Exit the application.
                PostQuitMessage(0);
                break;
            case MENUID_UNTRAY_ALL:
                {
                    //Untray all the applications.
                    UnTrayAll fpUnTrayAll = (UnTrayAll) GetProcAddress(hModule,
                        "UnTrayAll");

                    if(NULL == fpUnTrayAll)
                    {
                        MessageBox(_T(
                        "Failed to Get UnTrayAll Function pointer from Hooker Dll."),
                        _T("Hooking Dlg"), 0);
                        throw _T(
                            "Failed to Get UnTrayAll Function pointer from Hooker Dll.");
                    }

                    fpUnTrayAll();
                }
                break;
            case MENUID_ABOUT_DLG:
                {
                    //Show about dlg.

                    CAboutDlg dlgAbout;
                    dlgAbout.DoModal();
                }
                break;
            default :
                if(iSelectedIndex >= MENUID_APPNAMES)
                {
                    //Untray the selected application only.
                    UnTrayMe fpUnTrayMe = (UnTrayMe) GetProcAddress(hModule, "UnTrayMe");

                    if(NULL == fpUnTrayMe)
                        MessageBox(_T(
                        "Failed to Get UnTrayMe Function pointer from Hooker Dll."),
                            _T("Hooking Dlg"), 0);

                    OutputDebugString(szWindowCaptions[iSelectedIndex - MENUID_APPNAMES]);
                    fpUnTrayMe(szWindowCaptions[iSelectedIndex - MENUID_APPNAMES]);
                }
                break;
            }
        }
    }
    catch(TCHAR *pszError)
    {
        OutputDebugString(_T("\nCTrayMeDlg::OnTrayNotify:\tCustom Exception Caught."));
        DebugPrintErrorMessage(pszError);
    }
    catch(...)
    {
        DebugPrintErrorMessage(_T("\nCTrayMeDlg::OnTrayNotify:\tException Caught."));
    }

    if(hModule)
        FreeLibrary(hModule);

    return LRESULT();
}

New Feature

I was just modifying the code to work in Vista, so thought of adding a new feature to it. The featue I've added is short-cut keys to minimize the application to tray. For that I've added three more function RegisterApplicationHotKeys(), UnRegisterApplicationHotKeys() and OnHotKey() to CTrayMeDlg class. The function themselves describe their purpose. The other slight change in the DLL code is that the DLL maintains a registered message. The overall logic is: Whenever the user presses the short-cut key (Ctrl + Shift + Down Arrow), the sample application sends the WM_NOTIFY_TRAY_MESSAGE message to the forground window. This WM_NOTIFY_TRAY_MESSAGE message is handled into the window's procedure hook. So, whenever any window receives this message, it minimizez itself to tray.

This is all the what-how-n-when to do with the application that minimizes any application to the system tray. Now you're ready to minimize any application to the system tray. You can download the sample code, embed into your application and get going...

History

  • 7 November, 2007 -- Original version posted
  • 23 May, 2008 -- Updated.
  • 1 August, 2008 -- Modified article to
    • Make sample application work on Vista.
    • Added new feature of short-cut key to sample application.

License

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

About the Author

Malli_S
Software Developer
India India
Hello All !
This is Mallinath S. Karkanti, from India. I'm working as a Software Developer in one of the Middle Scale Company... !

Comments and Discussions

 
GeneralSource code download link invalid PinmemberDavid L.S.26-Oct-09 15:59 
GeneralRe: Source code download link invalid PinmemberMalli_S5-Nov-09 19:02 

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
Web01 | 2.8.140709.1 | Last Updated 1 Aug 2008
Article Copyright 2007 by Malli_S
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid