【Windows编程学习笔记】1:实现学生信息管理系统的简易页面

本学期非常幸运能和李青老师学习使用纯C/C++实现Win32的应用程序开发。开发过程中不使用MFC,而是直接调用WindowsAPI来实现应用程序编程,这样做的好处是能够理解界面程序的实现机制,对于日后进行不限于C++下的windows编程都是大有裨益的。

在实训过程中,我学会了设计注册窗口类,创建、显示和更新窗口,消息队列和消息响应机制,并探索了几个常用的子窗口控件(如EDIT、BUTTON、LISTBOX)的样式、通知码和消息响应机制。整个实训过程与李老师保持了较好的联系,李老师也为我的学习提供了非常重要的帮助。

建立一个Win32应用程序,而不是控制台程序

在以往的学习中,我开发的都是没有可视化界面的控制台程序。控制台程序常常被应用在测试、监控等用途,用户往往只关心数据,不在乎界面。但在实际开发应用软件时,界面就非常重要了。

 

问题的分析与算法设计

首先,用MinGW建立工程时,选择Win 32 Application而不是Win32 Console Application。Win32编程应包含头文件windows.h,它包含了很多windows的库,可以使用很多的api函数,还有窗口界面。并且函数的入口是int CALLBACK WinMain(HINSTANCE hInstance,HINSTANCE hPrvInstance,LPSTR lpCommandLine,int cmdShow)而不是int main()

在WinMain中应设计注册(RigisterClass)创建(CreatWindow)窗口类,得到其窗口句柄,然后使用ShowWindow()来显示窗口。

*基本的创建窗口代码(已包含头文件windows.h):

 

//设计注册窗口类函数
BOOL RegisterWnd(LPSTR pClassName)
{ 
    //设计窗口类  
    WNDCLASS wc = {};  //在注册窗口类之前必须填充WNDCLASS结构体
    wc.hInstance = g_hInst;  //当前程序的实例句柄
    wc.lpszClassName = pClassName;  //要向系统注册的类名
    wc.lpfnWndProc = MyWindowProc;  //定义的WindowProc的入口地址
	wc.style = CS_HREDRAW | CS_VREDRAW;//把类样式同时设为水平重画和垂直重画
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //在注册窗口类的时候,设置一个背景色
    //注册窗口类  
    return RegisterClass(&wc)!=0;//注册失败时RegisterClass函数返回0使本函数返回False
}

//自定的显示窗口函数
void DisplayWnd(HWND hWnd,int nCmdShow)  
{  
    ShowWindow(hWnd, nCmdShow);//按nCmdShow的方式显示/隐藏句柄为hWnd的窗口
    UpdateWindow( hWnd );//UpdateWindow()绕过应用程序的消息队列,直接发送WM_PAINT消息给指定窗口,更新客户区的多个无效区域
}

//自定的消息循环函数
void Message()  
{
    MSG msg={};  
    while(GetMessage(&msg,NULL,0,0))  
    {
        TranslateMessage(&msg);  //转换按键信息
        DispatchMessage(&msg);  //把消息传到WindowProc
    }
}

// 入口点  
int CALLBACK WinMain(  
    HINSTANCE hInstance,  //应用程序当前实例的句柄
    HINSTANCE hPrvInstance,  //应用程序的先前实例的句柄
    LPSTR lpCommandLine,  //指向应用程序命令行的字符串的指针
    int cmdShow)	//指明窗口如何显示
{
    RegisterWnd("MyApp");//调用自封装的设计注册窗口类函数
	HWND hMainwnd = CreateWindow(
        "MyApp",  //传参而入的类名,要和刚才注册的一致
        "学生信息管理程序",  //传参而入的窗口标题文字
        WS_OVERLAPPEDWINDOW^WS_THICKFRAME^WS_MAXIMIZEBOX,	//窗口外观样式:按位异或符'^'后接要排除的样式(6种窗口类型的组合^具有可调边框^具有最大化按钮)
        550,  //窗口相对于父级的X坐标
        100,  //窗口相对于父级的Y坐标 
        800,  //窗口的宽度
        700,  //窗口的高度
        NULL,  //没有父窗口,为NULL
        NULL,  //没有菜单,为NULL
        g_hInst,  //当前应用程序的实例句柄
        NULL);  //没有附加数据,为NULL
    //显示窗口  
    if(hMainwnd == NULL)  
        return 0;
    DisplayWnd(hMainwnd,SW_SHOW);  //SW_SHOW表示正常显示窗口
 	Message();//调用自封装的消息循环函数  
    return 0;  
}

