一种基于C语言开发的桌面快捷托盘

一种基于C语言开发的桌面快捷托盘

引言

我不喜欢电脑桌面上有太多图标,用上动态壁纸了桌面就是用来看的,,在网上找了一下,雨滴美化对我来说有些繁琐,BitDesk也是如此,对我来说没有的功能太多了,故自己写一个吧。

正文

现在Ai横行,再加之我没有开发Windows应用的经验,索性直接让ChatGPT生成了一个框架,如下:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <windows.h>
#include <shellapi.h>

#define WM_TRAYICON (WM_USER + 1) // 托盘图标消息ID
#define ID_TRAY_EXIT 1001 // 托盘菜单退出选项ID

HINSTANCE hInst;
HWND hWnd;
NOTIFYICONDATA nid;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateTrayIcon(HWND);
void RemoveTrayIcon();

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
hInst = hInstance;

MessageBox(NULL, "程序已启动!", "调试信息", MB_OK); // 调试信息

// 注册窗口类
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = "CustomTrayTool";

RegisterClass(&wc);

// 创建隐藏的窗口(用于托盘图标交互)
hWnd = CreateWindowEx(WS_EX_TOOLWINDOW, "CustomTrayTool", NULL, WS_POPUP,
CW_USEDEFAULT, CW_USEDEFAULT, 200, 50, NULL, NULL, hInstance, NULL);

// 创建托盘图标
CreateTrayIcon(hWnd);

// 进入消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

// 退出时移除托盘图标
RemoveTrayIcon();
return (int)msg.wParam;
}

// 窗口回调函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_TRAYICON: // 处理托盘图标事件
if (lParam == WM_RBUTTONUP) {
// 创建托盘右键菜单
HMENU hMenu = CreatePopupMenu();
AppendMenu(hMenu, MF_STRING, ID_TRAY_EXIT, "Exit");

// 获取鼠标位置并显示菜单
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(hWnd);
TrackPopupMenu(hMenu, TPM_BOTTOMALIGN | TPM_RIGHTALIGN, pt.x, pt.y, 0, hWnd, NULL);
DestroyMenu(hMenu);
}
break;
case WM_COMMAND: // 处理菜单点击事件
if (LOWORD(wParam) == ID_TRAY_EXIT) {
PostQuitMessage(0); // 退出程序
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

// 创建托盘图标
void CreateTrayIcon(HWND hWnd) {
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = hWnd;
nid.uID = 1;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
nid.uCallbackMessage = WM_TRAYICON; // 设置托盘图标的回调消息
nid.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 设置图标
strcpy(nid.szTip, "Tray Tool"); // 设置鼠标悬停提示
Shell_NotifyIcon(NIM_ADD, &nid); // 添加托盘图标
}

// 移除托盘图标
void RemoveTrayIcon() {
Shell_NotifyIcon(NIM_DELETE, &nid);
}

框架生成了,继续添加功能,要实现拖动窗口的效果,因为是无边框窗口所以要做这个功能。

就仅仅到这一步我的ChatGPT就限制使用了,难崩。

转而使用国产大模型DeepSeek。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <windows.h>  // 包含Windows API的头文件

#define WM_MOVEWINDOW (WM_USER + 1) // 自定义消息,用于窗口移动(未在本示例中使用)

// 窗口过程函数声明,用于处理窗口消息
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// 程序入口点
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
static TCHAR szAppName[] = TEXT("BorderlessWindow"); // 窗口类名
HWND hwnd; // 窗口句柄
MSG msg; // 消息结构
WNDCLASS wndclass; // 窗口类结构

// 设置窗口类的属性
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 窗口样式:水平和垂直变化时重绘
wndclass.lpfnWndProc = WndProc; // 指定窗口过程函数
wndclass.cbClsExtra = 0; // 类额外内存,未使用
wndclass.cbWndExtra = 0; // 窗口额外内存,未使用
wndclass.hInstance = hInstance; // 当前程序实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 加载默认应用程序图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // 加载默认箭头光标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 设置背景为白色
wndclass.lpszMenuName = NULL; // 无菜单
wndclass.lpszClassName = szAppName; // 设置窗口类名

// 注册窗口类
if (!RegisterClass(&wndclass)) {
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); // 注册失败时弹出错误提示
return 0;
}

// 创建窗口
hwnd = CreateWindow(
szAppName, // 窗口类名
TEXT("Borderless Window"), // 窗口标题
WS_POPUP, // 窗口样式:无边框
CW_USEDEFAULT, CW_USEDEFAULT, // 窗口初始位置(默认)
CW_USEDEFAULT, CW_USEDEFAULT, // 窗口初始大小(默认)
NULL, // 父窗口句柄(无)
NULL, // 菜单句柄(无)
hInstance, // 程序实例句柄
NULL // 附加参数(无)
);

// 显示窗口
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd); // 更新窗口,触发重绘

// 消息循环
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg); // 翻译消息(如将按键消息转换为字符消息)
DispatchMessage(&msg); // 将消息分发到窗口过程函数
}

