Windows编程第一课:纯手工创建一个窗体

2023-11-02

第一节 创建应用程序主窗体

1 创建消息处理函数

LRESULT CALLBACK fWinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg){
	case WM_CLOSE: //处理关闭按钮消息
		DestroyWindow(hWnd); //销毁窗口
		break;
	case WM_DESTROY:
		PostQuitMessage(0); //发送退出消息
		break;
	}
	//将不处理的函数交给系统默认处理
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}		

这个函数不用自己调用,赋值给窗体后会自动调用这个函数

关键是第二个参数,这个参数是获取到的消息标识

本例只处理两个消息:

(1) 当按下窗体右上角的关闭按钮时的消息: WM_CLOSE

(2) 当窗体进行真正销毁时:WM_DESTROY

消息队列只有在收到:WM_QUIT GetMessage()函数才会返回false值.因此必须要有一个行为能够发送这个消息.

此例中,我们在接到受到销毁窗口时发送这个消息:PostQuitMessage(0)

本例中消息处理函数:

LRESULT CALLBACK fWinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg){
	case WM_CLOSE: //处理关闭按钮消息
		DestroyWindow(hWnd); //销毁窗口
		break;
	case WM_DESTROY:
		PostQuitMessage(0); //发送退出消息
		break;
	}
	//将不处理的函数交给系统默认处理
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}		

返回值类型为: LRESULT

限定词CALLBACK作用同WINAPI 用来设定参数的入栈方式(从右往左,以及参数的管理者:调用者)

第一个参数: 窗体句柄

第二个参数: 消息事件标识ID

第三个参数: 附加消息

第四个参数: 附加消息

返回值: 系统消息处理函数(对于不处理的消息,交回给系统处理,并返回调用结果的返回值

2 创建窗体结构体实例并设置相关属性

2.1 窗体结构体: WNDCLASS
WNDCLASS wc;
2.2 设置窗体实例属性

​ 所有属性都要设置

typedef struct tagWNDCLASS {
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCTSTR   lpszMenuName;
  LPCTSTR   lpszClassName;
} WNDCLASS, *PWNDCLASS;
2.2.1 窗体显示风格
UINT style

style的值为下列值的组合

描述
CS_DBLCLKS 接受双击事件的窗体
CS_HREDRAW 调整大小后自动重绘的窗体
CS_NOCLOSE 禁用窗口菜单上的关闭按钮
CS_PARENTDC
CS_VREDRAW 客户区移动或大小调整后重绘
CS_SAVEBITS 保存被窗体挡住部分的图像,以便恢复

​ 可以上以上风格的任意组合,用(|) 连接即可

本例中设置为:

wc.style = CS_VREDRAW | CS_HREDRAW;
2.2.2 消息处理函数
WNDPROC   lpfnWndProc;

就是第一步我们定义的消息处理函数,传入函数名即可

本例我们设置为:

wc.lpfnWndProc = fWinProc;							//消息处理函数
2.2.3 窗体扩展额外空间
int       cbClsExtra;

本例赋值为0

wc.cbClsExtra = 0;	
2.2.4 窗体额外空间
int       cbWndExtra;

本例赋值为0

wc.cbWndExtra = 0;									//窗口的窗外扩展空间
2.2.5 应用程序句柄
HINSTANCE hInstance;

本例赋值为当前程序实例句柄

wc.hInstance = hInstrance;							//当前应用程序句柄

这个变量为WinMain 函数的第一个参数

2.2.6 窗体左上角图标资源句柄
HICON hIcon

如果用系统默认的图标,设置为NULL

wc.hIcon = NULL;

如果用自己的资源

wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));

函数: LoadIcon用来加Icon资源

第一个参数: 应用程序句柄

第二个参数: 一个字符串, MAKEINTRESOURCE将资源的ID转换为资源对应的字符串

2.2.7 窗体鼠标形状

​ 如果用系统默认的鼠标图标,设置为NULL

wc.hCursor = NULL;

