window编程补充

本文深入讲解Windows编程的基础概念,包括数据类型、调试技巧、消息处理、主函数流程、控件使用、资源管理和对话框创建。从句柄类型到调试工具,从消息分类到控件消息示例,再到资源加载和对话框的模态与非模态区别,全面覆盖Windows编程入门所需知识点。
摘要由CSDN通过智能技术生成


windows编程与控制台编程的区别在于 属性 - 链接器 - 系统 - 子系统,选错会报错, 无法解析的外部符号

0x00. 数据类型

windows没有提供新的类型,而是大量重定义。

句柄类型是windows面向对象的实现,用来标识一个对象,大小4或8字节,其实就是一个数值, 有时候等于地址,但不是指针,。

把句柄理解为对象就阔以了。

0x01. 调试

OutputDebugString()

OutputDebugStringA(LPCWSTR),会在调试界面的输出窗口输出信息。

BOOL print(const TCHAR* format, ...) //变参函数
 {
     TCHAR buffer[1000];
     va_list argptr;
     va_start(argptr, format);
     //将格式化信息写入指定的缓冲区
     wvsprintf(buffer, format, argptr);
     va_end(argptr);
     //将缓冲区信息输出
     OutputDebugString(buffer);
     return TRUE;
 }

err,hr

在监视窗口输入err,hr

FormatMessage()

由错误码得到错误信息字符串。

void MyGetErrorInfo(
	UINT unErrCode,
	UINT unLine)// unLine=__LINE__
{
	LPTSTR lpMsgBuf = NULL;
	WCHAR  szMessage[128] = { 0 };

	// 通过错误代码获取到信息
	FormatMessageW(0x1300, NULL, unErrCode,
		0x400, (LPTSTR)& lpMsgBuf, 64, NULL);

	// 拼接错误信息和错误代码
	swprintf_s(szMessage, 128,
		L"%d - [0x%08X]: %s", unLine, unErrCode, lpMsgBuf);

	// 输出调试信息
	OutputDebugString(szMessage);
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPSTR lpCmdLine, int nShowCmd)
{
	CreateFile(0,0,0,0,0,0,0);
	DWORD ErrorCode = GetLastError();
	MyGetErrorInfo(ErrorCode,__LINE__);
	return 0;
}

工具

spy++,获取窗口消息,注意在工具栏 - 消息 - 记录选项中勾选消息类别。

错误查找,输入错误码返回错误信息。

0x02. 消息

windows消息分类

windows有3种消息类型:通用窗口消息,控件消息,自定义消息。

通用窗口消息以WM开头,分为:

  • 窗口消息,由os和控制其它窗口的窗口使用;CreateWindow(), DestroyWindow(), MoveWindow(), 点击鼠标等都会产生;
  • 命令消息,特指WM_COMMAND消息,子控件通知父窗口,例如按下按钮等标准控件;
  • 通知消息:特指WM_NOTIFY消息,命令消息无法满足复杂控件给父窗口传递消息的要求而衍生出来的消息,只适用于Windows、
  • 公共控件,如树、列表。

控件消息:一些控件有自己单独处理的消息。

自定义的消息必须是WM_USER之后的值,因为前面的值都已经被使用了

消息标识符:

  • 系统保留消息:0x0000 - 0x03ff
  • 应用程序消息:0x0400(WM_USER) - 0x7fff由自己定义; 0xc000 - 0xffff用来和其它程序通信。

#define UM_USER (WM_USER + 1)

使用 SendMessage()PostMessage() 可以发送自定义消息,发送的消息可以是给自己的窗口,也可以是给其它的窗口,由参数一指定

队列消息

队列、非队列消息是按照发送途径来分类的。

消息队列分为:

  • 系统消息队列,由windows维护;
  • 线程消息队列,gui线程自己维护。

一个消息会经过这两个队列。

事件消息发送给消息队列,由线程取出并发给窗口过程处理;

