相信带伙都用过或看过鲁大师或者类似鲁大师这种东西,讲道理不是我care它,鲁大师**都不用。
但是有个功能还是比较好的,他会在任务栏实时显示温度之类的资源信息。 最近新加了内存,想偶尔看看电脑资源占用,总不能老打开任务管理器吧....
实现思路
首先得想法就是,创建窗口,在窗口中绘制箭头效果做网速显示也好,绘制文字显示资源占用也好都好做。关键问题就是如何在任务栏创建出来窗口,这方面winapi并没有给出相应的接口。
我去查了一下,搜到了一个叫DeskBand的东西
气人的是win11把他移除了。
最终实现方法是创建一个POPUP的窗口,将该窗口的父窗口设置为任务栏。这个窗户就可以在任务栏的指定位置显示了,注意z轴关系,不要让任务栏把他遮盖了。
代码
目前代码只创建了窗口并让窗口显示在任务栏上,绘制有时间再写。
窗口类的实现好像是参考了B站一个搬运视频,一个讲DX11的大佬的写法,核心是将this指针在创建窗口时传给lparam,以下是winapi对lparam参数解释
[in, optional] lpParam
类型: LPVOID
指向要通过 CREATESTRUCT 结构传递到窗口的值的指针, (WM_CREATE消息的 lParam 参数指向的 lpCreateParams 成员) 。 此消息在返回之前由此函数发送到创建的窗口。
窗口类头文件:
class ZdsjWindow
{
public:
ZdsjWindow(HWND parent, HINSTANCE hinstance);
ZdsjWindow(const ZdsjWindow& zdsj_window) = delete;
ZdsjWindow& operator=(const ZdsjWindow& zdsj_window) = delete;
~ZdsjWindow();
HWND getHwnd();
private:
// 注册窗口类
bool registerClass(HINSTANCE hinstance);
// 创建窗口
bool createWindow(HINSTANCE hinstance);
// 计算宽度
int getWidth(const RECT* rect);
// 计算高度
int getHeight(const RECT* rect);
static LRESULT CALLBACK HandelMsgSetUp(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK HandelMsgForward(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK HandelMsg(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam);
const wchar_t* _windowName = L"SourceObserve";
const wchar_t* _className = L"SourceObserve";
HWND _hwnd = NULL;
RECT* _rect = nullptr;
TaskTray* _taskTray = nullptr;
Menu* _menu = nullptr;
};
窗口类实现:
ZdsjWindow::ZdsjWindow(HWND parent, HINSTANCE hinstance)
{
// 设置窗口大小
RECT rect = {};
GetWindowRect(parent, &rect);
this->_rect = new RECT;
this->_rect->left = rect.left + 100;
this->_rect->top = 0;
this->_rect->right = rect.left + 100 + 100;
this->_rect->bottom = this->_rect->top + this->getHeight(&rect);
// 1. 设置窗口类并注册
if(!this->registerClass(hinstance))
{
exit(REGISTER_ERROR_EXIT);
}
// 2. 创建窗口
if(!this->createWindow(hinstance))
{
exit(CREATEWINDOW_ERROR_EXIT);
}
// ShowWindow(this->_hwnd, 1);
// 3. 设置系统托盘
this->_menu = new Menu();
this->_taskTray = new TaskTray(this->_hwnd, this->_menu->getMenu());
// 4. 设置父窗口并显示
SetParent(this->_hwnd, parent);
SetWindowPos(this->_hwnd, HWND_TOP, this->_rect->left, this->_rect->top,
this->getWidth(this->_rect), this->getHeight(this->_rect), SWP_DEFERERASE);
SetLayeredWindowAttributes(this->_hwnd, NULL, 255, LWA_ALPHA);
ShowWindowAsync(this->_hwnd, 1);
}
ZdsjWindow::~ZdsjWindow()
{
UnregisterClass(this->_className, NULL);
delete this->_rect;
if(this->_hwnd != NULL)
{
DestroyWindow(this->_hwnd);
}
}
HWND ZdsjWindow::getHwnd()
{
return this->_hwnd;
}
bool ZdsjWindow::registerClass(HINSTANCE hinstance)
{
WNDCLASSEXW wndclass = {};
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = HandelMsgSetUp;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hinstance;
wndclass.hIcon = NULL;
// MAKEINTRESOURCE()
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
// wndclass.hCursor = NULL;
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = this->_className;
return RegisterClassExW(&wndclass);
}
bool ZdsjWindow::createWindow(HINSTANCE hinstance)
{
this->_hwnd = CreateWindowExW(
WS_EX_LAYERED,
this->_className,
this->_windowName,
WS_POPUP,
this->_rect->left,
this->_rect->top,
this->getWidth(this->_rect),
this->getHeight(this->_rect),
NULL,
NULL,
hinstance,
this
);
return this->_hwnd != NULL;
}
int ZdsjWindow::getWidth(const RECT* rect)
{
return rect->right - rect->left;
}
int ZdsjWindow::getHeight(const RECT* rect)
{
return rect->bottom - rect->top;
}
LRESULT ZdsjWindow::HandelMsgSetUp(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg == WM_NCCREATE) {
// 创建窗口时触发
// lParam 指向 CREATESTRUCT 结构的指针,其中包含有关正在创建的窗口的信息。
// CREATESTRUCT.lpCreateParams 也就是CreateWindowExW的LPVOID指向的指针
const CREATESTRUCTW* const pCreate = reinterpret_cast<CREATESTRUCTW*>(lParam);
ZdsjWindow* const pwnd = static_cast<ZdsjWindow*>(pCreate->lpCreateParams);
// 将执行CreateWindowExW的this指针存入窗口
SetWindowLongPtrW(handle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pwnd));
// 不能通过WINAPI调用成员函数, 在将此函数设置为消息处理函数后,被当作WINAPI了
// 将消息转发到静态方法
// GWLP_WNDPROC设置窗口过程的新地址
SetWindowLongPtrW(handle, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&ZdsjWindow::HandelMsgForward));
return pwnd->HandelMsg(handle, msg, wParam, lParam);
}
return DefWindowProc(handle, msg, wParam, lParam);
}
LRESULT ZdsjWindow::HandelMsgForward(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam)
{
ZdsjWindow* const pwnd = reinterpret_cast<ZdsjWindow*>(GetWindowLongPtrW(handle, GWLP_USERDATA));
return pwnd->HandelMsg(handle, msg, wParam, lParam);
}
LRESULT ZdsjWindow::HandelMsg(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
WORD event;
switch (msg)
{
case WM_SETCURSOR:
LOWORD(lParam);
event = HIWORD(lParam);
switch (event)
{
case WM_MOUSEMOVE:
// 目前只能move触发
break;
case WM_MOUSEHOVER :
// xPos = GET_X_LPARAM(lParam);
// yPos = GET_Y_LPARAM(lParam);
// std::cout << "x: " << xPos << " y: " << yPos << std::endl;
break;
case WM_RBUTTONDBLCLK:
// std::cout << "rclick" << std::endl;
break;
}
break;
case WM_SIZE:
//
// width = LOWORD(lParam);
// height = HIWORD(lParam);
// std::cout << "size" << " width: " << width << " height: " << height << std::endl;
break;
case WM_PAINT:
break;
case WM_NCPAINT:
break;
case WM_CREATE://窗口创建时候的消息.
break;
case WM_USER://连续使用该程序时候的消息.
break;
case WM_DESTROY://窗口销毁时候的消息.
PostQuitMessage(0);
break;
default:
break;
}
if (this->_menu != nullptr) {
switch (msg)
{
case WM_COMMAND:
this->_menu->dealMenuMessage(handle, msg, wParam, lParam);
break;
}
}
// 处理系统托盘消息
if (this->_taskTray != nullptr) {
this->_taskTray->dealTaskBarMessage(handle, msg, wParam, lParam);
}
return DefWindowProc(handle, msg, wParam, lParam);
}
其中关键点就是setParent,设置父窗口。父窗口,也就是任务栏句柄获取方式:
HWND parent = FindWindowW(L"Shell_TrayWnd", NULL);
这个类名和窗口名可以通过vs自带spy++或者其他工具获取,但好像获取的与这个不太一样。
需要注意的是setwindowpos里设置z轴,hWndInsertAfter参数。
[in, optional] hWndInsertAfter
类型:HWND
在 Z 顺序中定位窗口之前窗口的句柄。 此参数必须是窗口句柄或以下值之一。
文件里面的taskTray和menu是系统托盘区的,就是任务栏右边类似qq那种小图标,右键退出之类的。
后续准备通过dx11绘制具体资源数据,放在下篇细说吧,这个还没有传github后续应该会传的。