​ 如果用自定义的资源

wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR));

函数LoadCursor用来加载鼠标形状资源

第一个参数: 应用程序句柄

第二个参数: 资源字符串,一般用MAKEINTRESOURCE将资源对应的资源标识(IDC_CURSOR)转化为对应的字符串

2.2.8 客户区背景
HBRUSH    hbrBackground;

​ 设置客户区背景为红色

wc.hbrBackground = CreateSolidBrush(RGB(255, 0, 0 ));
2.2.9 菜单名

菜单 名: LPCTSTR lpszMenuName;

wc.lpszMenuName = NULL; //不设置没有菜单
2.2.10 类名

窗体类名: LPCTSTR lpszClassName;

wc.lpszClassName = L"MyWindowClass"; //随便写,暂时不知道什么用,用中文也是可以的

3 注册窗体

函数: RegisterClass

ATOM WINAPI RegisterClass(
  _In_  const WNDCLASS *lpWndClass
);

参数: 窗体类地址

RegisterClass(&wc);

在实际应用中要对返回值做一个判断,以确认是否正确注册

if (0 == RegisterClass(&wc)) {
	MessageBox(NULL, L"注册失败", L"温馨提示", MB_OK);
	return 0; //注册失败结束程序
}

4 创建窗体句柄

HWND hWnd = CreateWindow(
  LPCTSTR lpClassName, 			//类名  字符串
  LPCTSTR lpWindowName, 		//窗体标题 字符串
  DWORD dwStyle, 				//显示风格
  int x, 						//左上角x坐标
  int y, 						//左上角y坐标
  int nWidth, 					//窗体宽度
  int nHeight, 					//窗体高度
  HWND hWndParent, 				//父窗体句柄,没有设置为NULL
  HMENU hMenu, 					//菜单栏句柄,没有设置为NULL
  HANDLE hInstance, 			//所属应用程序句柄
  PVOID lpParam 				//附加参数,没有设置为NULL
);

本例设置如下:

HWND hWnd = CreateWindow(
		L"MyWindowClass",	//窗口类型名
		L"第一个窗体",	//窗口s标题
		//带标题栏 系统菜单(关闭按钮) 最小化按钮 最大按钮
		WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,//带标题栏的窗体
		0,				//左上角x坐标					
		0,				//左上角y坐标
		600,			//窗体宽度
		600,			//窗体高度
		NULL,			//没有父窗体
		NULL,			//没有菜栏
		hInstance,		//所属应用程序句柄		
		NULL,			//附加参数
		);

第一个参数要求必须与窗体类定义时的类名一致

第三个参数: 窗体风格,可以是如下值的组合

描述
WS_BORDER 带边框的窗口
WS_CAPTION 带标题栏的窗口(包含WS_BORDER)
WS_CHILD 子窗口(必须含父窗口), 不能与WS_POPUP 共用
WS_CLIPCHILDREN 父窗口使用,在绘制含本属性的窗口时会排除子窗口占用的区域
WS_CLIPSIBLINGS
WS_DISABLED 最初禁用的窗体(禁用指不能接受用户输入,包括按钮点击等事件)
WS_DLGFRAME 不能标题栏但有边框的窗体(一般用于对话框)
WS_GROUP 指定一组控件的第一个控件,后续不带WS_GROUP的控件为后续控件,直到下一个使用WS_GROUP的控件出现
WS_HSCROLL 带水平滚动条
WS_OVERLAPPED 带标题栏与边框的重叠窗口
WS_OVERLAPPEDWINDOW 创建重叠窗口
WS_POPUP 弹出窗口,不能与WS_CHILD同时使用
WS_SIZEBOX 可调节大小同, WS_THICKFRAME
WS_SYSMENU 在非客户区带关闭(X)按钮的窗体
WS_TABSTOP 可通过TAB切换控件焦点
WS_THICKFRAME 可调节大小同, WS_SIZEBOX
WS_VISIBLE 最初可见
WS_VSCROLL 带垂直滚动条