不同的消息对应了不同的附加参数,具体的意义需要查文档。

WM_PAINT是个例外,客户区部分或全部无效,或需要更新的时候会发送,多个该消息会合并为一个,目的是减少刷新次数。

通过PostMessage发送队列消息,消息会被放置到当前应用程序的消息队列中,不等待处理结果,所以是一个非阻塞的函/数。

消息通过GetMessage + DispatchMessage进行处理。

不想处理的消息要发送给DefWIndowProc()进行处理

非队列消息

绕过队列,直接发送给窗口过程。

通过SendMessage发送非队列消息,消息会被直接传递给消息回调函数,等待执行完毕后才会返回(同步函数),所以是阻塞的函数。

0x03. 主函数

int APIENTRY WinMain(
    _In_ HINSTANCE hInstance, 
    _In_opt_ HINSTANCE hPrevInstance, 
    _In_ LPSTR lpCmdLine, 
    _In_ int nShowCmd){}

APIENTRY,调用约定,必须;

_In_,批注,传入/传出/可选;

hinstance,表示当前应用程序;

hPrevInstance,废弃;

lpCmdLine,命令行参数,对应argv;

nShowCmd,显示方式。

流程

  1. 定义窗口类结构(WNDCLASS)
  2. 注册窗口类(RegisterClass)
  3. 创建窗口(CreateWindow)
  4. 显示窗口(ShowWindow)
  5. 更新窗口(UpdateWindow)
  6. 消息泵(GetMessage -> TranslateMessage ->DispatchMessage)

WNDCLASS

窗口类结构的两个成员非常重要,

wndcls.lpszClassName = L"MyWindow";
wndcls.lpfnWndProc = WndProc;

CreateWindow

一个窗口类只能被注册一次,即统一名称的模板只有一个。

CreateWindow()是一个宏(紫色),不是函数。

3种重要的风格:

  • 重叠窗口,WS_OVERLAPPEDWINDOW,可拉伸,顶层,默认有标题栏和边框;
  • 弹出窗口,WS_POPUPWINDOW,默认无标题栏,不可拉伸;
  • 子窗口,WS_CHILDWINDOW,它不能超出父窗口客户区,如所有控件。

窗口其它设置一般在窗口过程的case WM_CREATE处理。

其它窗口函数汇总:

  • MoveWindow() 移动窗口的位置
  • GetClientRect()获取窗口相对于用户界面左上角的位置
  • GetDlgItem() 获取控件的句柄值用于操作
  • GetWindowText() 获取一个窗口的文字信息
  • SetWindowText() 设置一个窗口的文字信息
  • FindWindow(窗口类名,窗口名) 查找窗口
  • EnumChildWindow() 枚举目标窗口下的所有子窗口
  • EnumWindow() 枚举桌面的所有子窗口
  • GetDesktopWindow(): 获取桌面的句柄
  • SetParent() 重置窗口的父窗口
  • Get\SetClassLong() 设置或者获取窗口类的属性 GCL
  • Get\SetDlgItemText 获取控件的文字信息
  • Get\SetDlgItemInt 将获取到的信息转换成整数类型

消息泵

TranslateMessage()转换函数,举例说明,可以将一组 WM_KEYDOWN 和 WM_KEYUP 组合成一个 WM_CHAR保留原有的两个消息,将新生成的消息放置到消息队列中

0x04. 控件

控件一般在窗口过程的case WM_CREATE创建,CreateWindow()坐标是相对于父窗口的坐标,当创建的是一个控件,那么第一个参数lpClassName是由系统定义的,HMENU保存的是一个 id,用于标识当前的消息是有哪一个控件产生的。

所有控件都有WS_CHILD | WS_VISIBLE风格。

控件名由系统确定。

获得句柄

获得控件/子窗口句柄:GetDlgItem(hParentWnd, ID);

获得父窗口句柄:GetParent()

获得主窗口句柄:AfxGetMainWnd()

控件消息