return msg.wParam; // 返回程序退出码
}

// 窗口过程函数,处理窗口消息
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
static POINT ptLastMousePos; // 记录鼠标按下时的位置
static BOOL bDragging = FALSE; // 标记是否正在拖动窗口

switch (message) {
case WM_LBUTTONDOWN: // 鼠标左键按下
ptLastMousePos.x = LOWORD(lParam); // 获取鼠标按下的X坐标
ptLastMousePos.y = HIWORD(lParam); // 获取鼠标按下的Y坐标
bDragging = TRUE; // 开始拖动
SetCapture(hwnd); // 捕获鼠标输入,确保即使鼠标移出窗口也能接收消息
break;

case WM_LBUTTONUP: // 鼠标左键释放
bDragging = FALSE; // 停止拖动
ReleaseCapture(); // 释放鼠标捕获
break;

case WM_MOUSEMOVE: // 鼠标移动
if (bDragging) { // 如果正在拖动
POINT ptCurrentMousePos; // 当前鼠标位置
RECT rectWindow; // 窗口的当前位置和大小

ptCurrentMousePos.x = LOWORD(lParam); // 获取当前鼠标X坐标
ptCurrentMousePos.y = HIWORD(lParam); // 获取当前鼠标Y坐标

GetWindowRect(hwnd, &rectWindow); // 获取窗口的屏幕坐标

// 计算鼠标移动的偏移量
int dx = ptCurrentMousePos.x - ptLastMousePos.x;
int dy = ptCurrentMousePos.y - ptLastMousePos.y;

// 移动窗口
MoveWindow(
hwnd, // 窗口句柄
rectWindow.left + dx, // 新位置的X坐标
rectWindow.top + dy, // 新位置的Y坐标
rectWindow.right - rectWindow.left, // 窗口宽度
rectWindow.bottom - rectWindow.top, // 窗口高度
TRUE // 是否重绘窗口
);
}
break;

case WM_DESTROY: // 窗口销毁消息
PostQuitMessage(0); // 发送退出消息,结束消息循环
break;

default: // 其他未处理的消息
return DefWindowProc(hwnd, message, wParam, lParam); // 调用默认窗口过程处理
}

return 0; // 消息处理完毕
}

使用

1
gcc -Wall -Wextra -g3 opinUI.c -o output/opinUI.exe -lgdi32

编译。

不出意外的报错了。:(

反馈给DeepSeek,让把

1
2
ptLastMousePos.x = GET_X_LPARAM(lParam);
ptLastMousePos.y = GET_Y_LPARAM(lParam);

改成

1
2
ptLastMousePos.x = (short)LOWORD(lParam);  // 显式转为有符号数
ptLastMousePos.y = (short)HIWORD(lParam);

在编译,没报错了,但是有个终端一起打开了。

改一下编译指令

1
gcc -Wall -Wextra -g3 -lgdi32 -mwindows opinUI.c -o opinUI.exe

编译,这次可算成功了。

弹出了一个无边框的窗口

窗口弹出

继续添加,桌面侧边吸附的功能。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#define WINVER 0x0500
#define _WIN32_WINNT 0x0500
#include <windows.h>

#define SNAP_THRESHOLD 20 // 吸附敏感度(像素)

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(szCmdLine);

const TCHAR szAppName[] = TEXT("SnapWindow");
HWND hwnd;
MSG msg;
WNDCLASS wndclass = {0};

wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszClassName = szAppName;

if (!RegisterClass(&wndclass)) {
MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_ICONERROR);
return 0;
}

hwnd = CreateWindow(
szAppName,
TEXT("可吸附窗口 - 拖动试试"),
WS_POPUP,
CW_USEDEFAULT, CW_USEDEFAULT,
400, 300, // 初始窗口尺寸
NULL, NULL, hInstance, NULL
);

ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
static POINT ptLastMousePos;
static BOOL bDragging = FALSE;
static RECT rcWindow; // 存储窗口尺寸

switch (message) {
case WM_LBUTTONDOWN:
GetWindowRect(hwnd, &rcWindow);
ptLastMousePos.x = GET_X_LPARAM(lParam);
ptLastMousePos.y = GET_Y_LPARAM(lParam);
bDragging = TRUE;
SetCapture(hwnd);
break;

case WM_LBUTTONUP:
bDragging = FALSE;
ReleaseCapture();
break;

case WM_MOUSEMOVE:
if (bDragging) {
// 获取屏幕尺寸
const int screen_width = GetSystemMetrics(SM_CXSCREEN);
const int screen_height = GetSystemMetrics(SM_CYSCREEN);

// 计算新位置
POINT ptCurrent = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
int new_x = rcWindow.left + (ptCurrent.x - ptLastMousePos.x);
int new_y = rcWindow.top + (ptCurrent.y - ptLastMousePos.y);
const int window_width = rcWindow.right - rcWindow.left;
const int window_height = rcWindow.bottom - rcWindow.top;

// 水平吸附检测
if (new_x <= SNAP_THRESHOLD && new_x >= -SNAP_THRESHOLD) {
new_x = 0; // 吸附到左边缘
} else if ((new_x + window_width) >= (screen_width - SNAP_THRESHOLD)) {
new_x = screen_width - window_width; // 吸附到右边缘
}

// 垂直吸附检测
if (new_y <= SNAP_THRESHOLD && new_y >= -SNAP_THRESHOLD) {
new_y = 0; // 吸附到顶部
} else if ((new_y + window_height) >= (screen_height - SNAP_THRESHOLD)) {
new_y = screen_height - window_height; // 吸附到底部
}

// 更新窗口位置
SetWindowPos(
hwnd, NULL,
new_x, new_y,
0, 0,
SWP_NOSIZE | SWP_NOZORDER
);
}
break;

case WM_DESTROY:
PostQuitMessage(0);
break;

default:
return DefWindowProc(hwnd, message, wParam, lParam);
}

