和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;
}
2.1 WinClasses.h
#pragma once
#include "WinProcs.h"
// 注册窗体类使用的类名
#define MAIN_CLASSNAME _T("Hello-Win")
// 注册窗体函数
ATOM WINAPI RegistMainWinClass(HINSTANCE hIns);
2.2 WinClasses.c
#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);
}
3.1 WinProcs.h
#pragma once
#include <tchar.h>
#include <windows.h>
// 用于处理主窗体消息的消息处理函数
LRESULT CALLBACK WndMainProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
3.2 WinProcs.c
#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;
}
3.1 Draw.h
#pragma once
#include <tchar.h>
#include <windows.h>
// 要显示的字符串
#define SHOW_STRING _T("Hello World,这是第一个 Win32 窗体程序")
// 显示字符串的函数
void WINAPI WndMainDrawString(HWND hWnd, HDC hDC);
3.2 Draw.c
#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);
}