从上面的代码中可以看到,一个Win32程序从WinMain函数入口开始,这个函数获得了当前应用程序的实例句柄以及窗口的显示方式(这可以在.exe文件右键的打开方式设置中传入)。进入后,首先应设计注册主窗口,创建一个WNDCLASS对象并填充该对象,该对象用于存储窗口的设置信息。然后使用RigisterClass函数注册这个窗口类。
注册过该窗口类后,用CreateWindow函数就可以创建这个窗口了。该函数具有多个参数,表明窗口的信息如标题文字、初始位置等,同时返回一个HWND类型的窗口句柄,作为该窗口的唯一标识符。
如果创建成功,该窗口句柄即不为NULL,这时就可以使用ShowWindow函数来显示窗口,该函数的第一参数是窗口句柄,第二参数是代表了显示方式的宏,本例中我使用了SW_SHOW表示正常显示窗口。
作为程序入口点,创建和显示主窗口后应进入消息循环,在这个循环中将接收到来自用户(键鼠)和子窗口控件的消息,转换并处理之,用这样的消息响应机制就可以实现一个程序对用户操作的响应。

 

为用户提供操作的空间,为用户的操作给予回应

刚刚建立的程序仅仅是Win32程序的一个基本框架,它创建了一个主窗口,但用户既不能在上面操作任何东西,也更不能根据操作来获得自己所需要的回应,下面要解决的就是这样的一个问题。如果说上一个问题解决的是Win32编程的基础,那么这个问题——消息处理可以说就是Win32编程要考察的核心。
问题的分析与算法设计

首先,子窗口控件的建立可以解决为用户提供操作空间的问题。Windows提供了很多已经注册好了的窗口类,这些窗口类可以直接创建使用。是在接收到WM_CREATE(窗口建立之后显示之前)后创建它们的。那么,如何知道接收了这样一个消息,这也就是窗口消息的响应问题了。在Win32编程中采用一个回调函数LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)来进行消息处理。在刚才解决的问题中(上一页代码第29行)DispatchMessage(&msg)就能将消息传给这个消息处理函数。比较有趣的是,它的函数名不会影响系统对其是消息处理函数的判断。

 

//窗口消息处理程序
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{  
	
	//GetDialogBaseUnits()返回值的低位字含有水平对话框基本单位,高位字含有垂直对话框基本单位
	int cxChar=LOWORD(GetDialogBaseUnits());//LOWORD()返回低位字
	int cyChar=HIWORD(GetDialogBaseUnits());//HIWORD()返回高位字
	
    switch(msg)
    {  
    case WM_PAINT://更新画面的消息时
		PAINTSTRUCT ps;	//声明一个PAINTSTRUCT结构体的变量,用于被填充与绘图相关的信息。
		BeginPaint(hwnd, &ps);	//BeginPaint,函数调用后开始画图
		SetTextColor(ps.hdc,RGB(0,0,255));	//设置文本颜色进入ps的hdc(环境设备描述句柄)中
		TextOut(ps.hdc, cxChar*9, cyChar*3, tx1, lstrlen(tx1));	//输出tx1字符串,MinGW中用TextOut可以通过编译
		TextOut(ps.hdc, cxChar*70, cyChar*6+5, "姓名", 4);	//姓名提示
		TextOut(ps.hdc, cxChar*70, cyChar*10, "性别", 4);	//性别提示
		TextOut(ps.hdc, cxChar*70, cyChar*14+7, "班级", 4);	//班级提示
		TextOut(ps.hdc, cxChar*70, cyChar*16+4, "学号", 4);	//学号提示
		EndPaint(hwnd, &ps);	//画完之后调用EndPaint函数,HDC(设备环境的句柄)会被自动释放
		return 0;
	case WM_CREATE://窗口创建之后显示之前时
		//创建列表框
		hwndList=CreateWindow(TEXT("LISTBOX"),//窗口类名
			NULL,//窗口标题(这个列表框没有标题)
			WS_CHILD|WS_VISIBLE|LBS_STANDARD,//窗口风格:子窗口|可见|LBS_STANDARD(相当于指定了WS_BORDER具有边框|WS_VSCROLL具有垂直滚动条|LBS_SORT插入时升序排序)
			cxChar*5,//初始x坐标
			cyChar*4,//初始y坐标
			cxChar*60+GetSystemMetrics(SM_CXVSCROLL),//初始水平尺寸
			//GetSystemMetrics()是用于得到被定义的系统数据或者系统配置信息,SM_CXVSCROLL是以像素计算的垂直滚动条的宽度
			cyChar*35,//初始垂直尺寸
			hwnd,//父窗口句柄
			(HMENU)ID_LIST,//窗口菜单句柄 或 子窗口标识(此处是后者)
			g_hInst,//程序实例句柄
			//g_hInst是本程序实例句柄,用((LPCREATESTRUCT)lParam)->hInstance或者用GetWindowLong()函数也是一样的
			NULL);//指向一个值的指针,该值传递给窗口WM_CREATE消息
case WM_COMMAND://单击时
		switch(LOWORD(wParam))	//无符号长整型参数的低16位
		{
			case 6:
				MessageBox(hwnd, TEXT("写入成功"), TEXT("提示"), MB_OK);
				break;
			case 7:
				for(int i=0;i<3;i++)
				{
				/*清空三个EDIT*/
				}
				MessageBox(hwnd, TEXT("清空成功"), TEXT("提示"), MB_OK);
				break;
			case 8:
				iIndex=SendMessage(hwndList,LB_GETCURSEL,0,0);//返回选中的项的下标
				/*文件操作*/
				SendMessage(hwndList,LB_DELETESTRING,iIndex,0);//删除选中项
				MessageBox(hwnd, TEXT("删除成功"), TEXT("提示"), MB_OK);
				break;
		}
		return 0;
    case WM_DESTROY://销毁窗口时
        PostQuitMessage(0);	//退出程序
        return 0;
    default:  
        return DefWindowProc(hwnd,msg,wParam,lParam);  //如果WndProc函数不处理这个消息,转向DefWindowProc函数来处理(系统的默认消息处理函数)
    }
	return 0;
}