第四个参数: 窗品显示的左上角x坐标(相对于屏幕左上角)

第五个参数: 窗品显示的左上角y坐标(相对于屏幕左上角)

第六个参数: 窗口的宽度

第七个参数: 窗口的高度

第八个参数: 父窗口,如果是顶级窗口没有父窗口,值为NULL

第九个参数: 菜单句柄,如果没有菜单栏,值为NULL

第十个参数: 应用程序句柄

第十一个参数: 附加参数 ,没有设置为NULL

注意: 在使用的时候,在后续的操作中要判断句柄是否创建成功,以免空指针成生的不良后果

if (hWnd == NULL){
    MessageBox(NULL, L"窗体创建失败", L"警告", MB_ICONWARNING);
    return 0;
}

5 显示窗体

函数原型

BOOL ShowWindow(HWND hWnd, nCmdShow)

第一个参数: 要显示的窗体句柄

第二个参数: 显示方式

显示方式列表:

描述
SW_HIDE 隐藏本窗体并激活另一个
SW_SHOW 正常显示
SW_SHOWNA 显示窗体带不激活(焦点仍在上一个窗体)
SW_SHOWNORMAL 如果窗体最小化了或者最大化了,会被还原成正常的大小

本例代码 :

ShowWindow(hWnd, SW_SHOW);

5 更新窗体

函数原型:

BOOL UpdateWindow(HWND hWnd);

参数: 要更新的窗体句柄

本例代码:

UpdateWindow(hWnd);

6 消息循环

6.1 消息类型MSG

定义一个消息对象(结构体)

MSG msg;
6.2 获取消息: GetMessage

函数原型:

BOOL WINAPI GetMessage(
  _Out_     LPMSG lpMsg,
  _In_opt_  HWND hWnd,
  _In_      UINT wMsgFilterMin,
  _In_      UINT wMsgFilterMax
);

第一个参数: 消息对象指针

第二个参数: 消息传入窗体句柄

第三个参数: 消息过滤器, 消息标识的最小值

第四个参数: 消息过滤器,消息标识的最大值

注意:

(1)最后两个参数如果不想过滤,统一设置成0. 如果设置了,不在范围内的消息将不被接收

(2) 本函数只在获取消息WM_QUIT时才会返回false,否则都为true

因此,要想中止消息队列循环,必须在适当的时机发送WM_QUIT消息

发送方式为调用函数:

PostQuitMessage(0);
6.3 消息循环
while(GetMessage(&msg, NULL, 0, 0)){
    TranslateMessage(&msg); 	//将虚拟键消息转换为字符消息
	DispatchMessage(&msg);		//将虚拟键消息分发给窗口处理函数
}

循环体里的两个函数:

(1) 函数原型

BOOL TranslateMessage(
  const MSG* lpMsg 
);

函数功能: 将虚拟按键消息转换为字符消息, 即将按键翻译字符,即处理GetMessage函数接收到的消息(msg)对象

使之能够在后续的处理中以字符的消息来处理

(2)函数原型

LONG  DispatchMessage( 
  const MSG* lpmsg 
);

函数功能: 调用消息处理函数来处理消息

完整示例代码

(1)main.cpp

#include<Windows.h>
#include "resource.h"
//窗口应用程序入口函数: WinMain
//命令行程序入入口函数: main

