Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Shell Extensibility - Explorer Desk Band, Tray Notification Icon et al.

, 28 Aug 2009 CPOL
A simple Calendar utility that demonstrates basic Shell extensibility techniques: desk band, tray notification icon, locales.
Calendar.zip
Calendar
CalendarDeskBand
CalendarDeskBand.def
CalendarDeskBand.rgs
CalendarDeskBandApp.rgs
CalendarSysTray
Res
01.ico
02.ico
03.ico
04.ico
05.ico
06.ico
07.ico
08.ico
09.ico
10.ico
11.ico
12.ico
13.ico
14.ico
15.ico
16.ico
17.ico
18.ico
19.ico
20.ico
21.ico
22.ico
23.ico
24.ico
25.ico
26.ico
27.ico
28.ico
29.ico
30.ico
31.ico
Blank-16x16-16.ico
Calendar.ico
Common
Win32
Release
Calendar.exe
x64
Release
Calendar.exe
#include "StdAfx.h"
#include "CalendarWindow.h"
#include "CalendarGuids.h"
#include "Utils.h"

/////////////////////////////////////////////////////////////////////////////
//
const LPCTSTR DESKBAND_BIN_NAME = TEXT("CalendarDeskBand.dll");

////////////////////////////////////////////////////////////////////////////////
// 
CString StringFromGUID2(REFGUID rguid)
{
    OLECHAR szGuid[MAX_GUID_STRING_LEN] = { 0 };
    ::StringFromGUID2(rguid, szGuid, MAX_GUID_STRING_LEN);

    return szGuid;
}

DWORD RunAsAdmin(HWND hWnd, LPCTSTR lpFile, LPCTSTR lpParameters)
{
    SHELLEXECUTEINFO sei = { 0 };

    const ULONG fMask =
        SEE_MASK_FLAG_DDEWAIT |
        SEE_MASK_FLAG_NO_UI |
        SEE_MASK_NOCLOSEPROCESS;

    sei.cbSize          = sizeof(SHELLEXECUTEINFOW);
    sei.hwnd            = hWnd;
    sei.fMask           = fMask;
    sei.lpVerb          = TEXT("runas");
    sei.lpFile          = lpFile;
    sei.lpParameters    = lpParameters;
    sei.nShow           = SW_SHOWNORMAL;

    if(::ShellExecuteEx(&sei))
    {
        CHandle handle(sei.hProcess);
        const DWORD dwWaitRes = ::WaitForSingleObject(handle, INFINITE);

        if(dwWaitRes == WAIT_OBJECT_0)
        {
            DWORD dwExitCode = ERROR_SUCCESS;
            const BOOL bRes = ::GetExitCodeProcess(handle, &dwExitCode);
            ATLASSERT(bRes);

            return dwExitCode;
        }
    }

    return ::GetLastError();
}

//////////////////////////////////////////////////////////////////////////////
// static
UINT CCalendarWindow::s_uTaskbarRestart =
    ::RegisterWindowMessage(_T("TaskbarCreated"));

//////////////////////////////////////////////////////////////////////////////
//
CCalendarWindow::CCalendarWindow() :
    m_ctrlMonthCal(MONTHCAL_CLASS, this, MONTHCAL_MSG_MAP_ID),
    m_hSysTrayIcon(NULL),
    m_hWndIconSmall(NULL),
    m_hWndIcon(NULL)
{
}

LRESULT CCalendarWindow::OnCreate(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM lParam,
    BOOL& /*bHandled*/)
{
    LPCREATESTRUCT pcs = reinterpret_cast<LPCREATESTRUCT>(lParam);

    if(!InitIcons())
        return -1;

    if(!CreateChildren(pcs))
        return -1;

    if(!InitMenu())
        return -1;

    if(!InitSettingsPath())
        return -1;

    m_dlgOptions.LoadSettings(m_pathSettings);

    if(!ApplySettings(/*Resize=*/true))
        return -1;

    if(!UpdateSysTrayIcon(NIM_ADD))
        return -1;

    if(SetUpdateTimer() == 0)
        return -1;

    return 0;
}