当接收到一个消息时,将带着这个消息msg以及两个附加参数wParam(有高位字和低位字)和lParam传入消息处理函数。在消息处理函数中对于收到的不同消息可以作出不同的反应,这通过switch-case结构实现。
当收到更新画面WM_PAINT的消息时,将显示姓名性别学号等文字信息,它们的颜色存入了环境设备描述句柄hdc中。通过TextOut函数可以将它们显示在指定的位置。并且为了在不同操作系统下程序的容错性,这里的横纵坐标都以水平/垂直对话框的基本单位为基准,它们存放在GetDialogBaseUnits返回值的低位字和高位字中。
当收到窗口创建WM_CREATE的消息时,可以直接通过windows.h中注册好的类名来创建子窗口控件。本例中我只保留了LISTBOX列表框控件的创建,并在CreateWindow函数的第三个参数中为其指定了窗口风格。
在实际的程序中,WM_CREATE消息中我还创建了按钮控件,当他们收到WM_COMMAND单击消息时,首先通过wParam的低位字和这些按钮控件的窗口句柄相比较来判断是哪个按钮被单击,然后分别进行响应。如当删除按钮被单击时,首先通过给该列表框发送LB_GETCURSEL消息来知道列表框中的哪一项被选中,然后在文件中删除这一编号对应的学生的信息,并给列表框发送LB_DELETESTRING消息和对应项的下标,将其删除。同时弹出MessageBox提示删除成功。这种接收消息-发送消息的方法即是Win32开发的核心原理。
当收到销毁窗口WM_DESTROY消息时,通过 PostQuitMessage(0)来退出程序。如果没有这样做,就会造成窗口已关闭而程序未终止运行的错误情况。
当然,实际情况中收到的消息远不止上述这些,如果WndProc函数不处理这个消息,就要转向DefWindowProc系统默认的消息处理函数来处理。

 

在高中时用VB写过一些可视化的小程序,当时是拖拽控件、调整大小和位置,直接对控件的消息写入响应方式的,虽然开发速度快,但对其具体实现并不清楚。在这个学期纯C/C++进行Win32开发的学习过程中,我对消息循环和消息响应机制有了一定程度的理解。
在学习过程中,我看了李青老师推荐的《windows程序设计》,并在CSDN上看了几个博主写的关于win32开发的教程,自己手动写了几个实例。其中有很多深奥之处在微信上请教了李青老师,他为我提供了非常重要的帮助。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值