С появлением Windows 7 в системе появилась полезная фишка, позволяющая размещать элементы управления в панели задач, а именно в превью, которое высвечивается при наведении на элемент в панели. Однако, редко встретишь приложение, которое её использует. Из множества приложений, которыми я пользуюсь, на ум приходит только одно - Media Player Classic. Вот так выглядят элементы управления для него:
Так сложилось, что я люблю слушать "полу-радио", в частности, Last.fm. Но, к сожалению, в клиенте Last.fm нет поддержки этих модных кнопочек, поэтому мне захотелось добавить их в него собственноручно. Перечень действий, которые необходимо для этого, примерно следующий:
1. Находим хендл основного окна клиента.
2. Добавляем элементы управления.
3. Ставим свой обработчик оконных сообщений, в котором задаем поведение элементов управления.
Итак, приступим. Для начала, инклюды и файл ресурсов:
1 2 3 4 5 6 7 8 |
#include <Windows.h> //В этом файле определены методы, позволяющие добавить нужные элементы управления #include <Shobjidl.h> #include <Strsafe.h> #include "resource.h" //Необходима, т.к. используются COM-интерфейсы #pragma comment(lib, "Comctl32") |
Файл ресурсов и несколько констант, которые понадобятся дальше:
1 2 |
//В buttons.bmp хранятся пиктограммы кнопочек BUTTONS_BMP BITMAP "buttons.bmp" |
1 2 3 4 |
#define BUTTONS_BMP 101 #define BUTTON1 31337 #define BUTTON2 31338 #define BUTTON3 31339 |
Теперь перейдем к основному коду. Во-первых, объявим несколько глобальных переменных и один полезный макрос:
1 2 3 4 5 |
#define MAKEDWORD(lo, hi) ((DWORD)(((WORD)(lo)) | ((DWORD)((WORD)(hi))) << 16)) HINSTANCE ghInstance; HWND ghWnd; WNDPROC old_proc; |
Далее реализуем функцию, которая будет "нажимать" кнопки по заданным координатам:
1 2 3 4 5 |
void ClickAt(HWND hWnd, int x, int y) { SendMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKEDWORD(x, y)); SendMessage(hWnd, WM_LBUTTONUP, 0, MAKEDWORD(x, y)); } |
Почему именно эмуляция нажатия? Дело в том, что клиент Last.fm написан с использованием Qt, а там своеобразная модель взаимодействия элементов интерфейса, отличная от стандартной виндовой, поэтому вместо того, чтобы искать хендлы кнопок и посылать команды им, я упростил себе задачу и остановился на этом варианте. Тем более, положение элементов управления в окне (для которых мы будем делать "бинды") не меняется при ресайзе.
Пришла очередь обработчика оконных сообщений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { if(msg == WM_COMMAND) { //Определяем, какая кнопка была нажата int const ButtonID = LOWORD(wParam); switch(ButtonID) { //Запустить/Остановить проигрывание case BUTTON1: ClickAt(hWnd, 447, 72); break; //Пропустить композицию case BUTTON2: ClickAt(hWnd, 514, 72); break; //Добавить композицию в список любимых case BUTTON3: ClickAt(hWnd, 318, 72); break; } } return CallWindowProc(old_proc, hWnd, msg, wParam, lParam); } |
Идентификатора кнопки получается из wParam, так как того требует MSDN.
Теперь самая главная функция, которая добавит элементы управления для окна с указанным хендлом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
HRESULT CreateThumbnailToolbar(HWND hWnd) { ITaskbarList4 *pTaskbarList; HRESULT hr = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pTaskbarList)); if (SUCCEEDED(hr)) { //Инициализируем таскбар hr = pTaskbarList->HrInit(); if (SUCCEEDED(hr)) { //Формируем список изображений из ресурса, при этом размер каждого элемента списка будет 20x20 пикселей HIMAGELIST himl = ImageList_LoadImage(ghInstance, MAKEINTRESOURCE(BUTTONS_BMP), 20, 0, RGB(255,0,255), IMAGE_BITMAP, LR_CREATEDIBSECTION); if (himl) { //Задаем список изображений, которые будут использоваться для кнопок hr = pTaskbarList->ThumbBarSetImageList(hWnd, himl); if (SUCCEEDED(hr)) { THUMBBUTTON buttons[3] = {}; //Задаем параметры для трех кнопок buttons[0].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; buttons[0].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK; buttons[0].iId = BUTTON1; buttons[0].iBitmap = 0; StringCchCopy(buttons[0].szTip, ARRAYSIZE(buttons[0].szTip), L"Start/Stop"); buttons[1].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; buttons[1].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK; buttons[1].iId = BUTTON2; buttons[1].iBitmap = 1; StringCchCopy(buttons[1].szTip, ARRAYSIZE(buttons[1].szTip), L"Skip"); buttons[2].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; buttons[2].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK; buttons[2].iId = BUTTON3; buttons[2].iBitmap = 2; StringCchCopy(buttons[2].szTip, ARRAYSIZE(buttons[2].szTip), L"Like"); //Добавляем элементы управления hr = pTaskbarList->ThumbBarAddButtons(hWnd, ARRAYSIZE(buttons), buttons); } ImageList_Destroy(himl); } } pTaskbarList->Release(); } return hr; } |
Основной этап пройден, осталось несколько мелких функций. Функция поиска хендла главного окна процесса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) { //Проверяем установлен ли стиль WS_VISIBLE if((GetWindowLong(hWnd, GWL_STYLE) & WS_VISIBLE)) { DWORD pid; GetWindowThreadProcessId(hWnd, &pid); //Сравниваем id текущего процесса (передается в lParam) и id процесса полученного хендла if(pid == lParam) { ghWnd = hWnd; return FALSE; } } return TRUE; } |
Оставшаяся часть кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
void ThreadProc() { //Ждем создания основного окна Sleep(5000); //Инициализируем COM-библиотеку CoInitialize(NULL); //Ищем основное окно EnumWindows(&EnumWindowsProc, GetCurrentProcessId()); //Создаем тулбар для найденного хендла CreateThumbnailToolbar(ghWnd); //Сохраняем адрес старого обработчика оконных сообщений и заменяем его на свой old_proc = (WNDPROC)GetWindowLong(ghWnd, GWL_WNDPROC); SetWindowLong(ghWnd, GWL_WNDPROC, (LONG)WndProc); ExitThread(0); } BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID reserved) { if(dwReason == DLL_PROCESS_ATTACH) { ghInstance = hInstance; //Запускаем поток, так как необходимо дать приложению загрузиться и создать окно, что произойдет только после загрузки DLL CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); } else if(dwReason == DLL_PROCESS_DETACH) { CoUninitialize(); } return TRUE; } //Пустая экспортируемая функция, которую мы добавим в импорт к LastFM.exe __declspec(dllexport) void __cdecl dummy(void) {}; |
Теперь у нас есть готовая DLL, которую можно добавить в импорт к клиенту и получить желаемые элементы управления. В качестве иконок для элементов управления я использовал оригинальные изображения из клиента. Извлечь их довольно просто:
1. Смотрим где находится секция данных, например, с помощью CFF Explorer.
2. Пролистываем секцию в поисках заголовков стандартных изображений (в нашем случае PNG). И разрезаем на отдельные файлы, например, с помощью WinHex.
Теперь из них формируем файл размером 60х20 пикселей, и шаблон для кнопок готов. В результате получится примерно так:
Исходные коды и скомпилированная DLL: скачать
круто конечно, но хотелось бы что то про мбр+стилинг файлов =)
А как добавить dll в приложение? Просто положить в папку не помогло
Как добавить dll в импорты описано, например, в предыдущей статье
У меня от таких статей прям вдохновение и ощущение прикосновения к волшебству.
В некоторых приложениях под семерку, в которых в процессе работы используется ProgressBar, прогресс также отображается прямо на кнопке приложения на панели задач (кнопка заполняется зелёным). Не знаешь, как добиться такого эффекта в своём приложении?
http://msdn.microsoft.com/en-us/library/dd391698%28v=VS.85%29.aspx
Спасибо!
Импортировал, но кнопки не работают почему-то :(
Не работают в смысле не отображаются или при нажатии ничего не происходит?
Если свернут клиент, то при нажатии ничего не происходит. Если развернут - работает.
Попробуй вариант отсюда http://kaimi.io/2011/10/lastfm-taskbar-controls-refining/
Не то, использовать бы стандартные компоненты Windows
Чего?