标准控件响应WM_COMMAND

  • wParam低位是控件id,高位是通知码;
  • lParam是控件句柄。
Message SourcewParam (high word)wParam (low word)lParam
Menu0Menu identifier (IDM_*)0
Accelerator1Accelerator identifier (IDM_*)0
ControlControl-defined notification codeControl identifierHandle to the control window

通用控件响应WM_NOTIFY

  • wParam: The identifier of the common control sending the message.
  • lParam: A pointer to an NMHDR structure that contains the notification code and additional information.

示例

注意要有背景色,在窗口需要重新绘制的时候,填充使用的颜色,否则MoveWindow()后旧窗口仍然显示。

#include <windows.h>

/*
	hWnd: 表示的是遍历到的所有子窗口的句柄
	lParam: 调用函数时传入的 lParam
*/
BOOL CALLBACK EnumProc(HWND hWnd, LPARAM lParam)
{
	CHAR Buffer[100] = { 0 };
	GetWindowTextA(hWnd, Buffer, 100);

	if (Buffer[0])
	{
		MessageBoxA(NULL, Buffer, Buffer, MB_OK);
		return TRUE;
	}


	return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HINSTANCE hInstance;
	WCHAR *pStr[5] = { L"move btn" ,L"text" ,L"set father",L"each child", L"I'm a editing box"};
	int i = 0;
	hInstance = (HINSTANCE)GetModuleHandle(NULL);

	switch (uMsg)
	{
	case WM_CREATE:
		for (i = 0; i < 4; ++i)
		{
			CreateWindow(L"button",pStr[i], WS_CHILD | WS_VISIBLE,
				10, 10 + 60 * i, 200, 50, 
				hWnd, (HMENU)(0x1000 + i), hInstance, NULL);
		}
		CreateWindow(L"edit", pStr[i], WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE,
			10, 10 + 60 * 4, 200, 50,
			hWnd, (HMENU)0x1004, hInstance, NULL);
		break;
	case WM_CLOSE:
		DestroyWindow(hWnd);
		PostQuitMessage(0);
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case 0x1000:
		{
			// 1. 获取客户区的矩形区域
			RECT ClientRect = { 0 };
			GetClientRect(hWnd, &ClientRect);

			// 2. 计算出一个有效范围内的新的位置
			LONG x = 10 + rand() % 200;
			LONG y = ClientRect.top + rand() % 50;

			// 3. 移动当前按钮的位置
			MoveWindow((HWND)lParam, x, y, 200, 50, TRUE);
			break;
		}
		// 每点击一次按钮,就获取编辑框的文字并设置新的文字
		case 0x1001:
		{
			// 1. 通过传入控件 id,获取指定窗口中控件的句柄(GetDlgItemTextA())
			HWND hEditWnd = GetDlgItem(hWnd, 0x1004);

			// 2. 获取编辑框的信息
			CHAR TextBuffer[100] = { 0 };
			GetWindowTextA(hEditWnd, TextBuffer, 100);
			MessageBoxA(hWnd, TextBuffer, TextBuffer, MB_OK);

			// 3. 设置新的编辑框的信息(SetDlgItemText())
			SetWindowTextA(hEditWnd, "这是新的信息");
			break;
		}
		// 将父窗口设置成桌面
		case 0x1002:
		{
			// 1. 获取桌面的句柄
			// HWND hDesktopWnd = GetDesktopWindow();

			HWND hFindWnd = FindWindow(NULL, "Win32Test - Microsoft Visual Studio");

			// 2. 设置新的父窗口(设置后这里就无法响应消息)
			SetParent((HWND)lParam, hFindWnd);

			break;
		}

		// 遍历指定窗口的所有子窗口
		case 0x1003:
		{
			HWND hFindWnd = FindWindowA(NULL, "Win32Test - Microsoft Visual Studio");

			// 遍历桌面的所有子窗口
			EnumChildWindows(hFindWnd, EnumProc, NULL);
			break;
		}
		}
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASS wndcls = { 0 };
	MSG msg = { 0 };
	HWND hWnd;

	wndcls.lpszClassName = L"MyWindow";
	wndcls.lpfnWndProc = WndProc;
	wndcls.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));

	RegisterClass(&wndcls);

	hWnd = CreateWindow(wndcls.lpszClassName, L"caption",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
		NULL, NULL,
		hInstance, 0);

	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		DispatchMessage(&msg);
	}

	return 0;
}

