410 lines
14 KiB
C++
410 lines
14 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved
|
|
|
|
// we need commctrl v6 for LoadIconMetric()
|
|
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
|
#pragma comment(lib, "comctl32.lib")
|
|
|
|
#include "resource.h"
|
|
#include <windows.h>
|
|
#include <shellapi.h>
|
|
#include <commctrl.h>
|
|
#include <strsafe.h>
|
|
|
|
HINSTANCE g_hInst = NULL;
|
|
|
|
UINT const WMAPP_NOTIFYCALLBACK = WM_APP + 1;
|
|
UINT const WMAPP_HIDEFLYOUT = WM_APP + 2;
|
|
|
|
UINT_PTR const HIDEFLYOUT_TIMER_ID = 1;
|
|
|
|
wchar_t const szWindowClass[] = L"NotificationIconTest";
|
|
wchar_t const szFlyoutWindowClass[] = L"NotificationFlyout";
|
|
|
|
// Use a guid to uniquely identify our icon
|
|
class __declspec(uuid("9D0B8B92-4E1C-488e-A1E1-2331AFCE2CB5")) PrinterIcon;
|
|
|
|
// Forward declarations of functions included in this code module:
|
|
void RegisterWindowClass(PCWSTR pszClassName, PCWSTR pszMenuName, WNDPROC lpfnWndProc);
|
|
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
|
|
ATOM RegisterFlyoutClass(HINSTANCE hInstance);
|
|
LRESULT CALLBACK FlyoutWndProc(HWND, UINT, WPARAM, LPARAM);
|
|
HWND ShowFlyout(HWND hwnd);
|
|
void HideFlyout(HWND hwndMainWindow, HWND hwndFlyout);
|
|
void PositionFlyout(HWND hwnd, REFGUID guidIcon);
|
|
void ShowContextMenu(HWND hwnd, POINT pt);
|
|
BOOL AddNotificationIcon(HWND hwnd);
|
|
BOOL DeleteNotificationIcon();
|
|
BOOL ShowLowInkBalloon();
|
|
BOOL ShowNoInkBalloon();
|
|
BOOL ShowPrintJobBalloon();
|
|
BOOL RestoreTooltip();
|
|
|
|
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR /*lpCmdLine*/, int nCmdShow)
|
|
{
|
|
g_hInst = hInstance;
|
|
RegisterWindowClass(szWindowClass, MAKEINTRESOURCE(IDC_NOTIFICATIONICON), WndProc);
|
|
RegisterWindowClass(szFlyoutWindowClass, NULL, FlyoutWndProc);
|
|
|
|
// Create the main window. This could be a hidden window if you don't need
|
|
// any UI other than the notification icon.
|
|
WCHAR szTitle[100];
|
|
LoadString(hInstance, IDS_APP_TITLE, szTitle, ARRAYSIZE(szTitle));
|
|
HWND hwnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT, 0, 250, 200, NULL, NULL, g_hInst, NULL);
|
|
if (hwnd)
|
|
{
|
|
ShowWindow(hwnd, nCmdShow);
|
|
|
|
// Main message loop:
|
|
MSG msg;
|
|
while (GetMessage(&msg, NULL, 0, 0))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void RegisterWindowClass(PCWSTR pszClassName, PCWSTR pszMenuName, WNDPROC lpfnWndProc)
|
|
{
|
|
WNDCLASSEX wcex = {sizeof(wcex)};
|
|
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
|
wcex.lpfnWndProc = lpfnWndProc;
|
|
wcex.hInstance = g_hInst;
|
|
wcex.hIcon = LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_NOTIFICATIONICON));
|
|
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
|
|
wcex.lpszMenuName = pszMenuName;
|
|
wcex.lpszClassName = pszClassName;
|
|
RegisterClassEx(&wcex);
|
|
}
|
|
|
|
BOOL AddNotificationIcon(HWND hwnd)
|
|
{
|
|
NOTIFYICONDATA nid = {sizeof(nid)};
|
|
nid.hWnd = hwnd;
|
|
// add the icon, setting the icon, tooltip, and callback message.
|
|
// the icon will be identified with the GUID
|
|
nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP | NIF_GUID;
|
|
nid.guidItem = __uuidof(PrinterIcon);
|
|
nid.uCallbackMessage = WMAPP_NOTIFYCALLBACK;
|
|
LoadIconMetric(g_hInst, MAKEINTRESOURCE(IDI_NOTIFICATIONICON), LIM_SMALL, &nid.hIcon);
|
|
LoadString(g_hInst, IDS_TOOLTIP, nid.szTip, ARRAYSIZE(nid.szTip));
|
|
Shell_NotifyIcon(NIM_ADD, &nid);
|
|
|
|
// NOTIFYICON_VERSION_4 is prefered
|
|
nid.uVersion = NOTIFYICON_VERSION_4;
|
|
return Shell_NotifyIcon(NIM_SETVERSION, &nid);
|
|
}
|
|
|
|
BOOL DeleteNotificationIcon()
|
|
{
|
|
NOTIFYICONDATA nid = {sizeof(nid)};
|
|
nid.uFlags = NIF_GUID;
|
|
nid.guidItem = __uuidof(PrinterIcon);
|
|
return Shell_NotifyIcon(NIM_DELETE, &nid);
|
|
}
|
|
|
|
BOOL ShowLowInkBalloon()
|
|
{
|
|
// Display a low ink balloon message. This is a warning, so show the appropriate system icon.
|
|
NOTIFYICONDATA nid = {sizeof(nid)};
|
|
nid.uFlags = NIF_INFO | NIF_GUID;
|
|
nid.guidItem = __uuidof(PrinterIcon);
|
|
// respect quiet time since this balloon did not come from a direct user action.
|
|
nid.dwInfoFlags = NIIF_WARNING | NIIF_RESPECT_QUIET_TIME;
|
|
LoadString(g_hInst, IDS_LOWINK_TITLE, nid.szInfoTitle, ARRAYSIZE(nid.szInfoTitle));
|
|
LoadString(g_hInst, IDS_LOWINK_TEXT, nid.szInfo, ARRAYSIZE(nid.szInfo));
|
|
return Shell_NotifyIcon(NIM_MODIFY, &nid);
|
|
}
|
|
|
|
BOOL ShowNoInkBalloon()
|
|
{
|
|
// Display an out of ink balloon message. This is a error, so show the appropriate system icon.
|
|
NOTIFYICONDATA nid = {sizeof(nid)};
|
|
nid.uFlags = NIF_INFO | NIF_GUID;
|
|
nid.guidItem = __uuidof(PrinterIcon);
|
|
nid.dwInfoFlags = NIIF_ERROR;
|
|
LoadString(g_hInst, IDS_NOINK_TITLE, nid.szInfoTitle, ARRAYSIZE(nid.szInfoTitle));
|
|
LoadString(g_hInst, IDS_NOINK_TEXT, nid.szInfo, ARRAYSIZE(nid.szInfo));
|
|
return Shell_NotifyIcon(NIM_MODIFY, &nid);
|
|
}
|
|
|
|
BOOL ShowPrintJobBalloon()
|
|
{
|
|
// Display a balloon message for a print job with a custom icon
|
|
NOTIFYICONDATA nid = {sizeof(nid)};
|
|
nid.uFlags = NIF_INFO | NIF_GUID;
|
|
nid.guidItem = __uuidof(PrinterIcon);
|
|
nid.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON;
|
|
LoadString(g_hInst, IDS_PRINTJOB_TITLE, nid.szInfoTitle, ARRAYSIZE(nid.szInfoTitle));
|
|
LoadString(g_hInst, IDS_PRINTJOB_TEXT, nid.szInfo, ARRAYSIZE(nid.szInfo));
|
|
LoadIconMetric(g_hInst, MAKEINTRESOURCE(IDI_NOTIFICATIONICON), LIM_LARGE, &nid.hBalloonIcon);
|
|
return Shell_NotifyIcon(NIM_MODIFY, &nid);
|
|
}
|
|
|
|
BOOL RestoreTooltip()
|
|
{
|
|
// After the balloon is dismissed, restore the tooltip.
|
|
NOTIFYICONDATA nid = {sizeof(nid)};
|
|
nid.uFlags = NIF_SHOWTIP | NIF_GUID;
|
|
nid.guidItem = __uuidof(PrinterIcon);
|
|
return Shell_NotifyIcon(NIM_MODIFY, &nid);
|
|
}
|
|
|
|
void PositionFlyout(HWND hwnd, REFGUID guidIcon)
|
|
{
|
|
// find the position of our printer icon
|
|
NOTIFYICONIDENTIFIER nii = {sizeof(nii)};
|
|
nii.guidItem = guidIcon;
|
|
RECT rcIcon;
|
|
HRESULT hr = Shell_NotifyIconGetRect(&nii, &rcIcon);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// display the flyout in an appropriate position close to the printer icon
|
|
POINT const ptAnchor = { (rcIcon.left + rcIcon.right) / 2, (rcIcon.top + rcIcon.bottom)/2 };
|
|
|
|
RECT rcWindow;
|
|
GetWindowRect(hwnd, &rcWindow);
|
|
SIZE sizeWindow = {rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top};
|
|
|
|
if (CalculatePopupWindowPosition(&ptAnchor, &sizeWindow, TPM_VERTICAL | TPM_VCENTERALIGN | TPM_CENTERALIGN | TPM_WORKAREA, &rcIcon, &rcWindow))
|
|
{
|
|
// position the flyout and make it the foreground window
|
|
SetWindowPos(hwnd, HWND_TOPMOST, rcWindow.left, rcWindow.top, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW);
|
|
}
|
|
}
|
|
}
|
|
|
|
HWND ShowFlyout(HWND hwndMainWindow)
|
|
{
|
|
// size of the bitmap image (which will be the client area of the flyout window).
|
|
RECT rcWindow = {};
|
|
rcWindow.right = 214;
|
|
rcWindow.bottom = 180;
|
|
DWORD const dwStyle = WS_POPUP | WS_THICKFRAME;
|
|
// adjust the window size to take the frame into account
|
|
AdjustWindowRectEx(&rcWindow, dwStyle, FALSE, WS_EX_TOOLWINDOW);
|
|
|
|
HWND hwndFlyout = CreateWindowEx(WS_EX_TOOLWINDOW, szFlyoutWindowClass, NULL, dwStyle,
|
|
CW_USEDEFAULT, 0, rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top, hwndMainWindow, NULL, g_hInst, NULL);
|
|
if (hwndFlyout)
|
|
{
|
|
PositionFlyout(hwndFlyout, __uuidof(PrinterIcon));
|
|
SetForegroundWindow(hwndFlyout);
|
|
}
|
|
return hwndFlyout;
|
|
}
|
|
|
|
void HideFlyout(HWND hwndMainWindow, HWND hwndFlyout)
|
|
{
|
|
DestroyWindow(hwndFlyout);
|
|
|
|
// immediately after hiding the flyout we don't want to allow showing it again, which will allow clicking
|
|
// on the icon to hide the flyout. If we didn't have this code, clicking on the icon when the flyout is open
|
|
// would cause the focus change (from flyout to the taskbar), which would trigger hiding the flyout
|
|
// (see the WM_ACTIVATE handler). Since the flyout would then be hidden on click, it would be shown again instead
|
|
// of hiding.
|
|
SetTimer(hwndMainWindow, HIDEFLYOUT_TIMER_ID, GetDoubleClickTime(), NULL);
|
|
}
|
|
|
|
void ShowContextMenu(HWND hwnd, POINT pt)
|
|
{
|
|
HMENU hMenu = LoadMenu(g_hInst, MAKEINTRESOURCE(IDC_CONTEXTMENU));
|
|
if (hMenu)
|
|
{
|
|
HMENU hSubMenu = GetSubMenu(hMenu, 0);
|
|
if (hSubMenu)
|
|
{
|
|
// our window must be foreground before calling TrackPopupMenu or the menu will not disappear when the user clicks away
|
|
SetForegroundWindow(hwnd);
|
|
|
|
// respect menu drop alignment
|
|
UINT uFlags = TPM_RIGHTBUTTON;
|
|
if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0)
|
|
{
|
|
uFlags |= TPM_RIGHTALIGN;
|
|
}
|
|
else
|
|
{
|
|
uFlags |= TPM_LEFTALIGN;
|
|
}
|
|
|
|
TrackPopupMenuEx(hSubMenu, uFlags, pt.x, pt.y, hwnd, NULL);
|
|
}
|
|
DestroyMenu(hMenu);
|
|
}
|
|
}
|
|
|
|
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
static HWND s_hwndFlyout = NULL;
|
|
static BOOL s_fCanShowFlyout = TRUE;
|
|
|
|
switch (message)
|
|
{
|
|
case WM_CREATE:
|
|
// add the notification icon
|
|
if (!AddNotificationIcon(hwnd))
|
|
{
|
|
MessageBox(hwnd,
|
|
L"Please read the ReadMe.txt file for troubleshooting",
|
|
L"Error adding icon", MB_OK);
|
|
return -1;
|
|
}
|
|
break;
|
|
case WM_COMMAND:
|
|
{
|
|
int const wmId = LOWORD(wParam);
|
|
// Parse the menu selections:
|
|
switch (wmId)
|
|
{
|
|
case IDM_LOWINK:
|
|
ShowLowInkBalloon();
|
|
break;
|
|
|
|
case IDM_NOINK:
|
|
ShowNoInkBalloon();
|
|
break;
|
|
|
|
case IDM_PRINTJOB:
|
|
ShowPrintJobBalloon();
|
|
break;
|
|
|
|
case IDM_OPTIONS:
|
|
// placeholder for an options dialog
|
|
MessageBox(hwnd, L"Display the options dialog here.", L"Options", MB_OK);
|
|
break;
|
|
|
|
case IDM_EXIT:
|
|
DestroyWindow(hwnd);
|
|
break;
|
|
|
|
case IDM_FLYOUT:
|
|
s_hwndFlyout = ShowFlyout(hwnd);
|
|
break;
|
|
|
|
default:
|
|
return DefWindowProc(hwnd, message, wParam, lParam);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WMAPP_NOTIFYCALLBACK:
|
|
switch (LOWORD(lParam))
|
|
{
|
|
case NIN_SELECT:
|
|
// for NOTIFYICON_VERSION_4 clients, NIN_SELECT is prerable to listening to mouse clicks and key presses
|
|
// directly.
|
|
if (IsWindowVisible(s_hwndFlyout))
|
|
{
|
|
HideFlyout(hwnd, s_hwndFlyout);
|
|
s_hwndFlyout = NULL;
|
|
s_fCanShowFlyout = FALSE;
|
|
}
|
|
else if (s_fCanShowFlyout)
|
|
{
|
|
s_hwndFlyout = ShowFlyout(hwnd);
|
|
}
|
|
break;
|
|
|
|
case NIN_BALLOONTIMEOUT:
|
|
RestoreTooltip();
|
|
break;
|
|
|
|
case NIN_BALLOONUSERCLICK:
|
|
RestoreTooltip();
|
|
// placeholder for the user clicking on the balloon.
|
|
MessageBox(hwnd, L"The user clicked on the balloon.", L"User click", MB_OK);
|
|
break;
|
|
|
|
case WM_CONTEXTMENU:
|
|
{
|
|
POINT const pt = { LOWORD(wParam), HIWORD(wParam) };
|
|
ShowContextMenu(hwnd, pt);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WMAPP_HIDEFLYOUT:
|
|
HideFlyout(hwnd, s_hwndFlyout);
|
|
s_hwndFlyout = NULL;
|
|
s_fCanShowFlyout = FALSE;
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
if (wParam == HIDEFLYOUT_TIMER_ID)
|
|
{
|
|
// please see the comment in HideFlyout() for an explanation of this code.
|
|
KillTimer(hwnd, HIDEFLYOUT_TIMER_ID);
|
|
s_fCanShowFlyout = TRUE;
|
|
}
|
|
break;
|
|
case WM_DESTROY:
|
|
DeleteNotificationIcon();
|
|
PostQuitMessage(0);
|
|
break;
|
|
default:
|
|
return DefWindowProc(hwnd, message, wParam, lParam);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void FlyoutPaint(HWND hwnd, HDC hdc)
|
|
{
|
|
// Since this is a DPI aware application (see DeclareDPIAware.manifest), if the flyout window
|
|
// were to show text we would need to increase the size. We could also have multiple sizes of
|
|
// the bitmap image and show the appropriate image for each DPI, but that would complicate the
|
|
// sample.
|
|
static HBITMAP hbmp = NULL;
|
|
if (hbmp == NULL)
|
|
{
|
|
hbmp = (HBITMAP)LoadImage(g_hInst, MAKEINTRESOURCE(IDB_PRINTER), IMAGE_BITMAP, 0, 0, 0);
|
|
}
|
|
if (hbmp)
|
|
{
|
|
RECT rcClient;
|
|
GetClientRect(hwnd, &rcClient);
|
|
HDC hdcMem = CreateCompatibleDC(hdc);
|
|
if (hdcMem)
|
|
{
|
|
HGDIOBJ hBmpOld = SelectObject(hdcMem, hbmp);
|
|
BitBlt(hdc, 0, 0, rcClient.right, rcClient.bottom, hdcMem, 0, 0, SRCCOPY);
|
|
SelectObject(hdcMem, hBmpOld);
|
|
DeleteDC(hdcMem);
|
|
}
|
|
}
|
|
}
|
|
LRESULT CALLBACK FlyoutWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
case WM_PAINT:
|
|
{
|
|
// paint a pretty picture
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hwnd, &ps);
|
|
FlyoutPaint(hwnd, hdc);
|
|
EndPaint(hwnd, &ps);
|
|
}
|
|
break;
|
|
case WM_ACTIVATE:
|
|
if (LOWORD(wParam) == WA_INACTIVE)
|
|
{
|
|
// when the flyout window loses focus, hide it.
|
|
PostMessage(GetParent(hwnd), WMAPP_HIDEFLYOUT, 0, 0);
|
|
}
|
|
break;
|
|
default:
|
|
return DefWindowProc(hwnd, message, wParam, lParam);
|
|
}
|
|
return 0;
|
|
}
|