图形用户界面之---窗口:
主函数一般是:
/*************************************************************************
* WinMain
* 功能:程序入口点,调用InitApplication,InitInstance,进行消息循环
* 参数:hinstance---应用程序本次运行实例
* hPrevInstance---应用程序之前的实例,始终为NULL
* lpCmdLine---命令行参数
* nCmdShow---窗口显示方式,如SW_SHOW
* 返回值:失败返回FALSE
************************************************************************/
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
BOOL fGotMessage;
if(!InitApplication(hinstance)) //注册窗口类
{
return FALSE;
}
if(!InitInstance(hinstance, nCmdShow)) //创建窗口和控件子窗口
{
return FALSE;
}
//消息循环
while((fGotMessage=GetMessage(&msg, (HWND)NULL, 0, 0)) != 0
&& fGotMessage != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
UNREFERENCED_PARAMETER(lpCmdLine);
}
1)注册窗口类
任何一个窗口都必须属于一个窗口类,同一个类的窗口具有同样的窗口消息处理过程。Windows API函数RegisterClass和RegisterClassEx可以完成窗口类的注册:
ATOM WINAPI RegisterClass(
__in const WNDCLASS *lpWndClass
);
ATOM WINAPI RegisterClassEx(
__in const WNDCLASSEX *lpwcx
);
这两个函数分别使用了WNDCLASS和WNDCLASSEX结构作为参数,其中WNDCLASS定义如下:
typedef struct tagWNDCLASS {
UINT style; //窗口样式,通过系统预先定义的一些常量来设置窗口的样子
WNDPROC lpfnWndProc; //消息处理函数
int cbClsExtra; //附加窗口类内存
int cbWndExtra; //附加窗口内存
HINSTANCE hInstance; //赋值为应用程序的实例
HICON hIcon; //窗口的图标样式
HCURSOR hCursor; //窗口的鼠标样式
HBRUSH hbrBackground; //窗口的背景画刷,如COLOR_BACKGROUND、COLOR_WINDOW.
LPCTSTR lpszMenuName; //菜单名
LPCTSTR lpszClassName; //新建类的类名,在创建窗口时需要指定窗口所属类的类名
} WNDCLASS, *PWNDCLASS;
WNDCLASSEX结构定义如下:
typedef struct tagWNDCLASSEX {
UINT cbSize; //WNDCLASSEX结构的大小
UINT style; //窗口样式
WNDPROC lpfnWndProc; //窗口消息处理函数
int cbClsExtra; //附加窗口类内存
int cbWndExtra; //附加窗口内存
HINSTANCE hInstance; //应用程序实例
HICON hIcon; //窗口图标
HCURSOR hCursor; //窗口鼠标
HBRUSH hbrBackground; //背景画刷
LPCTSTR lpszMenuName; //菜单资源
LPCTSTR lpszClassName; //窗口类名
HICON hIconSm; //小图标
} WNDCLASSEX, *PWNDCLASSEX;
实例代码中InitApplication函数注册了窗口类:
/*************************************************************
* BOOL InitApplication(HINSTANCE hinstance)
* 功能:注册主窗口类
* 参数:hinstance,应用程序本次运行实例
* 返回值:是否成功
*************************************************************/
BOOL InitApplication(HINSTANCE hinstance)
{
//使用RegisterClassEx
WNDCLASSEX wcx;
//填充结构
wcx.cbSize = sizeof(wcx); //WNDCLASSEX结构的大小
wcx.style = CS_HREDRAW | CS_VREDRAW; //如果大小改变了将重绘窗口
wcx.lpfnWndProc = MainWndProc; //窗口消息处理函数
wcx.cbClsExtra = 0; //无附加窗口类内存
wcx.cbWndExtra = 0; //无附加窗口内存
wcx.hInstance = hinstance; //应用程序实例
wcx.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON_MAIN)); //图标º
wcx.hCursor = LoadCursor(NULL, IDC_ARROW); //鼠标指针
wcx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //背景画刷
wcx.lpszMenuName = MAKEINTRESOURCE(IDR_MENU_MAIN); //菜单资源
wcx.lpszClassName = "ASCEClass"; //窗口类名
wcx.hIconSm = (HICON)LoadImage(hinstance, //小图标
MAKEINTRESOURCE(IDI_ICON_MAIN),
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_DEFAULTCOLOR);
//注册窗口类,并返回
return RegisterClassEx(&wcx);
}
2)创建窗口
WinMain主函数调用InitInstance函数实现了程序主窗口的创建,CreateWindow和CreateWindowEx是用于创建窗口的API函数。
CreateWindow函数的功能是创建一个窗口,在创建窗口前,需先注册窗口类。如果创建的窗口是系统控件,那么系统控制的类已经由系统注册好,所以不再需要注册:
HWND WINAPI CreateWindow(
__in_opt LPCTSTR lpClassName, //窗口所需窗口类的类名
__in_opt LPCTSTR lpWindowName, //窗口名,窗口有标题栏的话,将显示在上面
__in DWORD dwStyle, //窗口样式,窗口是否具有标题栏,是否可改变大小,
//边框的样式等都通过这个参数设置
__in int x, //创建创建后的初始位置,可设为CW_USEDEFAULT,由系统来设置
__in int y,
__in int nWidth, //窗口的宽和高,以像素点为单位,同样可指定为CW_USEDEFAULT
__in int nHeight,
__in_opt HWND hWndParent, //父窗口的句柄,当窗口样式设有WS_CHILD时需设置该项
__in_opt HMENU hMenu, //
__in_opt HINSTANCE hInstance, //应用程序实例,设置为WinMain的hInstance参数
__in_opt LPVOID lpParam //指定相关参数,指向CREATESTRUCT结构的变量,可以为NULL
);
最常见的窗口样式:
WS_BORDER //具有边框
WS_CAPTION //具有标题栏
WS_CHILD //是子窗口
WS_CHILDWINDOW //同上
WS_HSCROLL //具有水平滚动条
WS_MAXIMIZE //创建最大化窗口
WS_MINIMIZE //创建最小化窗口
WS_POPUP //顶层窗口(无父窗口,不能和WS_CHILD同时使用)
WS_SIZEBOX //具有拖拉窗口边框,窗口可改变大小
WS_SYSMENU //窗口具有菜单(窗口必须同时具有标题栏)
WS_TILED //具有标题栏和边框
WS_VISIBLE //窗口可见
WS_VSCROLL //具有竖直滚动条
CreateWindow和CreateWindowEx返回值都是HWND类型。HWND是窗口句柄数据类型。在Windows系统中每个窗口都有一个句柄,所有窗口句柄都是HWND数据类型的。
WinMain函数调用InitInstance函数实现了窗口的创建:
/*************************************************************
* BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
* 功能;创建主窗口和控件
* 参数:hinstance,应用程序本次运行实例
* nCmdShow,如何显示
* 返回值:是否成功
*************************************************************/
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
HWND hwnd;
RECT rect;
//保存应用程序实例句柄
hinst = hinstance;
hwnd = CreateWindow(
"ASCEClass", //窗口类名,使用之前注册的主窗口类
"This’s ASCE’s Windows", //窗口名,显示在窗口标题栏上的字符串
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
WS_POPUPWINDOW, //窗口样式
CW_USEDEFAULT, //水平位置(默认)
CW_USEDEFAULT, //垂直位置(默认)
800, //宽
600, //高
(HWND)NULL, //无父窗口
(HMENU)LoadMenu(hinst, MAKEINTRESOURCE(IDR_MENU_COMMAND)), //菜单
hinstance, //应用程序实例
(LPVOID)NULL //无窗口创建数据
);
//窗口创建是否成功
if(!hwnd)
return FALSE;
//保留窗口句柄
hwndMain = hwnd;
//保证通用控件动态链接库已经加载
InitCommonControls();
//创建三种控件、子窗口
hwndTreeView = CreateTreeView(hwndMain, "files");
hwndListView = CreateListView(hwndMain, "processing");
hwndEdit = CreateEdit(hwndMain, "textarea");
//获取本窗口客户区的RECT(矩形方框的4个边界点)
GetClientRect(hwndMain, &rect);
//设置子窗口客户区的大小和位置
SetWindows(&rect);
//在EDIT控件中显示文本
ShowTextOnEdit(lpszLatin);
//显示、刷新窗口,使用WinMain函数设置的窗口显示方式
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return TRUE;
}
3)窗口消息处理函数:
消息处理函数是一个回调函数,是注册窗口类时指定,在有消息需要处理时由系统调用。
消息处理函数具有固定的接口形式:
LRESULT CALLBACK WindowProc( //函数名任意
__in HWND hwnd, //操作的窗口
__in UINT uMsg, //消息标识符,窗口处理函数中需根据这个函数判断消息的类型做出不同的处理
__in WPARAM wParam, //消息的两个参数,根据消息类型的不同而有不同的意义
__in LPARAM lParam
);
窗口处理消息实例:
/****************************************************
* MainWndProc
* 窗口消息处理函数
****************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
switch(uMsg)
{
case WM_CREATE: //窗口被创建时收到此消息
break;
case WM_PAINT: //窗口被绘制时收到此消息
break;
case WM_SIZE: //窗口大小改变时收到此消息
OnWindowResize();
break;
case WM_NOTIFY: //通常由控件发送给其父窗口,说明控件正在进行某项窗口操作
OnChildWindowsNotify((PVOID)lParam);
break;
case WM_DESTROY: //窗口销毁,单击右上角的关闭按钮会触发此消息
PostQuitMessage(0);
break;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
//在“帮助”菜单中选择“关于”
case ID_HELP_ABOUT:
{
DialogBox(hinst, (LPCTSTR)IDD_DIALOG_ABOUT, hwnd, (DLGPROC)About);
return 0;
}
}
}
//可以在这里处理其他消息
default:
break;
}
//有很多消息未做处理,需要由默认窗口消息处理函数来处理
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
常见的窗口消息:
WM_CLOSE //收到此消息时窗口应关闭
WM_CREATE //窗口被创建时
WM_DESTROY//当窗口从界面上销毁时(用户单击窗口右上角关闭按钮)
WM_MOVING //窗口被移动时
WM_SIZE //窗口大小改变时
WM_NOTIFY //窗口所包含的子窗口被操作时,窗口会收到此消息
WM_COMMAND //当菜单、按钮被操作时,窗口会收到此消息
WM_NCLBUTTONDOWN //当用户在窗口中单击左键时,窗口会收到此小溪
WM_MOUSEHOVER //当鼠标在窗口客户区徘徊一段时间后窗口会收到此消息
重要消息:
1)WM_NOTIFY:
当一个窗口的子窗口被操作时,该子窗口会向父窗口发送WM_NOTIFY通知。WM_NOTIFY的lParam参数是一个指向NMHDR结构的指针:
typedef struct tagNMHDR {
HWND hwndFrom; //向主窗口发送此消息的子窗口的句柄
UINT_PTR idFrom; //子窗口的标识符
UINT code; //表示进行哪种操作
} NMHDR;
比如上面提到的OnChildWindowNotify函数所有到了NM_RCLICK表示右键单击;此外,还有NM_SETFOCUS(获得焦点)、NM_KEYDOWN(键盘按键)等,OnChildWindowNotify函数具体代码如下:
/*****************************************************************
* 功能:处理控制子窗口向父窗口发送的WM_NOTIFY消息
* 参数:pParam---WM_NOTIFY的消息参数
* 返回值:是否是Tree View发送的
******************************************************************/
BOOL OnChildWindowNotify(PVOID pParam)
{
LPNMHDR phdr = (LPNMHDR)pParam;
//只处理Tree View发送的Notify,其他的不予处理
if(phdr->hwndFrom != hwndTreeView)
{
return FALSE;
}
switch((LPNMHDR)pParam->code)
{
//如果是右键单击,则调用OnRclickTree函数,弹出右键菜单
case NM_RCLICK:
OnRclickTree((LPNMHDR)pParam);
break;
default:
break;
}
return TRUE;
}
2)WM_DESTROY:
收到WM_DESTROY消息表示用户单击窗口的关闭按钮。因此在MainWndProc函数收到WM_DESTROY消息后,调用PostQuitMessage函数退出程序运行。当然,要退出一个进程的运行,也可以调用ExitProcess函数。
3)WM_COMMAND:
收到WM_COMMAND一般是因为用户通过菜单、按钮等向程序输入命令。WM_COMMAND消息的wParam的低位WORD是表示用户单击的控件或菜单项的ID。
窗口会收到很多消息,但这些消息不一定需要处理,有些消息可能直接由系统来处理。DefWindowProc
的作用就是处理窗口处理函数没有处理完、或者不需要处理的函数。如果在消息处理函数中不把消息交给DefWindowProc处理,那么对窗口的动作几乎不会有响应,包括移动窗口、关闭窗口等,甚至有很多窗口样式不能显示。
设置窗口位置和大小:
上面的InitInstance函数除了完成父窗口的创建外,还创建了3个子窗口。在创建子窗口完成后,调用了SetWindows函数,代码如下:
/*************************************************************************
* 功能:本函数设置子窗口的位置和大小
* 参数:指向表示父窗口客户区的RECT
*************************************************************************/
DWORD SetWindows(LPRECT lpRect)
{
//Tree View
SetWindowPos(hwndTreeView, HWND_TOP,
lpRect->left, lpRect->top,
lpRect->right * 0.3, lpRect->bottom,
SWP_SHOWWINDOW);
//List View
SetWindowPos(hwndListView, HWND_TOP,
lpRect->right * 0.3, lpRect->bottom * 0.7,
lpRect->right * 0.7, lpRect->bottom * 0.3,
SWP_SHOWWINDOW);
//Edit
SetWindowPos(hwndEdit, HWND_TOP,
lpRect->right * 0.3, lpRect->top,
lpRect->right * 0.7, lpRect->bottom * 0.7,
SWP_SHOWWINDOW);
return 0;
}
上面用到的API函数是SetWindowPos:
BOOL WINAPI SetWindowPos(
__in HWND hWnd, //需设置的窗口句柄
__in_opt HWND hWndInsertAfter, //说明窗口的层次顺序,HWND_TOP表示显示在最上方
__in int X, //窗口的左上角所在位置和窗口的宽、高
__in int Y,
__in int cx,
__in int cy,
__in UINT uFlags //窗口大小和位置标志,SWP_SHOWWINDOW表示窗口正常显示
);
不仅窗口的位置和大小可以设置,窗口几乎所有属性都可以设置,包括窗口样式、消息处理函数等。
SetWindowLong函数可实现窗口多种属性设置:
LONG WINAPI SetWindowLong(
__in HWND hWnd, //指明需要设置属性的窗口
__in int nIndex, //指明需要设置哪种属性
__in LONG dwNewLong //新属性值
);
nIndex常见的值有:
GWL_STYLE //窗口样式
GWL_WNDPROC //窗口的消息处理函数
GWL_HINSTANCE //应用程序实例
窗口显示方式:
创建好的窗口可以在屏幕上显示,也可以不在屏幕上显示,通过ShowWindow函数进行设置:
BOOL WINAPI ShowWindow(
__in HWND hWnd, //窗口句柄
__in int nCmdShow //显示的方式,如SW_HIDE、SW_SHOW,SW_MAXIMIZE等
);
线程消息处理和消息循环:
GetMessage函数是从其所在的线程的消息队列中得到一条消息:
BOOL WINAPI GetMessage(
__out LPMSG lpMsg, //指向MSG结构的指针,该结构由于存储获取的消息
__in_opt HWND hWnd, //指定从哪个窗口获取消息,1)为NULL时,表示处理属于当前线程的
//所有窗口消息,以及线程消息队列中hwnd为NULL的窗口消息;
//2)为-1时,表示只处理线程消息队列中hwnd为NULL的窗口消息
__in UINT wMsgFilterMin, //获取消息的ID编号最小值,小于这个值就不获取
__in UINT wMsgFilterMax //获取消息的ID编号最大值,大于这个值就不获取
);
TranslateMessage函数用来把虚拟键消息转换成字符消息。因为Windows对所有键盘编码都采用虚拟键的定义,当按键按下时,并不得到字符消息,需要该函数来进行转换。转换后字符消息被传递到调用线程的消息队列中,当下一次调用GetMessage或PeekMessage时被取出:
BOOL WINAPI TranslateMessage(
__in const MSG *lpMsg
);
DispatchMessage函数把消息发送到窗口的消息处理函数处:
LRESULT WINAPI DispatchMessage(
__in const MSG *lpmsg
);
Windows系统为每个线程创建消息队列,从消息队列中获取消息使用GetMessage函数。也可以调用API函数将消息添加到消息队列中。PostMessage和SendMessage函数不同的是:PostMessage函数只是把消息放置在线程的消息队列之后就立即返回;而SendMessage函数是直接交给窗口消息处理函数:
BOOL WINAPI PostMessage(
__in_opt HWND hWnd,
__in UINT Msg,
__in WPARAM wParam,
__in LPARAM lParam
);
LRESULT WINAPI SendMessage(
__in HWND hWnd,
__in UINT Msg,
__in WPARAM wParam,
__in LPARAM lParam
);
PostQuitMessage函数将WM_QUIT消息消息放置在消息队列中,GetMessage函数在获得了WM_QUIT消息后,会返回0,退出消息循环。
注意,GetMessage函数只是获取了其所在的线程的消息队列中的消息,因此,在开发多线程的图形用户界面程序时,一般将所有窗口的创建放置在同一个线程,子窗口的创建最好在主窗口的消息处理函数中,这样能保证消息都集中中同一个线程中。如果在其他线程中需要操作界面,可以直接使用PostThreadMessage、SendMessage等函数向窗口发送自定义的消息,然后再在窗口消息处理函数中处理这些自定义窗口消息时进行窗口操作。
BOOL WINAPI PostThreadMessage(
__in DWORD idThread,
__in UINT Msg,
__in WPARAM wParam,
__in LPARAM lParam
);