0x05. 资源

这一部分,应注意编译版本为32位,工具集和vs版本匹配,否则出现未定义符号。

光标(.cur),位图(bmp),图标(ico),加速键,菜单,字符串,对话框等。

获取资源要用LoadXXX()api。

  • 添加了资源之后,会产生一个头文件 resource.h,保存的是资源对应的ID
  • 大多数的资源都是使用 LoadXXX(hInstance,MAKEINTRESOURCE(ID)) 的形式被加载
  • 双击项目下的资源目录中的 xxxxx.rc 进入资源管理器
  • 同一时刻不能同时打开【对话框编辑器】和【资源头文件】
  • 操作对话框时,在工具箱中拖拽需要的控件

以cursor、icon、menu为例。

WM_COMMAND代表菜单消息时:

  • wParam低位是菜单id,高位是0;
  • lParam是0。

菜单有3种创建方法

  • 窗口类中lpszMenuName成员指定;
  • 创建窗口时加载,即CreateWindow()时指定,优先级高于第一种方法;
  • 动态加载,SetMenu(HWND,HMENU);

示例

#include <windows.h>

#include "resource.h"

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HINSTANCE hInstance;
	HICON hIcon;
	HCURSOR hCur;

	static BOOL b = TRUE;

	hInstance = (HINSTANCE)GetModuleHandle(NULL);

	switch (uMsg)
	{
	case WM_CREATE:
	{
		// 所有的控件必须拥有的风格是 WS_CHILD 和 WS_VISIBLE(默认显示的)
		CreateWindow(L"button", L"修改图标", WS_CHILD | WS_VISIBLE,
			// 坐标是相对于父窗口的坐标,当创建的是一个控件,那么 HMENU 保
			// 存的是一个 id ,用于标识当前的消息是有哪一个控件产生的
			10, 10, 200, 50, hWnd, (HMENU)0x1000, hInstance, NULL);

		CreateWindow(L"button", L"修改光标", WS_CHILD | WS_VISIBLE,
			10, 70, 200, 50, hWnd, (HMENU)0x1001, hInstance, NULL);

		CreateWindow(L"button", L"设置菜单", WS_CHILD | WS_VISIBLE,
			10, 130, 200, 50, hWnd, (HMENU)0x1002, hInstance, NULL);

		break;
	}
	case WM_CLOSE:
		DestroyWindow(hWnd);
		PostQuitMessage(0);
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case 0x1000:
			hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
			SetClassLong(hWnd, GCL_HICON, (LONG)hIcon);
			break;
		case 0x1001:
			hCur = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
			SetClassLong(hWnd, GCL_HCURSOR, (LONG)hCur);
			break;
		case 0x1002:
		{
			if (b == TRUE)
				SetMenu(hWnd, LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)));
			else
				SetMenu(hWnd, LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU2)));

			b = !b;

			break;
		}
		case ID_MENU1_SUB1:
		{
			MessageBoxA(hWnd, "子菜单1", "子菜单1", MB_OK);
			break;
		}
		case ID_MENU2_SUBMENU1:
		{
			MessageBoxA(hWnd, "子菜单2", "子菜单2", MB_OK);
			break;
		}
		}
	case WM_RBUTTONDOWN:
	{
		// 1. 获取到鼠标的位置
		POINT point = { LOWORD(lParam), HIWORD(lParam) };

		// 1.1 因为获取到的坐标是客户区的坐标,但是弹出的位置是桌面的位置
		ClientToScreen(hWnd, &point);

		// 2. 获取到具体的菜单
		HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));

		// 3. 获取到菜单的子菜单
		HMENU hSubMenu = GetSubMenu(hMenu, 0);

		// 4. 弹出菜单
		TrackPopupMenu(hSubMenu, TPM_LEFTALIGN, point.x, point.y, 0, hWnd, NULL);
	}
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASS wndcls = { 0 };
	MSG msg = { 0 };
	HWND hWnd;
	HMENU hMenu;

	wndcls.lpszClassName = L"MyWindow";
	wndcls.lpfnWndProc = WndProc;
	wndcls.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
	
	wndcls.hIcon = LoadIcon(NULL, IDI_QUESTION);
	wndcls.hCursor = LoadCursor(NULL, IDC_WAIT);
	wndcls.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);

	hMenu = LoadMenu(hInstance, wndcls.lpszMenuName);

	RegisterClass(&wndcls);

	hWnd = CreateWindow(wndcls.lpszClassName, L"caption",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
		NULL, hMenu,
		hInstance, 0);

	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}