LRESULT CCalendarWindow::OnSize(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM lParam,
    BOOL& /*bHandled*/)
{
    m_ctrlMonthCal.MoveWindow(0, 0, LOWORD(lParam), HIWORD(lParam), FALSE);

    return 0;
}

LRESULT CCalendarWindow::OnSysCommand(
    UINT /*uMsg*/,
    WPARAM wParam,
    LPARAM /*lParam*/,
    BOOL& bHandled)
{
    switch(wParam)
    {
    case IDC_OPTIONS:
        ShowOptions();
        break;
    case IDC_ABOUT:
        ShowAboutBox();
        break;
    default:
        bHandled = FALSE;
    }

    return 0;
}

LRESULT CCalendarWindow::OnDestroy(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM /*lParam*/,
    BOOL& /*bHandled*/)
{
    KillTimer(UPDATE_TIMER_ID);

    if(m_hSysTrayIcon)
    {
        ATLVERIFY(UpdateSysTrayIcon(NIM_DELETE));
        ::DestroyIcon(m_hSysTrayIcon);
    }

    ATLASSERT(m_hWndIconSmall != NULL);
    ::DestroyIcon(m_hWndIconSmall);

    ATLASSERT(m_hWndIcon != NULL);
    ::DestroyIcon(m_hWndIcon);

    ::PostQuitMessage(0);

    return 0;
}

LRESULT CCalendarWindow::OnSysTrayNotify(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM lParam,
    BOOL& /*bHandled*/)
{
    switch(lParam)
    {
    case WM_LBUTTONUP:
        ShowAndActivate();
        break;
    case WM_RBUTTONUP:
        HandleContextCommand(ShowContextMenu());
        break;
    }

    return 0;
}

LRESULT CCalendarWindow::OnClose(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM /*lParam*/,
    BOOL& /*bHandled*/)
{
    ShowWindow(SW_HIDE);

    return 0;
}

LRESULT CCalendarWindow::OnTimeChange(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM /*lParam*/,
    BOOL& /*bHandled*/)
{
    SYSTEMTIME stNow = { 0 };
    ::GetLocalTime(&stNow);

    UpdateTodayDate(stNow);

    return 0;
}

LRESULT CCalendarWindow::OnTimer(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM /*lParam*/,
    BOOL& /*bHandled*/)
{
    SYSTEMTIME stNow = { 0 };
    ::GetLocalTime(&stNow);

    SYSTEMTIME stCtrlToday = { 0 };
    MonthCal_GetToday(m_ctrlMonthCal, &stCtrlToday);

    if(stNow.wDay != stCtrlToday.wDay)
        UpdateTodayDate(stNow);

    return 0;
}

LRESULT CCalendarWindow::OnEndSession(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM /*lParam*/,
    BOOL& /*bHandled*/)
{
    DestroyWindow();

    return TRUE; // system can continue to end session
}

LRESULT CCalendarWindow::OnPowerBroadcast(
    UINT /*uMsg*/,
    WPARAM wParam,
    LPARAM /*lParam*/,
    BOOL& /*bHandled*/)
{
    if(wParam == PBT_APMRESUMECRITICAL ||
       wParam == PBT_APMRESUMESUSPEND ||
       wParam == PBT_APMRESUMESTANDBY)
    {
        // re-enable timer
        ATLVERIFY(SetUpdateTimer() != 0);

        // refresh tray icon (sometimes it is not refreshed after resume)
        ATLVERIFY(UpdateSysTrayIcon(NIM_MODIFY));
    }

    return 0;
}

LRESULT CCalendarWindow::OnSetFocus(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM /*lParam*/,
    BOOL& /*bHandled*/)
{
    m_ctrlMonthCal.SetFocus();

    return 0;
}

LRESULT CCalendarWindow::OnTaskbarRestart(
    UINT /*uMsg*/,
    WPARAM /*wParam*/,
    LPARAM /*lParam*/,
    BOOL& /*bHandled*/)
{
    return (UpdateSysTrayIcon(NIM_ADD) ? 0L : -1L);
}

