和MS-DOS或控制台编程不同, Win32子系统的绝大多数应用程序是以窗体为基础建立的, 所以学习Win32编程, 就必须要懂得如何建立窗体, 本例简要的介绍了如何去建立一个Windows窗体以及其最基本的工作原理。
主要包括:WinMain主函数;窗体类;创建窗体;消息循环;窗体消息处理
程序清单:
1. Main.c
#include <stdio.h> #include "WinClasses.h" /** * WinMain函数和wWinMain函数是Windows图形子系统进程 * 启动的主函数, 作用和main函数相同, 只不过Windows不会为具 * 有WinMain函数(或wWinMain函数)的进程创建控制台窗口。 * 如果操作系统支持UNICODE字符集, 则系统会呼叫程序中的 * wWinMain函数, 否则呼叫程序中的WinMain函数。 * * _tWinMain宏的作用是:在定义了_UNICODE宏或UNICODE * 宏时, 表示wWinMain,否则表示WinMain。 * * 参数:hInstance, 表示当前进程的句柄 * hPrevInstance, 表示前一个进程的句柄(不用) * lpCmdLine, 由操作系统传入的命令行参数 * nCmdShow, 一个整数值, 操作系统传入如何创建窗口的 * 标志 */ int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // 用于显示错误信息的字符串缓冲 TCHAR szError[BUFSIZ] = _T(""); // 所建立窗口的句柄 HWND hWnd = NULL; // 消息结构体, 用于消息循环 MSG msg; ZeroMemory(&msg, sizeof(msg)); // 创建窗口: // 步骤1:注册窗口类, 每一个被创建的窗口都必须属于一个窗口类, 这个概念和面向对象编程很类似 if (RegistMainWinClass(hInstance) == 0) { _stprintf_s(szError, BUFSIZ, _T("出现错误, 无法注册窗口, 错误代码:%d。"), GetLastError()); // 显示一个消息框 MessageBox( NULL, // 消息框所属的窗体, NULL表示消息框不属于任何窗体(此时还没有窗体被建立) szError, // 显示在消息框内部的文字 _T("错误"), // 显示在消息狂标题栏的文字 MB_OK | MB_ICONERROR // 消息框样式, MB_OK表示具备一个确定按钮, MB_ICONERROR表示显示一个错误图标 ); } else { // 步骤2:创建窗口 hWnd = CreateWindowEx( 0, // 创建窗口的扩展样式, 0为默认样式 MAIN_CLASSNAME, // 要创建窗口所属的窗口类名称 _T("Hello World"), // 窗口标题字符串 WS_OVERLAPPEDWINDOW, // 窗口样式, WS_OVERLAPPEDWINDOW为一个普通窗体具有标题栏, 系统菜单, 最大化按钮和边框架 CW_USEDEFAULT, // 设置窗体左上角相对于屏幕显示的x坐标, CW_USEDEFAULT表示显示在默认位置 0, // 设置窗体左上角相对于屏幕显示的y坐标,当x坐标设置为CW_USEDEFAULT, 该参数被忽略 CW_USEDEFAULT, // 设置窗体的宽度, CW_USEDEFAULT表示窗体为默认宽度(约为整个屏幕的2/3) 0, // 设置窗体的高度, 当宽度设置为CW_USEDEFAULT时, 该参数被忽略 NULL, // 设置该窗体的父窗体, 目前没有父窗体 NULL, // 设置该窗体的菜单, 目前没有菜单 hInstance, // 设置该窗体所属的进程句柄 NULL // 设置该窗体的显示参数, 该参数将通过WM_CREATE消息发送到消息队列 ); // CreateWindowEx返回一个表示窗口的句柄, 如果返回值为NULL, 表示创建窗口失败 if (hWnd == NULL) { _stprintf_s(szError, BUFSIZ, _T("出现错误, 无法创建窗口, 错误代码:%d。"), GetLastError()); MessageBox(NULL, szError, _T("错误"), MB_OK | MB_ICONERROR); } else { // 步骤3:显示并刷新窗口 // 如果窗口创建成功, 则进一步需要显示该窗口(ShowWindow), 紧接着刷新一次窗口(UpdateWindow) ShowWindow( hWnd, // 要显示的窗口句柄 nCmdShow // 显示窗口的方式, 这里采用系统传入的nCmdShow, 即由操作系统来决定如何显示该窗口 ); UpdateWindow(hWnd); // 步骤4:启动消息循环 // 消息循环是Windows窗体程序得以运转的核心, 这里先做简要介绍 while (GetMessage( &msg, // 一个 MSG 结构体变量的指针, 调用该函数可以获取到最新发来的消息 NULL, // 一个窗体句柄, 表示接收发送给某个窗体的消息, 如果为 NULL, 表示接收所有消息 0, // 后两个参数表示消息过滤范围, 此处不适用 0 )) { // 转化键盘消息 TranslateMessage(&msg); // 将消息送入消息处理函数, 即在注册窗体类时填入的函数指针指向的函数 DispatchMessage(&msg); } } } // 一般来说, 主函数返回消息结构体的 wParam 域 return msg.wParam; }
#pragma once #include "WinProcs.h" // 注册窗体类使用的类名 #define MAIN_CLASSNAME _T("Hello-Win") // 注册窗体函数 ATOM WINAPI RegistMainWinClass(HINSTANCE hIns);
#include "WinClasses.h" /** * 注册主窗体类。 * 参数:hIns 进程句柄 * 返回:如果窗体注册成功, 返回窗体类注册编号(一个唯一值);返回0表示窗体注册失败 */ ATOM WINAPI RegistMainWinClass(HINSTANCE hIns) { // 窗体类是一个结构体, 结构体的每一个分量域都有特殊的含义, 需仔细填写 WNDCLASSEX wcex; ZeroMemory(&wcex, sizeof(wcex)); // 首先进行初始化, 将结构体占据内存清空 // cbSize, 填入WNDCLASSEX结构体的大小, 在SDK早期版本里, 使用WNDCLASS结构体 // 目前使用 WNDCLASSEX 结构体, cbSize 域就是用于区别这两个结构体的 wcex.cbSize = sizeof(wcex); // 设置窗体绘制方式, 横向绘制和纵向绘制 wcex.style = CS_HREDRAW | CS_VREDRAW; // 设置处理该窗口消息的回调函数指针 wcex.lpfnWndProc = WndMainProc; // 设置该窗体类所属的进程句柄 wcex.hInstance = hIns; // 设置该窗体类普通图标 wcex.hIcon = LoadIcon(hIns, MAKEINTRESOURCE(IDI_APPLICATION)); // 设置该窗体类鼠标指针类型 wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // 设置该窗体类背景颜色 wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 设置该窗体类类名 wcex.lpszClassName = MAIN_CLASSNAME; // 设置该窗体类小图标 wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION)); return RegisterClassEx(&wcex); }
#pragma once #include <tchar.h> #include <windows.h> // 用于处理主窗体消息的消息处理函数 LRESULT CALLBACK WndMainProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
#include "WinProcs.h" #include "Draw.h" /** * 处理窗口消息的回调函数 * 参数:hWnd, 窗口句柄, 表示消息的来源窗体 * message, 消息标识 * wParam, 消息参数1 * lParam, 消息参数2 * 返回:整数值, 表示该消息处理的结果 */ LRESULT CALLBACK WndMainProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // 绘图上下文句柄 HDC hDC = NULL; // 绘图结构体 PAINTSTRUCT ps; LRESULT lReturn = 0L; switch (message) { case WM_CREATE: // 窗口被创建的消息 break; case WM_PAINT: // 窗口重绘消息 hDC = BeginPaint(hWnd, &ps); // 调用绘制字符串函数显示内容 WndMainDrawString(hWnd, hDC); EndPaint(hWnd, &ps); break; case WM_CLOSE: // 窗口被关闭的消息 if (MessageBox(hWnd, _T("是否要退出应用程序?"), _T("提示"), MB_YESNO | MB_ICONQUESTION) == IDYES) DestroyWindow(hWnd); break; case WM_DESTROY: // 窗口被撤销的消息, // 一般来说, 只有主窗体被关闭, 才能获得这个消息 PostQuitMessage(0); break; default: // 对于其它未处理的消息, 调用默认消息处理函数处理, 这是必须的 lReturn = DefWindowProc(hWnd, message, wParam, lParam); } return lReturn; }
#pragma once #include <tchar.h> #include <windows.h> // 要显示的字符串 #define SHOW_STRING _T("Hello World,这是第一个 Win32 窗体程序") // 显示字符串的函数 void WINAPI WndMainDrawString(HWND hWnd, HDC hDC);
#include "Draw.h" // 保存字体句柄的变量 static HFONT hFont = NULL; /** * 绘制字符串的函数 * 参数:hWnd, 要绘制字符串的窗体句柄 * hDC,用于绘制的上下文句柄 */ void WINAPI WndMainDrawString(HWND hWnd, HDC hDC) { // 用于获取窗体工作区矩形尺寸的结构体 // 由四个域组成 long left, long top, long width, long height RECT rect; // 保存上下文当前字体句柄的变量,用于恢复原先字体 HGDIOBJ hOldFont; // 保存上下文当前字体颜色的变量,用于恢复原先字体颜色 COLORREF cOldColor; // 如果字体未被创建,则创建字体 if (hFont == NULL) { // 字体信息结构体 LOGFONT logFont; ZeroMemory(&logFont, sizeof(logFont)); // 设置字体的字符集 logFont.lfCharSet = GB2312_CHARSET; // 设置字体的高度和宽度 logFont.lfHeight = 30; logFont.lfWidth = logFont.lfHeight * 0.75; // 设置字体名称 _tcscpy_s(logFont.lfFaceName, 32, _T("隶书")); // 创建字体 hFont = CreateFontIndirect(&logFont); } // 将创建的字体选入上下文中,返回之前的字体句柄 hOldFont = SelectObject(hDC, hFont); // 获取窗体用户区尺寸 GetClientRect(hWnd, &rect); // 设置字体颜色,返回之前的颜色变量 cOldColor = SetTextColor(hDC, COLOR_WINDOWTEXT); // 绘制字体 DrawText( hDC, // 要绘制字体的上下文 SHOW_STRING, // 要绘制字体的字符串指针 -1, // 字符串长度,-1表示自行测量 &rect, // 要绘制字体所在窗口的矩形范围 DT_SINGLELINE | DT_CENTER | DT_VCENTER // 绘制方式 单行 | 水平居中 | 垂直居中 ); // 恢复绘图前上下文的状态 SelectObject(hDC, hOldFont); SetTextColor(hDC, cOldColor); }