前言
最近更新網易云發現任務欄按鈕中除了播放相關的按鈕,多了一個喜歡的按鈕:
之前我一直以為網易云任務欄的按鈕只是 Windows 為音樂軟件專門提供的,于是我又看了一眼系統自帶的播放器,發現并沒有愛心按鈕:
這時我就想會不會是 Windows 提供了相關接口可以讓用戶自定義,一搜發現還真有,ITaskbarList3接口提供了自定義任務欄按鈕的方法,于是就有了下面這個 demo 的實現:
在實現的過程中也遇到了很多問題:
- 由于自定義縮略圖,導致懸浮在縮略圖上無法查看原有的預覽窗口內容。
- 使用 WIN + TAB 切換窗口時,顯示的預覽圖是縮略圖無法查看原有的預覽窗口內容。
不過,經過搜索發現網易云的開發者已經分享過相關的思路(文末的參考文獻),就是沒有相應的編碼實現,之后我就按照自己的理解實現了相關的功能,相關效果見下圖,本文涉及到的完整代碼已上傳到GitHub。
使用 WIN + TAB 正常顯示原窗口信息:
鼠標懸浮縮略圖上正常顯示原窗口信息:
自定義按鈕
首先是自定義按鈕的實現,我們先添加四個按鈕,使用ITaskbarList3
接口即可:
#include <shobjidl.h>#define BTN_COUNT 4// 任務欄按鈕
THUMBBUTTON btns[BTN_COUNT];// 任務欄對象
ITaskbarList3* pTaskbar;// 初始化 COM
CoInitialize(NULL);
CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pTaskbar));WCHAR tips[BTN_COUNT][4] = { L"上一首", L"暫停", L"下一首", L"喜歡" };
int icons[BTN_COUNT] = { IDI_PREVIOUS, IDI_PAUSE, IDI_NEXT, IDI_UNLIKE };for (int i = 0; i < BTN_COUNT; i++)
{btns[i].dwMask = THB_BITMAP | THB_ICON | THB_FLAGS | THB_TOOLTIP;btns[i].iId = 1000 + i;btns[i].iBitmap = i;btns[i].hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(icons[i]));btns[i].dwFlags = THBF_ENABLED;wcscpy_s(btns[i].szTip, tips[i]);
}pTaskbar->ThumbBarAddButtons(hWnd, BTN_COUNT, btns);// 釋放資源
pTaskbar->Release();
CoUninitialize();
然后針對對應的按鈕,設置相應的點擊事件,這里的DwmSetIconicThumbnail
用于設置縮略圖,留到下面再具體說明,1000 ~ 1003
對應上文中設置的按鈕的iId
:
#define BG_COUNT 3// 當前下標
int bgIndex = 0;// 背景圖
WCHAR bgImgs[3][8] = { L"bg1.bmp", L"bg2.bmp", L"bg3.bmp" };// 控制暫停/播放切換
bool play = true;// 控制喜歡/取消喜歡切換
bool unlike = true;case WM_COMMAND:{int wmId = LOWORD(wParam);// 分析菜單選擇:switch (wmId){case 1000:bgIndex = (bgIndex + BG_COUNT - 1) % BG_COUNT;DwmSetIconicThumbnail(hWnd, LoadImageAndConvertToHBITMAP(bgImgs[bgIndex]), 0);break;case 1001:if (play) {btns[1].hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_PAUSE));wcscpy_s(btns[1].szTip, L"播放");}else {btns[1].hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_PLAY));wcscpy_s(btns[1].szTip, L"暫停");}play = !play;// 更新按鈕顯示pTaskbar->ThumbBarUpdateButtons(hWnd, BTN_COUNT, btns);break;case 1002:bgIndex = (bgIndex + 1) % BG_COUNT;DwmSetIconicThumbnail(hWnd, LoadImageAndConvertToHBITMAP(bgImgs[bgIndex]), 0);break;case 1003:if (unlike) {btns[3].hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_LIKE));wcscpy_s(btns[3].szTip, L"取消喜歡");}else {btns[3].hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_UNLIKE));wcscpy_s(btns[3].szTip, L"喜歡");}unlike = !unlike;// 更新按鈕顯示pTaskbar->ThumbBarUpdateButtons(hWnd, BTN_COUNT, btns);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}}break;
以上兩步實現了以下效果:
自定義縮略圖
自定義縮略圖需要使用到DwmSetIconicThumbnail接口,同時還需要注意縮略圖的格式必須為bmp
,這里使用GDI
進行加載:
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")// 開啟自定義背景
BOOL enableBg = TRUE;// 初始化 GDI+
ULONG_PTR gdiplusToken;// 是否初始化 GDI+
bool initGDI = false;void InitializeGDIPlus() {Gdiplus::GdiplusStartupInput gdiplusStartupInput;Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
}void ShutdownGDIPlus() {Gdiplus::GdiplusShutdown(gdiplusToken);
}// 加載圖像文件并返回 HBITMAP
HBITMAP LoadImageAndConvertToHBITMAP(const WCHAR* filePath) {if (!initGDI) {InitializeGDIPlus();initGDI = true;}Gdiplus::Bitmap bitmap(filePath);if (bitmap.GetLastStatus() != Gdiplus::Ok) {return nullptr;}HBITMAP hBitmap = nullptr;Gdiplus::Color color;bitmap.GetHBITMAP(color, &hBitmap);return hBitmap;
}case WM_CREATE:// 開啟自定義縮略圖DwmSetWindowAttribute(hWnd, DWMWA_HAS_ICONIC_BITMAP, &enableBg, sizeof(BOOL));DwmSetWindowAttribute(hWnd, DWMWA_FORCE_ICONIC_REPRESENTATION, &enableBg, sizeof(BOOL));DwmInvalidateIconicBitmaps(hWnd);break;case WM_DWMSENDICONICTHUMBNAIL:// 設置縮略圖DwmSetIconicThumbnail(hWnd, LoadImageAndConvertToHBITMAP(bgImgs[bgIndex]), 0);break;
GdiplusStartup
不能在 main 中調用,原因參考官方文檔。
以上步驟就實現了我們的基本功能:
細節優化
通過上述操作,我們已經完成了自定義按鈕和縮略圖的功能,但是通過 WIN + TAB 會發現顯示的窗口也變成光禿禿的縮略圖:
同時懸浮在縮略圖上顯示的窗口也不正常:
強迫癥表示受不了!
于是就有了下面的優化(思路參考參考文獻的文章):
- 創建一個臨時窗口用于正常顯示以上兩個界面,并把該窗口設置為隱藏。
- 通過
ITaskbarList3
接口的RegisterTab
和SetTabOrder
方法將隱藏窗口和原窗口設置成組。
具體實現如下:
// 臨時窗口
HWND tmp;tmp = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,0, 0, 0, 0, nullptr, nullptr, hInstance, nullptr);// SW_HIDE 隱藏窗口
ShowWindow(tmp, SW_HIDE);// 注冊成組
pTaskbar->RegisterTab(tmp, hWnd);
pTaskbar->SetTabOrder(tmp, hWnd);UpdateWindow(tmp);// 發送 WM_DWMSENDICONICTHUMBNAIL 信息避免第一次縮略圖顯示異常
SendMessage(tmp, WM_DWMSENDICONICTHUMBNAIL, (WPARAM)tmp, 0);case WM_CREATE:// 開啟自定義縮略圖DwmSetWindowAttribute(hWnd, DWMWA_HAS_ICONIC_BITMAP, &enableBg, sizeof(BOOL));break;case WM_DWMSENDICONICTHUMBNAIL:// 需要重新設置按鈕, 否則無法正常顯示pTaskbar->ThumbBarAddButtons(hWnd, BTN_COUNT, btns);pTaskbar->ThumbBarUpdateButtons(hWnd, BTN_COUNT, btns);DwmSetWindowAttribute(hWnd, DWMWA_FORCE_ICONIC_REPRESENTATION, &enableBg, sizeof(BOOL));DwmInvalidateIconicBitmaps(hWnd);DwmSetIconicThumbnail(hWnd, LoadImageAndConvertToHBITMAP(bgImgs[bgIndex]), 0);break;
以上步驟就可以解決 WIN+TAB 的顯示問題了,如下圖所示:
但是仍然無法處理懸浮在縮略圖上顯示異常的問題,這是由于原窗口自定義了縮略圖后未定義實時預覽圖,導致原窗口無法正常顯示,也就導致了臨時窗口的預覽圖無法顯示,解決方法如下:
// 設置實時預覽圖
void SetWindowLivePreview(HWND hwnd, HBITMAP hBitmap) {// 不顯示原窗口的預覽圖, 這里設置負坐標POINT ptOffset;ptOffset.x = -1000;ptOffset.y = -2000;DwmSetIconicLivePreviewBitmap(hwnd, hBitmap, &ptOffset, 0);
}case WM_DWMSENDICONICLIVEPREVIEWBITMAP:SetWindowLivePreview(hWnd, LoadImageAndConvertToHBITMAP(bgImgs[bgIndex]));break;
通過以上設置就可以發現實時預覽圖也顯示正常了:
總結
本文簡單講解了如何在 Windows 下實現任務欄自定義按鈕和縮略圖,由于個人水平有限,示例代碼可能存在一些問題,歡迎一起交流討論。
參考文獻
- 一個體驗好的Windows 任務欄縮略圖開發心得