bool CCalendarWindow::InitMenu(void)
{
    HMENU hSysMenu = GetSystemMenu(FALSE);
    ATLVERIFY(::AppendMenu(hSysMenu, MF_SEPARATOR, 0, NULL));

    CString strMenuText;
    ATLVERIFY(strMenuText.LoadString(IDS_OPTIONS));

    BOOL bAppend = ::AppendMenu(hSysMenu, MF_STRING,
        IDC_OPTIONS, strMenuText);
    ATLASSERT(bAppend);

    ATLVERIFY(strMenuText.LoadString(IDS_ABOUT));

    bAppend = ::AppendMenu(hSysMenu, MF_STRING,
        IDC_ABOUT, strMenuText);

    return (bAppend != FALSE);
}

bool CCalendarWindow::InitIcons()
{
    m_hWndIconSmall = (HICON)::LoadImage(
        _AtlBaseModule.GetResourceInstance(),
        MAKEINTRESOURCE(IDI_CALENDAR),
        IMAGE_ICON,
        ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON),
        LR_DEFAULTCOLOR);
    ATLASSERT(m_hWndIconSmall != NULL);

    SetIcon(m_hWndIconSmall, /*bBigIcon=*/FALSE);

    m_hWndIcon = (HICON)::LoadImage(
        _AtlBaseModule.GetResourceInstance(),
        MAKEINTRESOURCE(IDI_CALENDAR),
        IMAGE_ICON,
        ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON),
        LR_DEFAULTCOLOR);
    ATLASSERT(m_hWndIcon != NULL);

    SetIcon(m_hWndIcon, /*bBigIcon=*/TRUE);

    return (m_hWndIconSmall && m_hWndIcon);
}

bool CCalendarWindow::CreateChildren(LPCREATESTRUCT /*pcs*/)
{
    RECT rc = { 0 };
    GetClientRect(&rc);
    ATLVERIFY(m_ctrlMonthCal.Create(m_hWnd, rc) != NULL);
    
    return (m_ctrlMonthCal.IsWindow() != FALSE);
}

bool CCalendarWindow::InitSettingsPath()
{
    ATLASSERT(m_pathSettings.m_strPath.IsEmpty());

    // Try our folder
    TCHAR sz[MAX_PATH] = { TEXT('\0') };
    ::GetModuleFileName(NULL, sz, MAX_PATH);

    CPath pathFilename(sz);
    pathFilename.RenameExtension(TEXT(".ini"));

    if(pathFilename.FileExists())
        m_pathSettings = pathFilename;
    else
        ATLVERIFY(::MakeSettingsFilename(m_pathSettings));

    return (!m_pathSettings.m_strPath.IsEmpty());
}

bool CCalendarWindow::ApplySettings(bool bResize)
{
    // Transparency
    if(!ApplyTransparency()) return false;

    // Window size and pos (default - one month size)
    if(!ApplyWindowPos(bResize)) return false;

    // Update calendar control
    if(!ApplyCalendarProperties()) return false;

    // Update system tray icon properties
    if(!ApplySysTrayProperties()) return false;

    // Update deskband state
    if(!ApplyDeskbandProperties()) return false;

    return true;
}

bool CCalendarWindow::ApplyTransparency()
{
    const BOOL bRes = ::SetLayeredWindowAttributes(m_hWnd,
        0, m_dlgOptions.m_byteAlpha, LWA_ALPHA);
    ATLASSERT(bRes);

    return (bRes != FALSE);
}

bool CCalendarWindow::ApplyWindowPos(bool bResize)
{
    CRect rcWin;

    if(bResize)
    {
        GetWindowRect(&rcWin);

        CRect rcMin;
        MonthCal_GetMinReqRect(m_ctrlMonthCal, &rcMin);

        rcWin.right = rcWin.left + max(rcMin.Width(),
            (int)MonthCal_GetMaxTodayWidth(m_ctrlMonthCal));
        rcWin.bottom = rcWin.top + rcMin.Height();

        // Do some margins
        rcWin.InflateRect(0, 0,
            rcMin.Width() / 3, rcMin.Height() / 2);
    }

    // Apply changes
    HWND hWndInsertAfter = (m_dlgOptions.m_bTopmost ?
        HWND_TOPMOST : HWND_NOTOPMOST);
    UINT nFlags = SWP_DEFERERASE |
        (bResize ? 0 : (SWP_NOSIZE | SWP_NOMOVE));

    const BOOL bRes = SetWindowPos(hWndInsertAfter, &rcWin, nFlags);
    ATLASSERT(bRes);

    return (bRes != FALSE);
}

