The easiest yet user-friendly WTL way to Minimize Application to System Tray Icon

A good old task to easily minimize to system tray icon to not clog the application bar and without too much thinking about it. How To with WTL?

The key points are:

  • manage NOTIFYICONDATA structure (obviously!), where non-zero .cbSize will be an indication of created icon
  • create an icon in WM_SYSCOMMAND/SC_MINIMIZE handler and hide instead default minimization
  • handle icon’s WM_LBUTTONDBLCLK to restore and WM_RBUTTONUP to pop up a menu
  • use default dialog menu to avoid having private one, handle SC_RESTORE and SC_CLOSE system commands to restore and close from system tray icon popup menu

Relevant source code from application main window (dialog) class:

class CMainDialog :
...
{
public:

BEGIN_MSG_MAP_EX(CMainDialog)
...
    MSG_WM_INITDIALOG(OnInitDialog)
    MSG_WM_DESTROY(OnDestroy)
    MSG_WM_SYSCOMMAND(OnSysCommand)
    MESSAGE_HANDLER_EX(WM_SYSTEMTRAYICON, OnSystemTrayIcon)
    COMMAND_ID_HANDLER_EX(SC_RESTORE, OnScRestore)
    COMMAND_ID_HANDLER_EX(SC_CLOSE, OnScClose)
...
END_MSG_MAP()
...
    enum
    {
        WM_FIRST = WM_APP,
        WM_SYSTEMTRAYICON,
    };
...
private:
    NOTIFYICONDATA m_NotifyIconData;
...
public:
// CMainDialog
    CMainDialog() throw()
    {
        ZeroMemory(&m_NotifyIconData, sizeof m_NotifyIconData);
        ...
    }

// Window message handlers
    LRESULT OnInitDialog(HWND, LPARAM)
    {
        ZeroMemory(&m_NotifyIconData, sizeof m_NotifyIconData);
        ...
    }
    LRESULT OnDestroy()
    {
        if(m_NotifyIconData.cbSize)
        {
            Shell_NotifyIcon(NIM_DELETE, &m_NotifyIconData);
            ZeroMemory(&m_NotifyIconData, sizeof m_NotifyIconData);
        }
        ...
    }
    LRESULT OnSysCommand(UINT nCommand, CPoint)
    {
        switch(nCommand)
        {
        case SC_MINIMIZE:
            if(!m_NotifyIconData.cbSize)
            {
                m_NotifyIconData.cbSize = NOTIFYICONDATAA_V1_SIZE;
                m_NotifyIconData.hWnd = m_hWnd;
                m_NotifyIconData.uID = 1;
                m_NotifyIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
                m_NotifyIconData.uCallbackMessage = WM_SYSTEMTRAYICON;
                m_NotifyIconData.hIcon = AtlLoadIconImage(IDI_MODULE, LR_DEFAULTCOLOR, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
                CString sWindowText;
                GetWindowText(sWindowText);
                _tcscpy_s(m_NotifyIconData.szTip, sWindowText);
                if(!Shell_NotifyIcon(NIM_ADD, &m_NotifyIconData))
                {
                    SetMsgHandled(FALSE);
                    return 0;
                }
            }
            ShowWindow(SW_HIDE);
            break;
        ...
default:
            SetMsgHandled(FALSE);
        }
        return 0;
    }
    LRESULT OnSystemTrayIcon(UINT, WPARAM wParam, LPARAM lParam)
    {
        ATLASSERT(wParam == 1);
        switch(lParam)
        {
        case WM_LBUTTONDBLCLK:
            SendMessage(WM_COMMAND, SC_RESTORE);
            break;
        case WM_RBUTTONUP:
            {
                SetForegroundWindow(m_hWnd);
                CMenuHandle Menu = GetSystemMenu(FALSE);
                Menu.EnableMenuItem(SC_RESTORE, MF_BYCOMMAND | MF_ENABLED);
                Menu.EnableMenuItem(SC_MOVE, MF_BYCOMMAND | MF_GRAYED);
                Menu.EnableMenuItem(SC_SIZE, MF_BYCOMMAND | MF_GRAYED);
                Menu.EnableMenuItem(SC_MINIMIZE, MF_BYCOMMAND | MF_GRAYED);
                Menu.EnableMenuItem(SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED);
                Menu.EnableMenuItem(SC_CLOSE, MF_BYCOMMAND | MF_ENABLED);
                CPoint Position;
                ATLVERIFY(GetCursorPos(&Position));
                Menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_BOTTOMALIGN, Position.x, Position.y, m_hWnd);
            }
            break;
        }
        return 0;
    }
    LRESULT OnScRestore(UINT, INT, HWND)
    {
        if(m_NotifyIconData.cbSize)
        {
            Shell_NotifyIcon(NIM_DELETE, &m_NotifyIconData);
            ZeroMemory(&m_NotifyIconData, sizeof m_NotifyIconData);
        }
        ShowWindow(SW_SHOW);
        BringWindowToTop();
        return 0;
    }
    LRESULT OnScClose(UINT, INT, HWND)
    {
        PostMessage(WM_COMMAND, IDCANCEL);
        return 0;
    }

Leave a Reply