窗口(window)是我们在windows编程中接触最频繁的对象。用户对窗口的操作(点击,键盘输入)都是以消息的形式传递给窗口,窗口间也是通过消息来和其他窗口进行通信。windows程序设计是以事件驱动方式的程序设计模式,主要是基于消息的。所以理解消息十分重要。
每一个windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序创建的窗口的消息。
当消息投放到消息队列中后,应用程序则通过一个消息循环不断地从消息队列中取出消息,并进行响应。这种消息机制,就是windows程序运行的机制。
这里我搬来了windows程序设计第5版这本书中的示例代码,感兴趣的可以复制到本地,运行下,看下效果。
通过这段代码,我们可以大概了解下windows编程。
/*------------------------------------------------------------
HELLOWIN.C -- Displays "Hello, Windows 98!" in client area
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
case WM_CREATE:
return 0;
case WM_CHAR: // 按下键盘后,弹出对话框;
{
char szChar[20];
sprintf(szChar, "char is %d", wParam);
MessageBox(hwnd, szChar, "hello", MB_OK);
return 0;
}
case WM_PAINT: // 绘制主窗体
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, TEXT("Hello, Windows 98!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
case WM_LBUTTONDOWN: // 左键按下弹出对话框
{
MessageBox(hwnd, "mouse click", "test", MB_OK);
HDC hDC = GetDC(hwnd);
const char *str = "hello world";
TextOut(hDC, 0, 50, str, strlen(str));
ReleaseDC(hwnd, hDC);
return 0;
}
case WM_CLOSE: // 退出确认;
{
if (IDYES == MessageBox(hwnd, "你是否要退出?", "ddd", MB_YESNO))
DestroyWindow(hwnd);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("HelloWin");
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, // window class name
TEXT("The Hello Program"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL); // creation parameters
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
在windows编程中,我们的入口是WinMain
函数,作为应用程序的入口点。函数参数hInstance
为这个窗口的实例句柄,可以理解为是一个标识(资源),具有唯一性,由操作系统分配。通过这个标识,操作系统对其进行管理。
窗口总是基于窗口类
来创建。可以理解窗口类
其实就是要创建的窗口的配置,操作系统按照这个配置,创建出窗口。这里给出窗口类的定义,及各个字段的含义:
typedef struct _WNDCLASS {
UINT style;
//指定这一类型窗口的样式,CS_HREDRAW水平重画,CS_VREDRAW垂直重画,CS_NOCLOSE 禁用Close命令,这将导致窗口没有关闭按钮。CS_DBLCLKS当用户在窗口中双击时,向窗口过程发送鼠标双击消息。CS(Class Style)
WNDPROC lpfnWndProc;
//函数指针,指向窗口过程函数,窗口过程函数就是一个回调函数。一个Windows程序可以包包含多个窗口过程函数,一个窗口过程总是与某一个特定的窗口相关联(通过本成员变量指定),基于该窗口类创建的窗口使用同一窗口过程。
int cbClsExtra;
//可以为窗口类分配一个附加的内存空间,由属于这种窗口类的所有窗口所共享,我们一般将这个参数设置为0。
int cbWndExtra;
//为窗口分配一个附加内存空间,应用程序可以用这部分内存存储窗口的特有的数据。初始化一般为0。如果应用程序用WNDCLASS结构注册对话框(用资源文件中的CLASS伪指令创建)必须给DLGWINDOWEXTRA设置这个成员。
HINSTANCE hInstance;
//指令包含窗口过程的程序的实例句柄。
HICON hIcon;
//指定窗口类的图标句柄。如果这个成员为NULL,那么系统提供一个默认的图标。在为hIcon变量赋值时可以调用LoadIcon函数加载一个图标资源,返回一个图标句柄。
HCURSOR hCursor;
//为光标变量赋值时,可以用LoadCusrsor加载一个光标资源,返回光标句柄。用法同LoadIcon
HBRUSH hbrBackground;
//指定窗口类的画刷句柄,当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口背景。,既可以指定画刷句柄,也可以指定标准系统颜色值。GetStockObject得到系统标准刷。还可以获取画笔、字体和调色板句柄,因为反回多种资源对像句柄,使用时要根据具体情况,强制转换。
LPCTSTR lpszMenuName;
//指定菜单资源的名字。如果为NULL那么基于这个窗口类创建的窗口将没有菜单。
LPCTSTR lpszClassName;
//指定窗口类的名字。
} WNDCLASS;
RegisterClass
函数的作用是通知系统,你要定义一个新的窗体类型,然后把这个类型记录到系统里面,以后你就可以使用CreateWindow
来创建一个基于此类型的窗体。基于此类型的窗体都具有相同的属性,比如,背景色,光标,图标等等。
RegisterClass
函数的作用是定义一个窗体类,相对于C++中的class概念,而CreateWindow
这个函数是定义基于这个类型的对象,相对于C++中的对象概念。
注释为 window class name的参数是szAppName
,其参数包含"HelloWin",即刚刚注册的窗口类的名称。我们所要创建的窗口正是通过这种方式和窗口类建立了关联。如果名字不一致,窗口是无法建立的。
hwnd = CreateWindow(szAppName, // window class name
TEXT("The Hello Program"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL); // creation parameters
函数ShowWindow(hwnd, iCmdShow)
用于将窗口显示在屏幕中。在调用UpdateWindow(hwnd);
后新建的窗口在屏幕中便完全可见了。此时,程序必须能够接收来自用户的键盘和鼠标的输入,这里通过循环,应用程序来进行消息的接收:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
在windows程序中,消息是由MSG结构体来表示的。MSG结构体定义如下:
typedef struct tagMSG {
HWND hwnd;
//消息所属的窗口
UINT message;
//消息的标识符,消息由数值表识的不便记忆,所以定义为宏WM_XXX(WM是windows message的缩写)XXX对定消息英文大写,例如鼠标左键按下消息WM_LBUTTONDOWN,键盘按下消息WM_KEYDOWN,字符消息就是WM_CHAR。
WPARAM wParam;
LPARAM lParam;
//wParam和lParam用于指定消息的附加信息。例如收到一个字符消息时,message的成员变量就是WM_CHAR,但用户到底输入的到底什么字符,就由wParam和lParam来说明。
DWORD time;
//消息投递到消息队列的时间。
POINT pt;
//鼠标的当前位置。
} MSG;
在定义窗口类时,注册的回调函数LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
决定了窗口客户区的显示内容以及窗口如何对用户的输入做出响应。代码很简单,不再赘述。
至此我们就粗浅地了解了windows编程的知识,这是后面理解duilib的基础。
参考文献:
- windows程序设计第5版 (美 Charles Petzold著,方敏 张胜 梁路平 赵勇等译) 第三章
- B站孙鑫视频
- Windows程序内部运行机制_subleo的专栏-CSDN博客
- 关于RegisterClass和CreateWindow - 菊花也是花 - 博客园 (cnblogs.com)