タスクトレイは上三角印を押すと表示される。

本ページでは以下の機能の確認を行う。
- 不可視のウィンドウを作成する
- タスクトレイにプログラムのアイコンを表示する
- アイコンを右クリックでメニューを表示し、項目が選択されたことを知る
実装にあたり、主に以下のページを参考にした。
ソースファイル
#include <stdio.h> #include <strsafe.h> #include <wchar.h> #include <windows.h> #include <windowsx.h> #include "./resource.h" #include "./util.h" namespace { constexpr wchar_t WINDOW_NAME[] = L"Template window"; constexpr wchar_t CLASS_NAME[] = L"Template class"; constexpr int TASKTRAY_ICONID = 1; } // namespace BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { (void)hwnd; (void)lpCreateStruct; HINSTANCE hInstance = (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE); // Add task tray icon. NOTIFYICONDATA nid; ZeroMemory(&nid, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = hwnd; nid.uID = TASKTRAY_ICONID; nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; nid.uCallbackMessage = WM_TASKTRAY; nid.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1)); StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), L"Task tray test"); if (Shell_NotifyIcon(NIM_ADD, &nid) != TRUE) { #ifdef DEBUG fwprintf(stderr, L"Failed to add an icon on task tray!\n"); #endif return FALSE; } return TRUE; } void Cls_OnDestroy(HWND hwnd) { (void)hwnd; // Remove task tray icon. NOTIFYICONDATA nid; ZeroMemory(&nid, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = hwnd; nid.uID = TASKTRAY_ICONID; nid.uFlags = 0; Shell_NotifyIcon(NIM_DELETE, &nid); PostQuitMessage(0); } void Cls_OnClose(HWND hwnd) { DestroyWindow(hwnd); } void Cls_OnCommand(HWND hwnd, int id, HWND hWndCtl, UINT codeNotify) { (void)hwnd; (void)id; (void)hWndCtl; (void)codeNotify; switch (id) { case IDM_FOLDER: #ifdef DEBUG fwprintf(stderr, L"Folder is clicked\n"); #endif break; case IDM_QUIT: #ifdef DEBUG fwprintf(stderr, L"Quit is clicked\n"); #endif break; default: break; } } void Cls_OnTaskTray(HWND hwnd, UINT id, UINT uMsg) { (void)hwnd; if (id != TASKTRAY_ICONID) { return; } switch (uMsg) { case WM_RBUTTONDOWN: { // Display menu when right button is clicked on task tray icon. POINT point; GetCursorPos(&point); HINSTANCE hInstance = (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE); HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)); HMENU hSubMenu = GetSubMenu(hMenu, 0); SetForegroundWindow(hwnd); TrackPopupMenu(hSubMenu, TPM_LEFTALIGN | TPM_BOTTOMALIGN, point.x, point.y, 0, hwnd, NULL); DestroyMenu(hMenu); PostMessage(hwnd, WM_NULL, 0, 0); } break; default: break; } } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { HANDLE_MSG(hwnd, WM_CREATE, Cls_OnCreate); HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy); HANDLE_MSG(hwnd, WM_COMMAND, Cls_OnCommand); HANDLE_MSG(hwnd, WM_CLOSE, Cls_OnClose); HANDLE_MSG(hwnd, WM_TASKTRAY, Cls_OnTaskTray); default: return DefWindowProc(hwnd, message, wParam, lParam); } } int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpsCmdLine, int nCmdShow) { (void)hPrevInstance; (void)lpsCmdLine; #ifdef DEBUG FILE* fp = nullptr; AllocConsole(); _wfreopen_s(&fp, L"CONOUT$", L"w", stdout); _wfreopen_s(&fp, L"CONOUT$", L"w", stderr); _wfreopen_s(&fp, L"CONIN$", L"r", stdin); #endif #ifdef DEBUG fwprintf(stdout, L"Hello world to stdout!\n"); fwprintf(stderr, L"Hello world to stderr!\n"); #endif WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1)); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = CLASS_NAME; if (!RegisterClass(&wc)) { return FALSE; } HWND hWnd = CreateWindow(CLASS_NAME, WINDOW_NAME, WS_DISABLED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); if (hWnd == NULL) { return FALSE; } MSG msg; while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } #ifdef DEBUG FreeConsole(); #endif return 0; }
(2019/9/10 追記)
Notification iconからポップアップメニューを表示する場合、先にSetForegroundWindow()を呼んでおかないと、マウスカーソルがよそに行ってもメニューが消えなない不具合が発生する。また、現在のウィンドウがフォアグラウンドウインドウだと、先に強制的にタスクスイッチを切り替えておかないと、次回以降のメニューの表示で一瞬ポップアップメニューが出てすぐに消えてしまう不具合が発生するらしい(参考 TrackPopupMenu function)。
ヘッダファイル
#include <wchar.h> #ifndef _UTIL_H_ #define _UTIL_H_ #include <wchar.h> #include <windows.h> #include <windowsx.h> // Application-defined message identifiers. #define WM_TASKTRAY (WM_APP + 1) // void Cls_OnTaskTray(HWND hwnd, UINT id, UINT uMsg) #define HANDLE_WM_TASKTRAY(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), (UINT)(wParam), (UINT)(lParam)), 0L) #endif // _UTIL_H_
ポイント
- TASKTRAY_ICONID
タスクトレイアイコンに任意のIDを指定する。
今回はアイコンを一つだけタスクトレイに登録するので、IDは一つでよい。 - Shell_NotifyIcon
プログラムがタスクトレイにアイコンを登録する際に呼ぶ関数。
アイコンを登録する場合には第一引数をNIM_ADD、削除する場合にはNIM_DELETEとする。
第二引数には、アイコンに関する細かな指定をするためのNOTIFYICONDATA構造体を渡す必要がある。
アイコンの初期化と終了の処理は、それぞれCls_OnCreateとCls_OnDestroyで行う。 - NOTIFYICONDATA
uIDにはTASKTRAY_ICONIDを入れる。
uCallbackMessageには、WM_TASKTRAYを入れる(後述)。
そのほか細かい設定ができる。 - ZeroMemory(&nid, sizeof(nid));
若干脱線するが、sizeofについての小ネタ。
sizeofには型を入れるべきか、変数名を入れるべきかという疑問を実装中に抱いた。
調べたところ、Google Style Guideによると、後の変更の可能性を考慮してsizeofには変数名を渡した方が良いという。 - GetWindowLong
プログラムのインスタンスハンドル(hInstance)をウィンドウハンドル(hWnd)から取得したい場合によく使う関数。
ほかにもいろいろできるらしい。 - Cls_OnCommand
メニューが選択された時、WM_COMMANDメッセージが送られて来る。
メッセージクラッカでは引数のidにメニュー項目の識別子がいれられるので、switch文で押された項目を判定できる。
似たメッセージにWM_MENUSELECTがある。
最初はそちらのメッセージで、どのメニュー項目が選ばれたかを知ろうとしていた。
しかし、WM_MENUSELECTは選択中のメニュー項目が変化したこと知るためのメッセージである。
クリックやエンターで選択された項目の取得には使えないようだ。
ちょっとハマったので注意。 - WS_DISABLED
ウィンドウ作成時に、ウィンドウスタイルとしてWS_DISABLEDを指定する。
これにより、不可視のウィンドウが作成できる。 - WM_TASKTRAY
タスクトレイアイコンに対する操作のメッセージは、プログラマが任意に決めることができる。
WM_TASKTRAYはもともとのWindows APIには存在しないメッセージ定数であるが、定義しておくことで利便性を図る。 - Cls_OnTaskTray、HANDLE_WM_TASKTRAY
WM_TASKTRAYに対応するメッセージクラッカも自前で作成する必要がある。
HANDLE_WM_TASKTRAYはマクロ関数である。
実装方法はwindowsx.hの他のマクロ関数と同様。
リソースファイル
- // Generated by ResEdit 1.6.6
- // Copyright (C) 2006-2015
- // http://www.resedit.net
#include <windows.h> #include <commctrl.h> #include <richedit.h> #include "resource.h" #pragma code_page(65001) // // Menu resources // LANGUAGE LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN IDR_MENU1 MENU { POPUP "PopupMenu" { MENUITEM "Folder", IDM_FOLDER MENUITEM "Quit", IDM_QUIT } } // // Icon resources // LANGUAGE LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN IDI_ICON1 ICON ".\\icon.ico"
こんな感じでメニュー項目を定義する。
リソースファイルは毎度のごとくResEditで作成した。
プロジェクト全体はGitHubにあげておく。
0 件のコメント:
コメントを投稿
コメント表示は承認制に設定しています