Windows API窗口程序设计
第一个 Windows API 程序
一、实验要求
- 窗口背景色为灰色;
- 窗口内居中显示"大家好,这是我的第一个Windows API程序!";
- 播放背景音乐
hellowin.wav(打开时播放一次); - 可通过程序把窗口显示风格切换为"只有标题栏";
- 鼠标指针形状为手型。
二、整体设计思路
核心步骤:
- 注册窗口类
RegisterClass:定义窗口过程、背景刷、光标等; - 创建窗口
CreateWindow:指定类名、标题、样式; - 进入消息循环:
GetMessage → TranslateMessage → DispatchMessage,把消息交给窗口过程处理; - 在窗口过程中响应消息:
WM_PAINT负责绘制灰底和文字,WM_CREATE里创建菜单并播放音乐,WM_COMMAND处理菜单点击,WM_SETCURSOR控制鼠标形状。
全部使用
W后缀的宽字符版本 API,并结合/utf-8编译选项,确保中文字符串显示正确、不乱码。
三、完整源码
/*
* hellowin.c —— 我的第一个 Windows API 程序
*
* 功能:
* 1. 窗口背景色为灰色;
* 2. 窗口内居中显示“大家好,这是我的第一个Windows API程序!”;
* 3. 播放背景音乐(hellowin.wav,打开时播放一次);
* 4. 可通过菜单把窗口显示风格切换为“只有标题栏”;
* 5. 可通过菜单切换鼠标指针形状为“手型”。
*
* 编译(MSVC,需先执行 vcvars64.bat):
* cl /nologo /O2 /utf-8 hellowin.c /Fe:hellowin.exe /link /SUBSYSTEM:WINDOWS
* 依赖库:user32.lib gdi32.lib winmm.lib
*/
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "winmm.lib")
/* 菜单命令 ID */
#define IDM_STYLE_NORMAL 1001 /* 标准窗口 */
#define IDM_STYLE_CAPTION 1002 /* 只有标题栏 */
#define IDM_CURSOR_HAND 1003 /* 手型指针(开关) */
#define IDM_EXIT 1004 /* 退出 */
static BOOL g_handCursor = TRUE; /* 默认使用手型指针 */
static WCHAR g_wavPath[MAX_PATH]; /* wav 完整路径 */
/* 依据可执行文件所在目录,拼出 hellowin.wav 的完整路径 */
static void BuildWavPath(void)
{
WCHAR exe[MAX_PATH];
GetModuleFileNameW(NULL, exe, MAX_PATH);
WCHAR *slash = wcsrchr(exe, L'\\');
if (slash) *slash = L'\0';
wsprintfW(g_wavPath, L"%s\\hellowin.wav", exe);
}
/* 用灰色填充客户区 */
static void PaintGray(HWND hwnd, HDC hdc)
{
RECT rc;
GetClientRect(hwnd, &rc);
HBRUSH gray = CreateSolidBrush(RGB(128, 128, 128));
FillRect(hdc, &rc, gray);
DeleteObject(gray);
}
/* 窗口过程 */
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
/* 构建菜单 */
HMENU hMenu = CreateMenu();
HMENU hStyle = CreatePopupMenu();
AppendMenuW(hStyle, MF_STRING, IDM_STYLE_NORMAL, L"标准窗口(&N)");
AppendMenuW(hStyle, MF_STRING, IDM_STYLE_CAPTION, L"只有标题栏(&T)");
AppendMenuW(hMenu, MF_POPUP, (UINT_PTR)hStyle, L"显示风格(&S)");
HMENU hPtr = CreatePopupMenu();
AppendMenuW(hPtr, MF_STRING | MF_CHECKED, IDM_CURSOR_HAND, L"手型指针(&H)");
AppendMenuW(hMenu, MF_POPUP, (UINT_PTR)hPtr, L"指针形状(&C)");
HMENU hFile = CreatePopupMenu();
AppendMenuW(hFile, MF_STRING, IDM_EXIT, L"退出(&X)");
AppendMenuW(hMenu, MF_POPUP, (UINT_PTR)hFile, L"文件(&F)");
SetMenu(hwnd, hMenu);
DrawMenuBar(hwnd);
/* 播放背景音乐(打开时播放一次) */
if (GetFileAttributesW(g_wavPath) != INVALID_FILE_ATTRIBUTES)
PlaySoundW(g_wavPath, NULL, SND_FILENAME | SND_ASYNC);
}
return 0;
case WM_ERASEBKGND: /* 灰色背景,避免闪烁 */
PaintGray(hwnd, (HDC)wParam);
return 1;
case WM_PAINT: /* 绘制灰色背景与居中文字 */
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
PaintGray(hwnd, hdc);
RECT rc;
GetClientRect(hwnd, &rc);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(255, 255, 255));
HFONT hFont = CreateFontW(28, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY, DEFAULT_PITCH, L"Microsoft YaHei");
HFONT old = (HFONT)SelectObject(hdc, hFont);
DrawTextW(hdc, L"大家好,这是我的第一个Windows API程序!", -1, &rc,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
SelectObject(hdc, old);
DeleteObject(hFont);
EndPaint(hwnd, &ps);
}
return 0;
case WM_SETCURSOR: /* 客户区内鼠标指针形状 */
if (LOWORD(lParam) == HTCLIENT)
{
SetCursor(LoadCursorW(NULL, g_handCursor ? (LPCWSTR)IDC_HAND
: (LPCWSTR)IDC_ARROW));
return TRUE;
}
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_STYLE_NORMAL: /* 恢复标准窗口 */
SetWindowLongPtrW(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow(hwnd, SW_SHOW);
InvalidateRect(hwnd, NULL, TRUE);
CheckMenuItem(GetMenu(hwnd), IDM_STYLE_NORMAL, MF_BYCOMMAND | MF_CHECKED);
CheckMenuItem(GetMenu(hwnd), IDM_STYLE_CAPTION, MF_BYCOMMAND | MF_UNCHECKED);
return 0;
case IDM_STYLE_CAPTION: /* 切换为只有标题栏 */
SetWindowLongPtrW(hwnd, GWL_STYLE, WS_CAPTION);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow(hwnd, SW_SHOW);
InvalidateRect(hwnd, NULL, TRUE);
CheckMenuItem(GetMenu(hwnd), IDM_STYLE_NORMAL, MF_BYCOMMAND | MF_UNCHECKED);
CheckMenuItem(GetMenu(hwnd), IDM_STYLE_CAPTION, MF_BYCOMMAND | MF_CHECKED);
return 0;
case IDM_CURSOR_HAND: /* 手型 / 箭头指针切换 */
g_handCursor = !g_handCursor;
CheckMenuItem(GetMenu(hwnd), IDM_CURSOR_HAND,
MF_BYCOMMAND | (g_handCursor ? MF_CHECKED : MF_UNCHECKED));
SetCursor(LoadCursorW(NULL, g_handCursor ? (LPCWSTR)IDC_HAND
: (LPCWSTR)IDC_ARROW));
return 0;
case IDM_EXIT:
DestroyWindow(hwnd);
return 0;
}
break;
case WM_DESTROY:
PlaySoundW(NULL, NULL, 0); /* 停止音乐 */
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
/* 程序入口 */
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow)
{
(void)hPrev; (void)lpCmdLine;
BuildWavPath();
WNDCLASSW wc = { 0 };
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.hInstance = hInst;
wc.hIcon = LoadIconW(NULL, (LPCWSTR)IDI_APPLICATION);
wc.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_HAND); /* 手型指针 */
wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); /* 灰色背景 */
wc.lpszClassName = L"HelloWinClass";
if (!RegisterClassW(&wc))
return 0;
HWND hwnd = CreateWindowW(
L"HelloWinClass",
L"我的第一个Windows API程序",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 720, 400,
NULL, NULL, hInst, NULL);
if (!hwnd)
return 0;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return (int)msg.wParam;
}
四、关键知识点讲解
1. 窗口类注册与创建
任何 Win32 窗口都离不开 WNDCLASS 和 RegisterClass。在本例中,我把三处关键配置直接放在了窗口类里:
wc.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_HAND); /* 手型指针 */
wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); /* 灰色背景 */
hCursor:窗口的默认光标,这里直接设为系统手型光标IDC_HAND;hbrBackground:窗口背景刷,用GRAY_BRUSH设置灰色。
2. 消息循环
while (GetMessageW(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
Windows 是消息驱动的:程序不做主动轮询,而是等待系统投递消息,再分发给对应的窗口过程。GetMessage 取到 WM_QUIT 时返回 0,循环结束,程序退出。
3. 灰色背景与居中文字
绘制工作放在 WM_PAINT 里:
FillRect(hdc, &rc, gray); /* 灰色填充 */
DrawTextW(hdc, L"大家好,这是我的第一个Windows API程序!", -1, &rc,
DT_CENTER | DT_VCENTER | DT_SINGLELINE); /* 水平垂直居中 */
DrawText 配合 DT_CENTER | DT_VCENTER 就能轻松实现文字在客户区水平垂直居中。文字使用 28 号加粗的微软雅黑,白色,在灰色背景上对比鲜明。
同时处理
WM_ERASEBKGND并返回 1,可以避免窗口在刷新时的闪烁。
4. 背景音乐
用 PlaySound 播放 WAV,需要链接 winmm.lib:
PlaySoundW(g_wavPath, NULL, SND_FILENAME | SND_ASYNC);
SND_FILENAME:指定按文件名播放;SND_ASYNC:异步播放,不阻塞窗口;- 由于没加
SND_LOOP,音乐只播放一次。
优化:用 GetModuleFileNameW 获取 exe 所在目录,再拼出 hellowin.wav 的完整路径。这样无论程序从哪里启动,都能找到音乐文件,不会因为"当前工作目录不对"而播不出声音。
5. 菜单与样式切换
菜单用 CreateMenu / AppendMenuW 动态构建,不需要额外的 .rc 资源文件,单文件即可编译。
切换窗口样式是最有意思的部分——用 SetWindowLongPtr 改 GWL_STYLE,再调 SetWindowPos 让系统重算边框:
SetWindowLongPtrW(hwnd, GWL_STYLE, WS_CAPTION); /* 只保留标题栏 */
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
SWP_FRAMECHANGED :通知系统窗口样式已改变,需要重新绘制非客户区。这样"标准窗口"和"只有标题栏"就能在运行时即时切换。
6. 手型指针
系统手型光标就是 IDC_HAND。我在窗口类里设了默认手型,同时用 WM_SETCURSOR 在客户区主动控制,方便通过菜单在手型/箭头之间来回切换:
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT)
{
SetCursor(LoadCursorW(NULL, g_handCursor ? (LPCWSTR)IDC_HAND
: (LPCWSTR)IDC_ARROW));
return TRUE;
}
break;
7. 中文不乱码的两个关键
- 用宽字符版本 API(
W结尾):RegisterClassW、CreateWindowW、DrawTextW、PlaySoundW……字符串统一用L"..."宽字符; - 编译加
/utf-8:告诉编译器源码文件是 UTF-8 编码。
这两步缺一不可,否则中文字符串在 Win32 里极容易出现乱码。
五、编译与运行
我使用 Visual Studio 2022 自带的 MSVC 编译器(cl)。先在命令行加载编译环境,再编译:
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
cl /nologo /O2 /utf-8 hellowin.c /Fe:hellowin.exe /link /SUBSYSTEM:WINDOWS
/O2:优化;/utf-8:源码按 UTF-8 处理(中文关键);/link /SUBSYSTEM:WINDOWS:链接为 GUI 程序(不弹出控制台窗口)。
依赖的库通过源码里的 #pragma comment(lib, ...) 自动链接,无需手动指定。编译成功后,把 hellowin.exe 和 hellowin.wav 放在同一目录,双击运行即可。
六、运行效果
窗口正常启动时,灰色背景、居中文字、菜单栏清晰可见,同时会播放一次背景音乐:

通过菜单"显示风格 → 只有标题栏",窗口会实时变成仅剩标题栏的样子:

通过菜单"指针形状 → 手型指针",鼠标在窗口内会变成手型(也可切回普通箭头)。
七、总结
- 中文乱码:核心就是"宽字符 API +
/utf-8编译",从源头解决。 - 切样式后边框不刷新:忘了
SWP_FRAMECHANGED,窗口样式改了但界面没变,卡了很久才想明白。 PlaySound找不到文件:直接写相对路径,可能因为启动目录不对而静默失败。用GetModuleFileNameW拼绝对路径最稳妥。- 背景闪烁:处理
WM_ERASEBKGND直接返回 1,并让WM_PAINT一次性填充背景,能显著减少重绘闪烁。
浙公网安备 33010602011771号