让我们与星光探索者一起,探索Windows吧!
使用计时器
计时器是Windows的一种资源。定时器每隔一段时间,会产生WM_TIMER消息 。WM_TIMER附带的两个信息wParam, lParam分别是计时器的id, 计时器到点的处理函数。定时器的精度是毫秒,但是准确度不好。也就是说如果设置间隔1000ms,不一定会在1000ms后刚好产生消息。但是误差不会这么大,最多也就几毫秒。
要使用计时器,需要用SetTimer函数创建定时器。SetTimer的定义如下:
1 2 3 4 5 6
| UINT_PTR SetTimer( HWND hWnd, UINT_PTR nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc );
|
这个函数返回创建的计时器ID,如果创建失败了返回0。如果lpTimerFunc
传的是NULL,计时器的时间到了,就将发送WM_TIMER消息通知关联的窗口过程函数处理,否则就不发WM_TIMER消息,直接调用lpTimerFunc
处理。计时器依赖于消息循环,也就是要使用计时器,就需要消息循环。
TIMERPROC 是计时器回调函数的类型,因此计时器回调函数lpTimeProc因定义为:
1 2 3 4 5 6
| void CALLBACK lpTimerProc( HWND hWnd, UINT nMsg, UINT_PTR uTImerid, DWORD dwTime );
|
计时器是系统资源,销毁计时器使用KillTimer 函数 。
1 2 3 4
| BOOL KillTimer( HWND hWnd, UINT_PTR uIDEvent );
|
示例代码:创建两个计时器,时间间隔分别为1000毫秒和2000毫秒,每调一次将打印一些字符串。
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
|
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <iostream>
#pragma comment(lib, "user32.lib") #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "kernel32.lib")
LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { static const TCHAR TITLE[] = TEXT("window"); static const TCHAR CLASSNAME[] = TEXT("MyStruct");
WNDCLASS wc{0}; wc.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hInstance = hInstance; wc.lpfnWndProc = WinProc; wc.lpszClassName = CLASSNAME; wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx( 0,CLASSNAME, TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
MSG msg{0}; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
return 0; }
LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static UINT_PTR timer1, timer2; switch (uMsg) { case WM_CLOSE: FreeConsole(); KillTimer(hwnd, timer1); KillTimer(hwnd, timer2); PostQuitMessage(0); return 0; case WM_CREATE: AllocConsole(); freopen("CONOUT$", "w+t", stdout); timer1 = SetTimer(hwnd, 1, 1000, NULL); timer2 = SetTimer(hwnd, 2, 2000, NULL); case WM_TIMER: if (wParam == timer1) { printf("timer1\n"); return 0; } if (wParam == timer2) { printf("timer2\n"); return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
|
使用菜单资源
我们创建窗口时很想让这个窗口显示自己的东西,那么我们应该怎么做呢?
星光探索者前面的说过有个资源编译器rc.exe,可以编译资源文件。首先我们先探索一下如何添加资源吧!
对准想添加资源的项目右键,弹出下面的菜单。依次点击添加(Add),资源(Resource)即可。
然后弹出这样的对话框,选择菜单(Menu),然后点击新建(New)
然后将会在Visual Studio弹出可视化的编辑器。相信不用星光探索者多说,各位小伙伴都会摸索的出来如何添加菜单项吧。
之后在你的项目管理器会添加一个resource.h(可以改名)头文件,和拓展名为.rc的资源文件。资源文件是一种用脚本语言书写的代码文件,当然如果小伙伴乐意,可手写资源文件。在编译过程中,资源文件会被资源编译器(在VC++是rc.exe)编译成.rsc的目标文件,进而通过链接器(在VC++是link.exe)编译成最终文件。
资源创建了,如何使用呢?刚才添加的resource.h文件定义了那些ID标识符。在打开的资源管理器(Resource View)中,选中添加的菜单,然后右键,点击(Resource Symbol)即可查看添加的资源的id。星光探索者这里创建的菜单ID为IDC_MENU1。当然也可以直接查看resource.h头文件
还记得创建窗口类时的WNDCLASS结构体吗?那里有一个lpszMenuName属性,给他赋值就可以使用我们添加的菜单资源了。lpszMenuName需要LPCTSTR的类型,资源文件定义的ID都是数值,我们只需进行强制转换即可。或者CreateWindowEx的第10个参数也是叫传菜单句柄的,也可以选择在那里使用菜单资源。
既然有了菜单,我们就应该处理菜单的相关消息。处理菜单消息应该处理WM_COMMAND消息。当窗口内的控件(如按钮,文本等)被点击时,或菜单项被点击时,或窗口标题栏那些按钮(如最小化)被点击时产生此消息。当菜单项被点击时,wParam的高字节为0,低字节为点击的菜单项ID,菜单项ID之前已探索过如何找到。
于是我们写出像下面这样的代码来使用菜单资源:
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
| #define _CRT_SECURE_NO_WARNINGS #include "resource.h" #include <windows.h>
#pragma comment(lib, "user32.lib") #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "kernel32.lib")
LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { static const TCHAR TITLE[] = TEXT("window"); static const TCHAR CLASSNAME[] = TEXT("MyStruct");
WNDCLASS wc{0}; wc.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hInstance = hInstance; wc.lpfnWndProc = WinProc; wc.lpszClassName = CLASSNAME; wc.lpszMenuName = (LPCWSTR)IDR_MENU1; wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx( 0,CLASSNAME, TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
MSG msg{0}; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
return 0; }
LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CLOSE: PostQuitMessage(0); return 0; case WM_COMMAND: switch (LOWORD(wParam)) { case ID_NEW: MessageBox(hwnd, TEXT("点击新建选项"), TEXT("星光探索者"), MB_OK); break; case ID_OPEN: MessageBox(hwnd, TEXT("点击打开选项"), TEXT("星光探索者"), MB_OK); break; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
|
运行程序,点击菜单的新建选项,将出现这样的效果:
右键菜单实现
用过办公软件和浏览器的小伙伴都会体验过,鼠标右键会弹出一个菜单。那么我们应该如何实现呢?
直接说结论:我们使用TrackPopupMenu 函数可以弹出一个快捷菜单,这就是我们期望的。TrackPopupMenu是阻塞函数,直到菜单消失才会返回。
1 2 3 4 5 6 7 8 9
| BOOL TrackPopupMenu( HMENU hMenu, UINT uFlags, int x, int y, int nReserved, HWND hWnd, const RECT *prcRect );
|
然后什么时候弹出呢?我们可以通过处理WM_RBUTTONDOWN(鼠标右键按下)消息来实现,可以是可以。但是事实告诉我们微软提供了WM_CONTEXTMENU消息,这样更便捷,这是上下文菜单的消息。正好可以使用右键菜单。
此消息附带的两个参数如下:
添加图标和光标资源
和前面添加菜单资源一样,但是如果我们有现成的,在弹出这个对话框时,就可以点击导入(Import)使用现有资源。这个步骤相信各位摸索都可以摸索出来。
然后再调用LoadIcon,LoadCursor即可。
本期到此结束,下期看星光探索者探索Windows绘图编程和对话框