bool CCalendarWindow::ApplyCalendarProperties()
{
    BOOL bRes = TRUE;

    // Display week numbers
    const bool bWeekNumbersShown =
        ((m_ctrlMonthCal.GetStyle() & MCS_WEEKNUMBERS) != 0);

    if(bWeekNumbersShown != m_dlgOptions.m_bWeekNumbers)
    {
        DWORD dwRemove = 0;
        DWORD dwAdd = 0;

        if(m_dlgOptions.m_bWeekNumbers)
            dwAdd = MCS_WEEKNUMBERS;
        else
            dwRemove = MCS_WEEKNUMBERS;

        bRes = m_ctrlMonthCal.ModifyStyle(dwRemove, dwAdd);
        ATLASSERT(bRes);
    }

    return (bRes != FALSE);
}

bool CCalendarWindow::ApplySysTrayProperties()
{
    bool bRes = true;

    if(m_hSysTrayIcon)
    {
        bRes = UpdateSysTrayIcon(NIM_MODIFY);
        ATLASSERT(bRes);
    }

    return bRes;
}

bool CCalendarWindow::ApplyDeskbandProperties() const
{
    BOOL fWow64 = TRUE;
    ::IsWow64Process(::GetCurrentProcess(), &fWow64);

    if(fWow64) return true; // cannot install 32-bit deskband for Win64

    const CPath& deskbandPath = GetDeskbandModulePath();
    const bool bDeskbandInstalled = DeskbandInstalled();

    if(m_dlgOptions.m_bDeskband && !bDeskbandInstalled)
    {   // install and show
        if(!ExtractDeskbandBinary(deskbandPath))
            return false;

        if(!RegisterDeskband(deskbandPath, true))
            return false;

        // Show the desk band only if wasn't previously installed;
        // otherwise leave the choice to user.
        if(!ShowDeskband())
            return false;
    }
    else if(!m_dlgOptions.m_bDeskband && bDeskbandInstalled)
    {   // hide and uninstall

        // If there are leftovers of previous deskband objects that cannot
        // be uninstalled (missing files, etc), continue anyway.
        // No point to fail here.
        HideDeskband();
        RegisterDeskband(deskbandPath, false);
        DeleteDeskbandBinary(deskbandPath);
    }

    return true;
}

bool CCalendarWindow::DeskbandInstalled() const
{
    const CString& keyName = TEXT("CLSID\\") +
        ::StringFromGUID2(CLSID_CalendarDeskBand);

    CRegKey keyDeskband;
    const LONG lRes = keyDeskband.Open(HKEY_CLASSES_ROOT, keyName, KEY_READ);

    return (lRes == ERROR_SUCCESS);
}

CPath CCalendarWindow::GetDeskbandModulePath() const
{
    CPath deskbandPath = m_pathSettings;
    deskbandPath.RemoveFileSpec();
    deskbandPath.Append(DESKBAND_BIN_NAME);

    return deskbandPath;
}

bool CCalendarWindow::ExtractDeskbandBinary(const CPath& deskbandPath) const
{
    HRSRC hResInfo = reinterpret_cast<HRSRC>(
        ::FindResource(
            NULL,
            MAKEINTRESOURCE(IDR_DESKBAND_BIN),
            TEXT("DESKBAND")
        )
    );
    ATLASSERT(hResInfo);

    if(!hResInfo) return false;

    HGLOBAL hResData = ::LoadResource(NULL, hResInfo);
    ATLASSERT(hResData);

    if(!hResData) return false;

    LPVOID pDeskbandBinData = ::LockResource(hResData);
    ATLASSERT(pDeskbandBinData);

    if(!pDeskbandBinData) return false;

    const DWORD dwDeskbandBinSize = ::SizeofResource(NULL, hResInfo);
    ATLASSERT(dwDeskbandBinSize > 0);

    if(!dwDeskbandBinSize) return false;

    CAtlFile deskbandBin;
    HRESULT hr = deskbandBin.Create(deskbandPath,
        GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS);
    ATLASSERT(SUCCEEDED(hr));

    if(FAILED(hr)) return false;

    hr = deskbandBin.Write(pDeskbandBinData, dwDeskbandBinSize);
    ATLASSERT(SUCCEEDED(hr));
    if(FAILED(hr)) return false;

    return true;
}