0x06. 对话框

与窗口区别:

窗口对话框
原型DefWindowProc()INT_PTR
返回值LRESULTBOOL
初始化WM_CREATE,WM_INITDIALOG,依赖于资源(拖拽)
消息不处理WM_INITDIALOG不处理WM_CREATE,WM_DESTROY, WM_PAINT,但处理WM_CLOSE

对话框主要处理的消息:

  • WM_INITDIALOG
  • WM_COMMAND
  • WM_NOTIFY

模态对话框

DialogBox()创建,自带消息循环,可以自动显示,所以关闭后父窗口才能响应消息。

退出需要用EndDialog()

#include <windows.h>
#include "resource.h"

INT_PTR CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	// 对话框的回调函数中,如果消息已经被处理就返回 TRUE, 否则返回FALSE

	switch (uMsg)
	{
		// 创建,对话框在创建时响应 WM_INITDIALOG 消息
	case WM_INITDIALOG:
	{
		MessageBoxA(hWnd, "创建对话框", "提示", MB_OK);
		break;
	}

		// 关闭
	case WM_CLOSE:
	{
		// 模态对话框的关闭必须使用 EndDialog
		EndDialog(hWnd, 0);

		break;
	}

	default:
		// 没有处理的所有消息都返回 FALSE
		return FALSE;
	}

	return TRUE;
}


int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	// 实例句柄; 对话框id; 父窗口; 回调函数
	DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAINDLG), NULL, DlgProc);

	return 0;
}

非模态对话框

CreateDialog()创建,

两个api实际都调用了CreateWindow()

#include <windows.h>
#include "resource.h"

// 对话框的回调函数
INT_PTR CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	// 对话框的回调函数中,如果消息已经被处理就返回 TRUE, 否则返回FALSE

	switch (uMsg)
	{
		// 创建,对话框在创建时响应 WM_INITDIALOG 消息
	case WM_INITDIALOG:
	{
		MessageBoxA(hWnd, "创建对话框", "提示", MB_OK);
		break;
	}

	// 关闭
	case WM_CLOSE:
	{
		// 非模态对话框的关闭应该使用 DestroyWindow
		// 因为提供了一个消息循环,所以需要发送退出消息
		DestroyWindow(hWnd);
		PostQuitMessage(0);
		break;
	}

	default:
		// 没有处理的所有消息都返回 FALSE
		return FALSE;
	}

	return TRUE;
}

// 一个 GUI 程序的入口函数
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	// 实例句柄; 对话框id; 父窗口; 回调函数
	HWND hWnd = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);

	ShowWindow(hWnd, SW_SHOWNORMAL);

	// 对于一个非模态对话框,一定需要提供消息循环,且主动调用函数显示窗口,CreateDialog 是
	// 一个非阻塞函数

	MSG msg = { 0 };
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值