LRESULT CALLBACK fWinProc(HWND, UINT,WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreinstance, LPSTR lpCmdline, int nCmdShow){
	WNDCLASS wc; 
	LPCTSTR lpClassName = L"MyWindowClass";
	
	wc.style = CS_VREDRAW | CS_HREDRAW;					//设置显示风格
	wc.lpfnWndProc = fWinProc;							//消息处理函数
	wc.cbClsExtra = 0;									//窗口类的额外扩展空间
	wc.cbWndExtra = 0;									//窗口的窗外扩展空间
	wc.hInstance = hInstance;							//当前应用程序句柄
	//wc.hIcon = NULL;		//不设置窗口图标
	wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
	//wc.hCursor = NULL;		//不设置光标
	wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR));
	wc.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
	wc.lpszMenuName = NULL;
	wc.lpszClassName = lpClassName;
	//(3)注册窗体类
	if (0 == RegisterClass(&wc)){
		MessageBox(NULL, L"注册失败", L"警告", MB_OK || MB_ICONERROR);
		return 0;
	}
	// (4) 创建窗口句柄
	HWND hWnd = CreateWindow(
		//lpClassName,	//窗口类型名
		lpClassName,
		L"第一个窗体",	//窗口s标题
		//带标题栏 系统菜单(关闭按钮) 最小化按钮 最大按钮
		WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,//带标题栏的窗体
		0,				//左上角x坐标					
		0,				//左上角y坐标
		600,			//窗体宽度
		600,			//窗体高度
		NULL,			//没有父窗体
		NULL,			//没有菜栏
		hInstance,		//所属应用程序句柄		
		NULL,			//附加参数
		);
	if (hWnd == NULL){
		MessageBox(NULL, L"窗体创建失败", L"警告", MB_ICONWARNING);
		return 0;
	}
	//(5)显示窗体
	ShowWindow(hWnd, SW_SHOW);

	//(4)更新窗体
	UpdateWindow(hWnd);

	//(5) 消息队列
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0)){
		TranslateMessage(&msg);  //将虚拟键消息转换为字符消息
		DispatchMessage(&msg);		//将虚拟键消息分发给窗口处理函数
	}
	return 0;
}



LRESULT CALLBACK fWinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg){
	case WM_CLOSE: //处理关闭按钮消息
		DestroyWindow(hWnd); //销毁窗口
		break;
	case WM_DESTROY:
		PostQuitMessage(0); //发送退出消息
		break;
	}
	//将不处理的函数交给系统默认处理
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}													

资源文件: resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 Demo01创建窗口.rc 使用
//
#define IDI_ICON                        101  //这两行是关键
#define IDC_CURSOR                      102  //这两行是关键

// Next default values for new objects