return 0;
}

编译运行,没报错,可喜可贺,:)

拖动试试,又出BUG了。让人很无语的BUG。

BUG

在拖动过程中窗口反复横跳。保留吧,一会在修。先试试怎么错按钮。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <windows.h>

#define SNAP_THRESHOLD 20
#define BUTTON_1 1001

int MOVE_SWITCH = 1;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(szCmdLine);


const TCHAR szAppName[] = TEXT("BorderlessWindow");
HWND hwnd;
MSG msg;
WNDCLASS wndclass = {0};

wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszClassName = szAppName;

if (!RegisterClass(&wndclass)) {
MessageBox(NULL, TEXT("Registration Failed!"), szAppName, MB_ICONERROR);
return 0;
}

hwnd = CreateWindow(
szAppName,
TEXT("Borderless Window"),
WS_POPUP,
CW_USEDEFAULT, CW_USEDEFAULT,
220, 600,
NULL, NULL, hInstance, NULL
);

CreateWindow(
TEXT("BUTTON"),
MOVE_SWITCH ? TEXT("开启") : TEXT("关闭"),
WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
10,10,100,30,
hwnd,
(HMENU)BUTTON_1,
hInstance,
NULL
);

ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
static POINT ptLastMousePos;
static BOOL bDragging = FALSE;
static RECT rcWindow;

switch (message) {
case WM_LBUTTONDOWN:// 替换 WM_LBUTTONDOWN 和 WM_MOUSEMOVE 中的坐标获取代码

if (MOVE_SWITCH == 1){
bDragging = TRUE;
SetCapture(hwnd);
ptLastMousePos.x = (short)LOWORD(lParam);
ptLastMousePos.y = (short)HIWORD(lParam);
// ptLastMousePos.x = GET_X_LPARAM(lParam);
// ptLastMousePos.y = GET_Y_LPARAM(lParam);
};
break;

case WM_LBUTTONUP:
bDragging = FALSE;
ReleaseCapture();
break;

case WM_MOUSEMOVE:
if (bDragging && MOVE_SWITCH == 1) {
const int screen_width = GetSystemMetrics(SM_CXSCREEN);
// const int screen_height = GetSystemMetrics(SM_CYSCREEN);

POINT ptCurrent = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
//POINT ptCurrent = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
int new_x = rcWindow.left + (ptCurrent.x - ptLastMousePos.x);
int new_y = rcWindow.top + (ptCurrent.y - ptLastMousePos.y);
const int window_width = rcWindow.right - rcWindow.left;
// const int window_height = rcWindow.bottom - rcWindow.top;

if (new_x <= SNAP_THRESHOLD && new_x >= -SNAP_THRESHOLD) { // 如果接近左边缘
new_x = 0; // 吸附到左边缘
} else if ((new_x + window_width) >= (screen_width - SNAP_THRESHOLD)) { // 如果接近右边缘
new_x = screen_width - window_width; // 吸附到右边缘
}

if (new_y <= SNAP_THRESHOLD && new_y >= -SNAP_THRESHOLD) { // 如果接近上边缘
new_y = 0; // 吸附到上边缘
}

SetWindowPos(
hwnd, NULL,
new_x, new_y, // 新位置的 X 和 Y 坐标
0, 0, // 不改变窗口大小
SWP_NOSIZE | SWP_NOZORDER // 标志:不调整大小,不改变 Z 顺序
);
}
break;

case WM_COMMAND:
if (LOWORD(wParam) == BUTTON_1){
MOVE_SWITCH = !MOVE_SWITCH; // 切换拖动功能状态
// 更新按钮文本
SetWindowText(GetDlgItem(hwnd, BUTTON_1), MOVE_SWITCH ? TEXT("开启拖动") : TEXT("关闭拖动"));
};
break;
case WM_DESTROY:
PostQuitMessage(0);
break;

default:
return DefWindowProc(hwnd, message, wParam, lParam);
}

return 0;
}

成功!!注意按钮的类名应为 TEXT("BUTTON")不要用错了。

按钮