bool CCalendarWindow::DeleteDeskbandBinary(const CPath& deskbandPath) const
{
    if(!deskbandPath.FileExists()) return true;

    CPath deskbandDir = deskbandPath;
    deskbandDir.RemoveFileSpec();

    TCHAR szTmpFile[MAX_PATH + 1] = { 0 };
    const UINT nRet = ::GetTempFileName(deskbandDir, TEXT("del"), 0, szTmpFile);
    ATLASSERT(nRet);

    if(!nRet) return false;

    BOOL bRes = ::MoveFileEx(deskbandPath, szTmpFile, MOVEFILE_REPLACE_EXISTING);
    ATLASSERT(bRes);

    if(!bRes) return false;

    // If user is an admin, then the file gets deleted on the next reboot;
    // for standard users the MOVEFILE_DELAY_UNTIL_REBOOT flag is unavailable,
    // so we delete with DeleteFile, which is likely to fail because
    // the deskband DLL will be in use by Explorer.exe. However, the file
    // will be deleted when last handle to it is closed.
    if(::IsUserAnAdmin())
    {
        bRes = ::MoveFileEx(szTmpFile, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
    }
    else
    {
        ::DeleteFile(szTmpFile);
        bRes = TRUE;
    }
    ATLASSERT(bRes);

    return (bRes != FALSE);
}

bool CCalendarWindow::RegisterDeskband(const CPath& deskbandPath, bool bRegister) const
{
    bool bRes = false;
    if(::IsVistaOrHigher())
    {
        bRes = (RegisterDeskbandSecure(deskbandPath, bRegister) == ERROR_SUCCESS);
    }
    else    // XP
    {
        bRes = SUCCEEDED(RegisterDeskbandXP(deskbandPath, bRegister));
    }

    return bRes;
}

struct LibraryLoader
{
    LibraryLoader(LPCTSTR pszPath) : hModule(::LoadLibrary(pszPath)) {}
    ~LibraryLoader() { if(hModule) ::FreeLibrary(hModule); }

    operator HMODULE() const { return hModule; }

    HMODULE hModule;
};

HRESULT CCalendarWindow::RegisterDeskbandXP(const CPath& deskbandPath, bool bRegister) const
{
    LibraryLoader deskbandLib(deskbandPath);

    HRESULT hr = E_FAIL;
    if(deskbandLib)
    {
        typedef HRESULT (STDAPICALLTYPE *REGISTERSERVERPROC)(void);

        const LPCSTR pszProcName = (bRegister ?
            "DllRegisterServer" : "DllUnregisterServer");

        REGISTERSERVERPROC DllRegisterProc =
            reinterpret_cast<REGISTERSERVERPROC>(
                ::GetProcAddress(deskbandLib, pszProcName));

        if(DllRegisterProc)
        {
            hr = DllRegisterProc();
            ATLASSERT(SUCCEEDED(hr));

            return hr;
        }
    }

    hr = HRESULT_FROM_WIN32(::GetLastError());
    ATLASSERT(FALSE);

    return hr;
}

DWORD CCalendarWindow::RegisterDeskbandSecure(CPath deskbandPath, bool bRegister) const
{
    deskbandPath.QuoteSpaces();
    const CString& parameters = (bRegister ? TEXT("") : TEXT("/u ")) + deskbandPath;

    const DWORD dwExitCode = ::RunAsAdmin(m_hWnd, TEXT("REGSVR32.EXE"), parameters);
    ATLASSERT(dwExitCode == ERROR_SUCCESS);

    return dwExitCode;
}

bool CCalendarWindow::ShowDeskband() const
{
    CComPtr<ITrayDeskBand> spTrayDeskBand;
    HRESULT hr = spTrayDeskBand.CoCreateInstance(CLSID_TrayDeskBand);

    if(SUCCEEDED(hr))   // Vista and higher
    {
        hr = spTrayDeskBand->DeskBandRegistrationChanged();
        ATLASSERT(SUCCEEDED(hr));

        if(SUCCEEDED(hr))
        {
            hr = spTrayDeskBand->IsDeskBandShown(CLSID_CalendarDeskBand);
            ATLASSERT(SUCCEEDED(hr));

            if(SUCCEEDED(hr) && hr == S_FALSE)
                hr = spTrayDeskBand->ShowDeskBand(CLSID_CalendarDeskBand);
        }
    }
    else    // WinXP workaround
    {
        const CString& sAtom = ::StringFromGUID2(CLSID_CalendarDeskBand);

        if(!::GlobalFindAtom(sAtom))
            ::GlobalAddAtom(sAtom);

        // Beware! SHLoadInProc is not implemented under Vista and higher.
        hr = ::SHLoadInProc(CLSID_CalendarDeskBand);
        ATLASSERT(SUCCEEDED(hr));
    }

    return SUCCEEDED(hr);
}

bool CCalendarWindow::HideDeskband() const
{
    CComPtr<ITrayDeskBand> spTrayDeskBand;
    HRESULT hr = spTrayDeskBand.CoCreateInstance(CLSID_TrayDeskBand);

    if(SUCCEEDED(hr))   // Vista and higher
    {
        hr = spTrayDeskBand->IsDeskBandShown(CLSID_CalendarDeskBand);

        if(hr == S_OK)
            hr = spTrayDeskBand->HideDeskBand(CLSID_CalendarDeskBand);
    }
    else    // WinXP
    {
        CComPtr<IBandSite> spBandSite;
        hr = spBandSite.CoCreateInstance(CLSID_TrayBandSiteService);

        if(SUCCEEDED(hr))
        {
            DWORD dwBandID = 0;
            const UINT nBandCount = spBandSite->EnumBands((UINT)-1, &dwBandID);

            for(UINT i = 0; i < nBandCount; ++i)
            {
                spBandSite->EnumBands(i, &dwBandID);

                CComPtr<IPersist> spPersist;
                hr = spBandSite->GetBandObject(dwBandID, IID_IPersist, (void**)&spPersist);
                if(SUCCEEDED(hr))
                {
                    CLSID clsid = CLSID_NULL;
                    hr = spPersist->GetClassID(&clsid);

                    if(SUCCEEDED(hr) && ::IsEqualCLSID(clsid, CLSID_CalendarDeskBand))
                    {
                        hr = spBandSite->RemoveBand(dwBandID);
                        break;
                    }
                }
            }
        }
    }

    return SUCCEEDED(hr);
}

bool CCalendarWindow::ShowOptions(void)
{
    if(!IsWindowVisible() || IsIconic())
        ShowAndActivate();

    if(IDOK == m_dlgOptions.DoModal(m_hWnd))
    {
        ATLVERIFY(m_dlgOptions.SaveSettings(m_pathSettings));
        ATLVERIFY(ApplySettings(/*Resize=*/FALSE));
    }

    return true;
}

bool CCalendarWindow::UpdateSysTrayIcon(DWORD dwAction)
{
    NOTIFYICONDATA nid = { 0 };
    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd = m_hWnd;

    HICON hIconPrev = NULL;

    switch(dwAction)
    {
    case NIM_MODIFY:
        hIconPrev = m_hSysTrayIcon;
        // fall through
    case NIM_ADD:
        {
            SYSTEMTIME stToday = { 0 };
            ::GetLocalTime(&stToday);
            const CString& strDateStr =
                m_dlgOptions.m_dateFormat.FormatDateString(stToday);

            m_hSysTrayIcon = LoadDayIcon(stToday);

            nid.uFlags |= NIF_TIP | NIF_ICON | NIF_MESSAGE;
            nid.uCallbackMessage = WM_SYSTRAYNOTIFY;
            lstrcpyn(nid.szTip, strDateStr, ARRAYSIZE(nid.szTip));
            nid.hIcon = m_hSysTrayIcon;
        }
        break;
    case NIM_DELETE:
        hIconPrev = m_hSysTrayIcon;
        break;
    }

    BOOL bRes = ::Shell_NotifyIcon(dwAction, &nid);
    ATLASSERT(bRes);

    if(hIconPrev != NULL)
        ::DestroyIcon(hIconPrev);

    return (bRes != FALSE);
}

// Note: This function relies on the fact that
//       daily icon ID's increase incrementally.
HICON CCalendarWindow::LoadDayIcon(const SYSTEMTIME& st)
{
    HICON hIcon = (HICON)::LoadImage(
        _AtlBaseModule.GetResourceInstance(),
        MAKEINTRESOURCE(IDI_DAY00 + st.wDay),
        IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);

    ATLASSERT(hIcon != NULL);

    return hIcon;
}

void CCalendarWindow::ShowAboutBox(void)
{
    MSGBOXPARAMS mbp = { 0 };
    mbp.cbSize      = sizeof(MSGBOXPARAMS);
    mbp.hwndOwner   = m_hWnd;
    mbp.hInstance   = _AtlBaseModule.GetResourceInstance();
    mbp.lpszText    = MAKEINTRESOURCE(IDS_ABOUTTEXT);
    mbp.lpszCaption = MAKEINTRESOURCE(IDS_ABOUTCAPTION);
    mbp.dwStyle     = MB_USERICON | MB_OK;
    mbp.lpszIcon    = MAKEINTRESOURCE(IDI_CALENDAR);
    mbp.dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);

    ::MessageBoxIndirect(&mbp);
}