#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Windows编程第一课:纯手工创建一个窗体 的相关文章

  • 无法使用 strptime() 获取秒数

    我收到 YYYY MM DDThh mm ss S Z hh mm 这种格式的日期时间 我正在尝试使用复制该值strptime如下所示 struct tm time 0 char pEnd strptime datetime Y m dT
  • 自动从 C# 代码进行调试过程并读取寄存器值

    我正在寻找一种方法来读取某个地址的 edx 注册表 就像这个问题中所问的那样 读取eax寄存器 https stackoverflow com questions 16490906 read eax register 虽然我的解决方案需要用
  • Signalr 在生产服务器中总是陷入长轮询

    当我在服务器中托管应用程序时 它会检查服务器端事件并始终回退到长轮询 服务器托管环境为Windows Server 2012 R1和IIS 7 5 无论如何 我们是否可以解决这个问题 https cloud githubuserconten
  • 模板类的不明确多重继承

    我有一个真实的情况 可以总结为以下示例 template lt typename ListenerType gt struct Notifier void add listener ListenerType struct TimeListe
  • 如何在没有 Control.Invoke() 的情况下从后台线程修改控件属性

    最近 我们遇到了一些旧版 WinForms 应用程序 我们需要更新一些新功能 在专家测试该应用程序时 发现一些旧功能被破坏 无效的跨线程操作 现在 在您认为我是新手之前 我确实有一些 Windows 窗体应用程序的经验 我不是专家 但我认为
  • SSH 主机密钥指纹与模式 C# WinSCP 不匹配

    我尝试通过 WinSCP 使用 C 连接到 FTPS 服务器 但收到此错误 SSH 主机密钥指纹 与模式不匹配 经过大量研究 我相信这与密钥的长度有关 当使用 服务器和协议信息 下的界面进行连接时 我从 WinSCP 获得的密钥是xx xx
  • 如何在我的应用程序中使用 Windows Key

    Like Windows Key E Opens a new Explorer Window And Windows Key R Displays the Run command 如何在应用程序的 KeyDown 事件中使用 Windows
  • 为什么 POSIX 允许在只读模式下超出现有文件结尾 (fseek) 进行搜索

    为什么寻找文件结尾很有用 为什么 POSIX 让我们像示例中那样在以只读方式打开的文件中进行查找 c http en cppreference com w c io fseek http en cppreference com w c io
  • 为什么禁止在 constexpr 函数中使用 goto?

    C 14 对你能做什么和不能做什么有规则constexpr功能 其中一些 没有asm 没有静态变量 看起来相当合理 但标准也不允许goto in constexpr功能 即使它允许其他控制流机制 这种区别背后的原因是什么 我以为我们已经过去
  • 跨多个控件共享事件处理程序

    在我用 C 编写的 Windows 窗体应用程序中 我有一堆按钮 当用户的鼠标悬停在按钮上时 我希望按钮的边框发生变化 目前我有以下多个实例 每个按钮一个副本 private void btnStopServer MouseEnter ob
  • 如何在 WPF RichTextBox 中跟踪 TextPointer?

    我正在尝试了解 WPF RichTextBox 中的 TextPointer 类 我希望能够跟踪它们 以便我可以将信息与文本中的区域相关联 我目前正在使用一个非常简单的示例来尝试弄清楚发生了什么 在 PreviewKeyDown 事件中 我
  • 使用 C# 在 WinRT 中获取可用磁盘空间

    DllImport kernel32 dll SetLastError true static extern bool GetDiskFreeSpaceEx string lpDirectoryName out ulong lpFreeBy
  • 如何针对 Nancy 中的 Active Directory 进行身份验证?

    这是一篇过时的文章 但是http msdn microsoft com en us library ff650308 aspx paght000026 step3 http msdn microsoft com en us library
  • 当 Cortex-M3 出现硬故障时如何保留堆栈跟踪?

    使用以下设置 基于 Cortex M3 的 C gcc arm 交叉工具链 https launchpad net gcc arm embedded 使用 C 和 C FreeRtos 7 5 3 日食月神 Segger Jlink 与 J
  • 按字典顺序对整数数组进行排序 C++

    我想按字典顺序对一个大整数数组 例如 100 万个元素 进行排序 Example input 100 21 22 99 1 927 sorted 1 100 21 22 927 99 我用最简单的方法做到了 将所有数字转换为字符串 非常昂贵
  • 为什么模板不能位于外部“C”块内?

    这是一个后续问题一个答案 https stackoverflow com questions 4866433 is it possible to typedef a pointer to extern c function type wit
  • 线程、进程和 Application.Exit()

    我的应用程序由主消息循环 GUI 和线程 Task Factory 组成 在线程中我调用一些第三方应用程序var p new Process 但是当我调用Application Exit 在消息循环中 我可以看到在线程中启动的进程仍在内存中
  • 网络参考共享类

    我用 Java 编写了一些 SOAP Web 服务 在 JBoss 5 1 上运行 其中两个共享一个类 AddressTO Web 服务在我的 ApplycationServer 上正确部署 一切都很顺利 直到我尝试在我的 C 客户端中使用
  • 如何构建印度尼西亚电话号码正则表达式

    这些是一些印度尼西亚的电话号码 08xxxxxxxxx 至少包含 11 个字符长度 08xxxxxxxxxxx 始终以 08 开头 我发现这个很有用 Regex regex new Regex 08 0 9 0 9 0 9 0 9 0 9
  • GDK3/GTK3窗口更新的精确定时

    我有一个使用 GTK 用 C 语言编写的应用程序 尽管该语言对于这个问题可能并不重要 这个应用程序有全屏gtk window与单个gtk drawing area 对于绘图区域 我已经通过注册了一个刻度回调gtk widget add ti

随机推荐