int CCalendarWindow::ShowContextMenu(void)
{
    HMENU hMenu = ::LoadMenu(
        _AtlBaseModule.GetResourceInstance(),
        MAKEINTRESOURCE(IDM_CALENDAR));
    ATLASSERT(hMenu != NULL);

    HMENU hTrackPopup = ::GetSubMenu(hMenu, 0);
    ATLASSERT(hTrackPopup != NULL);

    const UINT nCheck = MF_BYCOMMAND |
        (IsWindowVisible() ? MF_CHECKED : MF_UNCHECKED);
    ::CheckMenuItem(hTrackPopup, IDC_CALENDAR, nCheck);

    POINT pt = { 0 };
    ::GetCursorPos(&pt);

    ::SetForegroundWindow(m_hWnd);

    int nCmd = ::TrackPopupMenu(hTrackPopup, TPM_RETURNCMD,
        pt.x, pt.y, 0, m_hWnd, NULL);

    PostMessage(WM_NULL);

    return nCmd;
}

void CCalendarWindow::HandleContextCommand(int nCmd)
{
    if(nCmd == 0) return;

    switch(nCmd)
    {
    case IDC_CALENDAR:
        if(IsWindowVisible())
            ShowWindow(SW_HIDE);
        else
            ShowAndActivate();
        break;
    case IDC_OPTIONS:
        ShowOptions();
        break;
    case IDC_ABOUT:
        ShowAboutBox();
        break;
    case IDC_EXIT:
        DestroyWindow();
        break;
    default:
        ATLASSERT(FALSE);
    }
}

UINT_PTR CCalendarWindow::SetUpdateTimer()
{
    // Wake up every 10 seconds.
    UINT_PTR nTimerId = SetTimer(UPDATE_TIMER_ID, 10 * 1000);

    ATLASSERT(nTimerId != 0);

    return nTimerId;
}

void CCalendarWindow::UpdateTodayDate(const SYSTEMTIME& stNow)
{
    MonthCal_SetToday(m_ctrlMonthCal, &stNow);

    ATLVERIFY(UpdateSysTrayIcon(NIM_MODIFY));
}

void CCalendarWindow::ShowAndActivate()
{
    if(!IsWindowVisible() || IsIconic())
        ShowWindow(SW_SHOWNORMAL);

    ::SetForegroundWindow(m_hWnd);
}

/////////////////////////////////////////////////////////////////////////////

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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

Share

About the Author

Alex Blekhman
Software Developer
Australia Australia
More than ten years of C++ native development, and counting.
 
Smile | :)

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411019.1 | Last Updated 28 Aug 2009
Article Copyright 2009 by Alex Blekhman
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid