在CodeBolcks+Windows API下的C++面向对象的编程教程——构建更合理的面向对象的一个Windows GUI项目(oTetris修订)
0.前言
我想通过编写一个完整的游戏程序方式引导读者体验程序设计的全过程。我将采用多种方式编写具有相同效果的应用程序,并通过不同方式形成的代码和实现方法的对比来理解程序开发更深层的知识。
了解我编写教程的思路,请参阅体现我最初想法的那篇文章中的“1.编程计划”和“2.已经编写完成的文章(目录)”:
学习编程从游戏开始——编程计划(目录) - lexyao - 博客园
我已经在下面这篇文章中介绍了用面向对象的方法构建一个Window GUI项目(oTetris)的过程:
在CodeBolcks+Windows API下的C++面向对象的编程教程——用面向对象的方法改写用向导创建一个Windows GUI项目(oTetris) - lexyao - 博客园
构建oTetris项目是参照Microsoft的BaseWindow示例改编的,虽然从功能上实现了预期的目的,但代码结构还存在许多不尽人意的地方。在这篇文章里,我将介绍优化oTetris项目代码的方法,打造一个更加合理的面向对象的项目的基础框架。
在这篇文章里,我主要讲述以下几个方面的内容:
- oTetris项目代码存在的缺点及改进的方案
- 让程序支持字符串Unicode编码格式
- 构建一个通用的面向对象的窗口基类cxWindowBase类
- 以cxWindowBase为基类构建通用公共控件类cxCommWindow类并由此衍生出状态栏类cxStatusBar
- 以cxWindowBase为基类构建通用的顶层窗口类cxTopWindow并在此基础上衍生出主窗口类cxMainFrame
- 使用wWinMain函数打造可以接受Unicode命令行参数的应用程序入口
- 结束语
1.oTetris项目代码存在的缺点及改进的方案
oTetris项目是参照Microsoft的BaseWindow示例改编而来,形成的主窗口类MainWindow中保留了BaseWindow中的一些不尽人意的代码。参照MainWindow构建的状态栏类cxStatusBar做了一些改进,但仍然有些不足。
oTetris项目的代码主要存在以下不足之处:
- 使用字符串的代码混合了Ansi和Unicode,没有形成一个统一的约定
- MainWindow和cxStatusBar都是窗口,却从不同的基类衍生
- 入口函数使用了WinMain,不支持Unicode命令行参数
下面就针对这几方面的问题做出改进,形成新的更加合理的面向对象的代码结构。
2.让程序支持字符串Unicode编码格式
随着程序设计国际化的要求日趋重要,Unicode成了事实上的标准,编写的程序能够支持Unicode成为必然的要求。
当初的计算机编程采用单字节(8位)表示字符,对于汉字这类需要双字节表示的文字会在屏幕上出现半字的问题(只显示汉字的左半部)。为了解决这个问题,业界推出了使用utf-8编码表示字符串的方法。
在Unicode官方资料中,Unicode的编码方式有三种:UTF-8、UTF-16、UTF-32。 由于UTF-8与字节序无关(无需BOM),同时兼容ASCII编码,使得UTF-8编码成为现今互联网信息编码标准而被广泛使用。
而在C++编程中所说的Unicode支持是说的用utf-16表示字符串的方式,也就是说用双字节(16位)表示字符。
使用单字节表示字符串的成为Ansi,使用双字节的称为Unicode。
Microsoft的操作系统和编程已经全面改用Unicode编码,即使保留了Ansi的函数入口,也仅仅是为了兼容早期的版本,内部代码还是要转换成Unicode从而使用Unicode的函数(更准确地说是宽字符函数)。
wxWidgets也在它的文件中表示:ANSI模式将在未来的 wxWidgets 版本中完全消失。(The ANSI mode will disappear completely in future wxWidgets releases.)
既然如此,我们还有什么理由不让我们编写的应用程序不支持Unicode呢?
那么,要支持Unicode模式需要怎么做呢?
在编写Win32 GUI程序时Code Blocks是默认使用Ansi模式的,只有在代码中使用了预编译指令#define UNICODE和#define _UNICODE才能在文件中后续的代码中支持Unicode模式。
而在wxWidget项目中则是默认支持Unicode模式的。
这说明什么呢?我想,应该是有一个设置的选项决定可以不用在自己的代码中使用#define UNICODE和#define _UNICODE就能支持Unicode模式。
这个选项在哪里呢?wxWidgets使用的是在setup中设置了#define wxUSE_UNICODE 1,而Win32项目在哪里设置呢?
由于我对CodeBlocks还不熟悉,不知道该怎么设置。如果有谁知道怎么做,还希望能告诉我。
在找到一劳永逸的办法之前,现在先采用在文件中添加预编译指令的办法。也就是在编写的每个使用字符串的文件的开始添加以下预编译指令:
#ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif
定义字符串格式时使用的标识符也要符合以下约定,通过编译指令去选择使用Ansi还是Unicode,而不是为两种格式编写使用不同字符模式的两个函数。这些约定是在winnt.h中定义的,不需要自己在编码中重新定义。
#ifdef UNICODE typedef WCHAR TCHAR, * PTCHAR ; typedef LPWSTR LPTCH, PTCH, PTSTR, LPTSTR ; typedef LPCWSTR LPCTSTR ; #else typedef char TCHAR, * PTCHAR ; typedef LPSTR LPTCH, PTCH, PTSTR, LPTSTR ; typedef LPCSTR LPCTSTR ; #endif
3.构建一个通用的面向对象的窗口基类cxWindowBase类
MainWindow和cxStatusBar都是窗口,却从不同的基类衍生。在这里,我们将从他们的基类中提取出共同的东西构建一个基类,让所有的窗口都从这个新的基类衍生。
为oTetris项目添加两个文件:oTetrisWindowBase.h和oTetrisWindowBase.cpp,在这两个文件中构建所有窗口共用的基类cxWindowBase<class DERIVED_TYPE>。将来所有窗口类共同使用的代码将会作为cxWindowBase的成员添加到这个文件中。
cxWindowBase<DERIVED_TYPE>的成员函数相对于BaseWindow做了如下修改:
- Create保持了与CreateWindowEx一致的参数,去掉了在Create内部能够获得的两个参数。ClassName通过参数提供,去掉了参数定义时的默认值,从而提供更大的适应性。
- HandleMessage保持了与WindowProc一致的参数,使得子类可以通过参数获得句柄,避免了子类中再去调用WindowHwnd函数。
- 统一使用LPCTSTR定义字符串
oTetrisWindowBase.h文件的完整代码如下:
#ifndef OTETRISWINDOWBASE_H_INCLUDED #define OTETRISWINDOWBASE_H_INCLUDED #ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif #include <tchar.h> #include <windows.h> template <class DERIVED_TYPE> class cxWindowBase { public: static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); cxWindowBase() : m_hwnd(NULL) { } BOOL Create( DWORD dwExStyle,
LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu ); inline HWND WindowHwnd() const { return m_hwnd; } protected: //virtual LPCTSTR ClassName() const = 0; virtual LRESULT HandleMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0; private: HWND m_hwnd; }; #endif // OTETRISWINDOWBASE_H_INCLUDED
oTetrisWindowBase.cpp文件的完整代码如下:
#ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif #include <tchar.h> #include <windows.h> template <class DERIVED_TYPE> BOOL cxWindowBase<DERIVED_TYPE>::Create( DWORD dwExStyle,
LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu ) { // 构建窗口实例,返回窗口句柄. m_hwnd = CreateWindowEx( dwExStyle, //正在创建的窗口的扩展窗口样式。 lpClassName, //如果 null-terminated 字符串,则指定窗口类名。 lpWindowName, //窗口名称。 如果窗口样式指定标题栏,lpWindowName 指向的窗口标题将显示在标题栏中。 //如果是创建控件(如按钮、复选框和静态控件)时,lpWindowName 指定控件的文本。 //使用 SS_ICON 样式创建静态控件时,lpWindowName 指定图标名称或标识符。 若要指定标识符,请使用语法“#num”。 dwStyle, //正在创建的窗口的样式。 X, //窗口的初始水平位置、初始垂直位置。 Y, //对于重叠窗口或弹出窗口,x 参数是窗口左上角的初始 x/y 坐标,以屏幕坐标表示。 //对于子窗口,x/y 是窗口左上角相对于父窗口工作区左上角的 x/y 坐标。 //如果 x 设置为 CW_USEDEFAULT,系统将选择窗口左上角的默认位置,并忽略 y 参数。 // CW_USEDEFAULT 仅适用于重叠窗口;如果为弹出窗口或子窗口指定,则 x 和 y 参数设置为零。 //如果使用 WS_VISIBLE 样式位设置创建重叠窗口,并将 x 参数设置为 CW_USEDEFAULT,则 y 参数将确定窗口的显示方式。 //如果 y 参数 CW_USEDEFAULT,则窗口管理器会在创建窗口后使用 SW_SHOW 标志调用 ShowWindow。 //如果 y 参数是一些其他值,则窗口管理器会调用 ShowWindow,该值作为 nCmdShow 参数。 nWidth, //窗口的宽度、高度(以设备单位为单位)。 nHeight, //对于重叠窗口,nWidth 是窗口的宽度、屏幕坐标或 CW_USEDEFAULT,nHeight 是窗口的高度。 //如果 nWidth 参数设置为 CW_USEDEFAULT,则系统将忽略 nHeight 。 //如果 nWidth 为 CW_USEDEFAULT,则系统会选择窗口的默认宽度和高度; //默认宽度从初始 x 坐标扩展到屏幕右边缘; //默认高度从初始 y 坐标扩展到图标区域的顶部。 //CW_USEDEFAULT 仅适用于重叠窗口;如果为弹出窗口或子窗口指定了 CW_USEDEFAULT,则 nWidth,nHeight 参数设置为零。 hWndParent, //正在创建的窗口的父窗口或所有者窗口的句柄。 若要创建子窗口或拥有的窗口,请提供有效的窗口句柄。 对于弹出窗口,此参数是可选的。 hMenu, //菜单的句柄,或指定子窗口标识符,具体取决于窗口样式。 //对于重叠或弹出窗口,hMenu 标识要与窗口一起使用的菜单;如果要使用类菜单,它可以 NULL。 //对于子窗口,hMenu 指定子窗口标识符,这是对话框控件用来通知其父级事件的整数值。 //应用程序确定子窗口标识符;对于具有相同父窗口的所有子窗口,它必须是唯一的。 GetModuleHandle(NULL), //要与窗口关联的模块实例的句柄。 this); //指向通过 CREATESTRUCT 结构(lpCreateParams 成员)指向的值的指针,该参数由 WM_CREATE 消息的 lParam 参数指向。 //此消息在返回之前由此函数发送到创建的窗口。 //如果应用程序调用 CreateWindow 来创建 MDI 客户端窗口,lpParam 应指向 CLIENTCREATESTRUCT 结构。 //如果 MDI 客户端窗口调用 CreateWindow 创建 MDI 子窗口,lpParam 应指向 MDICREATESTRUCT 结构。 //如果不需要其他数据,lpParam 可能会 NULL。 return (m_hwnd ? TRUE : FALSE); } template <class DERIVED_TYPE> LRESULT CALLBACK cxWindowBase<DERIVED_TYPE>::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { DERIVED_TYPE *pThis = NULL; if (uMsg == WM_NCCREATE) { CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam; pThis = (DERIVED_TYPE*)pCreate->lpCreateParams; SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis); pThis->m_hwnd = hwnd; } else { pThis = (DERIVED_TYPE*)GetWindowLongPtr(hwnd, GWLP_USERDATA); } if (pThis) { return pThis->HandleMessage(hwnd, uMsg, wParam, lParam); } else { return DefWindowProc(hwnd, uMsg, wParam, lParam); } }
调试时遇到一个解释不通的问题:
实现cxWindowBase<DERIVED_TYPE>::Create函数的代码应该是放在cpp文件中的,可是编译却不能通过。将实现cxWindowBase<DERIVED_TYPE>::Create的代码原样搬移到.h文件中,编译就顺利完成了。
如果有能够提供合理解释的请告诉我。谢谢。
4.以cxWindowBase为基类构建通用公共控件类cxCommWindow类并由此衍生出状态栏类cxStatusBar
原来的状态栏是在oTetrisCommCtrls.h和oTetrisCommCtrls.cpp两个文件中从基类CommWindow派生子类cxStatusBar形成的。
为oTetris项目添加两个文件:oTetrisCommCtrlsV2.h和oTetrisCommCtrlsV2.cpp代替原来的文件。改进后的状态栏将以cxWindowBase为基类衍生出公共控件共同的基类cxCommWindow,再以cxCommWindow为基类派生出cxStatusBar类。将来添加新的代码时,
- 将来所有公共控件共同使用的代码将会作为cxCommWindow的成员添加到这个文件中
- 状态栏独自使用的代码将会作为cxStatusBar的成员添加到这个文件中
cxCommWindow和cxStatusBar的成员函数相对于原来的CommWindow和cxStatusBar做了如下修改:
- 重写的cxCommWindow::HandleMessage直接调用DefWindowProc,成为不需要增加内容的子类共用的代码。
- 重写的Create函数,本想仅保留了当前类需要的参数,不常用的参数设置了默认值。到了实际编写的时候,cxCommWindow::Create的参数还是都留下来了,而且没有默认值。
- cxCommWindow::Create的代码中首先调用InitCommonControls(),确保dll文件已经被加载。
- cxStatusBar::Create只保留了需要的参数,其中包括dwStyle = SBARS_SIZEGRIP
- cxStatusBar::Create的代码中调用cxCommWindow::Create将lpClassName设置为STATUSCLASSNAME,而使用dwStyle|WS_CHILD | WS_VISIBLE预置了子窗口和可视 的属性。状态条自动停靠,位置和高宽都设置为默认值
- 统一使用LPCTSTR定义字符串
- 设置分格的两个函数SetStatusParts、SetStatusText都作为成员函数添加在cxStatusBar类中
oTetrisCommCtrlsV2.h文件的完整代码如下:
#ifndef OTETRISCOMMCTRLSV2_H_INCLUDED #define OTETRISCOMMCTRLSV2_H_INCLUDED #ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif #include <tchar.h> #include <windows.h> #include <commctrl.h> #include "oTetrisWindowBase.h" class cxCommWindow : public cxWindowBase<cxCommWindow> { public: BOOL Create( HWND hWndParent, HMENU hMenu, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, DWORD dwExStyle, int X, int Y, int nWidth, int nHeight ); protected: LRESULT HandleMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)override{return DefWindowProc(hwnd, uMsg, wParam, lParam);}; }; class cxStatusBar : public cxCommWindow { public: BOOL Create( HWND hWndParent, HMENU idCtrl = NULL, DWORD dwStyle = SBARS_SIZEGRIP, DWORD dwExStyle = 0 ); HRESULT SetStatusParts(int iParts, int* aPartWidth = NULL); HRESULT SetStatusText(PCWSTR pszStatusText, int iParts); protected: }; #endif // OTETRISCOMMCTRLSV2_H_INCLUDED
oTetrisCommCtrlsV2.cpp文件的完整代码如下:
#ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif //#include <tchar.h> #include <windows.h> //#include <commctrl.h> //#include "oTetrisWindowBase.h" #include "oTetrisCommCtrlsV2.h" BOOL cxCommWindow::Create( HWND hWndParent, HMENU hMenu, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, DWORD dwExStyle, int X, int Y, int nWidth, int nHeight) { // 确保已加载公共控件 DLL。 InitCommonControls(); // 调用基类函数构建窗口实例 return cxWindowBase<cxCommWindow>::Create(dwExStyle, (LPCTSTR)lpClassName, (LPCTSTR)lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu); } BOOL cxStatusBar::Create(HWND hWndParent, HMENU idCtrl, DWORD dwStyle, DWORD dwExStyle) { return cxCommWindow::Create(hWndParent, idCtrl, STATUSCLASSNAME, (PCWSTR)NULL, dwStyle|WS_CHILD | WS_VISIBLE, dwExStyle, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT); } HRESULT cxStatusBar::SetStatusParts(int iParts, int* aPartWidth) { HWND hwndParent; HWND hwndStatus; RECT rcClient; HLOCAL hloc; int i, nWidth; HRESULT hResult; hwndStatus = this->WindowHwnd(); //如果没有指定分格宽度,则自动按分格等宽分配 if(!aPartWidth) { // 获得主窗口客户区坐标 hwndParent = GetParent(hwndStatus); GetClientRect(hwndParent, &rcClient); // 为设置状态栏分格右边界坐标分配内存 hloc = LocalAlloc(LHND, sizeof(int) * iParts); aPartWidth = (PINT) LocalLock(hloc); // 计算状态栏分格右边界坐标 nWidth = rcClient.right / iParts; int rightEdge = nWidth; for(i = 0; i < iParts - 1; i++) { aPartWidth[i] = rightEdge; rightEdge += nWidth; } //最后一格设置为-1可以自动扩展到主主窗口右边界 aPartWidth[i] = -1; } hResult = SendMessage(hwndStatus, SB_SETPARTS, (WPARAM)iParts, (LPARAM)aPartWidth); // 释放内存 LocalUnlock(hloc); LocalFree(hloc); return hResult; } HRESULT cxStatusBar::SetStatusText(PCWSTR pszStatusText, int iParts) { return SendMessage(this->WindowHwnd(), SB_SETTEXT, iParts, (LPARAM)(pszStatusText)); }
经过以上修改后,在oTetrisMain.cpp文件中向主窗口添加状态栏控件的代码没有任何变化,只是包含文件从oTetrisCommCtrls.h改为oTetrisCommCtrlsV2.h。
5.以cxWindowBase为基类构建通用的顶层窗口类cxTopWindow并在此基础上衍生出主窗口类cxMainFrame
原来的主窗口是在oTetrisMain.cpp文件中从基类BaseWindow派生子类MainWindow形成的。
本来可以为oTetris项目添加两个文件:oTetrisMainWindow.h和oTetrisMainWindow.cpp代替原来的oTetrisMain.cpp文件。为了更像wxWidgets项目的层次结构,也为了更好地维护程序代码,将基础层的代码和应用层的代码分开,还是为oTetris项目添加了两层文件来代替原来的oTetrisMain.cpp文件:
- 添加oTetrisTopWindow.h和oTetrisTopWindow.cpp,首先以cxWindowBase为基类衍生出顶层窗口共同的基类cxTopWindow,再以cxTopWindow为基类派生出cxMainFrame类。
- 添加oTetrisMainV2.h和oTetrisMainV2.cpp,以cxMainFrame为基类派生出myMainWindow类。
将来添加新的代码时,
- 将来所有顶层窗口共同使用的代码将会添加为cxTopWindow类的成员
- Frame型的主窗口用到的代码添加为cxMainFrame类的成员
- 应用层的与界面相关的代码添加为myMainWindow类的成员
cxTopWindow、cxMainFrame和myMainWindow的成员函数相对于原来的BaseWindow派生子类MainWindow做了如下修改:
- 以cxWindowBase<cxTopWindow>定义cxTopWindow类
- 复制原来BaseWindow<DERIVED_TYPE>::Create的代码到cxTopWindow::Create中,将调用CreateWindowEx改为调用cxWindowBase::Create。
- 在cxTopWindow中定义虚函数WindowClassEx。
- 以cxTopWindow为基类定义cxMainFrame类。暂时没有添加任何成员,为将来的添加新的功能打下基础。
- 以cxMainFrame为基类定义myMainWindow类
- 复制原来MainWindow::WindowClassEx的代码到myMainWindow::WindowClassEx中
- 在myMainWindow::HandleMessage中改写原来MainWindow::HandleMessage的代码。为了避免添加更多事件处理程序后HandleMessage的代码太多难以维护,将HandleMessage需要的事件的处理代码改为形如OnCreate的函数。在OnCreate中添加处理WM_CREATE的代码,在OnCommand中添加处理WM_COMMAND的代码,其他类推。
oTetrisCommCtrlsV2.h文件的完整代码如下:
#ifndef OTETRISCOMMCTRLSV2_H_INCLUDED #define OTETRISCOMMCTRLSV2_H_INCLUDED #ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif #include <tchar.h> #include <windows.h> #include <commctrl.h> #include "oTetrisWindowBase.h" class cxCommWindow : public cxWindowBase<cxCommWindow> { public: BOOL Create( HWND hWndParent, HMENU hMenu, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, DWORD dwExStyle, int X, int Y, int nWidth, int nHeight ); protected: LRESULT HandleMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)override{return DefWindowProc(hwnd, uMsg, wParam, lParam);}; }; class cxStatusBar : public cxCommWindow { public: BOOL Create( HWND hWndParent, HMENU idCtrl = NULL, DWORD dwStyle = SBARS_SIZEGRIP, DWORD dwExStyle = 0 ); HRESULT SetStatusParts(int iParts); HRESULT SetStatusParts(int iParts, const int* aPartWidth); HRESULT SetStatusText(PCWSTR pszStatusText, int iParts); protected: }; #endif // OTETRISCOMMCTRLSV2_H_INCLUDED
oTetrisCommCtrlsV2.ccp文件的完整代码如下:
#ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif //#include <tchar.h> #include <windows.h> //#include <commctrl.h> //#include "oTetrisWindowBase.h" #include "oTetrisCommCtrlsV2.h" BOOL cxCommWindow::Create( HWND hWndParent, HMENU hMenu, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, DWORD dwExStyle, int X, int Y, int nWidth, int nHeight) { // 确保已加载公共控件 DLL。 InitCommonControls(); // 调用基类函数构建窗口实例 return cxWindowBase::Create(dwExStyle, (LPCTSTR)lpClassName, (LPCTSTR)lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu); } BOOL cxStatusBar::Create(HWND hWndParent, HMENU idCtrl, DWORD dwStyle, DWORD dwExStyle) { return cxCommWindow::Create(hWndParent, idCtrl, STATUSCLASSNAME, (PCWSTR)NULL, dwStyle|WS_CHILD | WS_VISIBLE, dwExStyle, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT); } HRESULT cxStatusBar::SetStatusParts(int iParts) { HWND hwndParent; HWND hwndStatus; RECT rcClient; HLOCAL hloc; PINT aPartWidth; int i, nWidth; HRESULT hResult; hwndStatus = this->WindowHwnd(); if(iParts < 0) iParts = 1; // 获得主窗口客户区坐标 hwndParent = GetParent(hwndStatus); GetClientRect(hwndParent, &rcClient); // 为设置状态栏分格右边界坐标分配内存 hloc = LocalAlloc(LHND, sizeof(int) * iParts); aPartWidth = (PINT) LocalLock(hloc); // 计算状态栏分格右边界坐标 nWidth = rcClient.right / iParts; int rightEdge = nWidth; for(i = 0; i < iParts - 1; i++) { aPartWidth[i] = rightEdge; rightEdge += nWidth; } //最后一格设置为-1可以自动扩展到主主窗口右边界 aPartWidth[i] = -1; hResult = SetStatusParts(iParts, aPartWidth); // 释放内存 LocalUnlock(hloc); LocalFree(hloc); return hResult; } HRESULT cxStatusBar::SetStatusParts(int iParts, const int* aPartWidth) { HRESULT hResult; if(!aPartWidth) { SetStatusParts(iParts); } else { hResult = SendMessage(this->WindowHwnd(), SB_SETPARTS, (WPARAM)iParts, (LPARAM)aPartWidth); } } HRESULT cxStatusBar::SetStatusText(PCWSTR pszStatusText, int iParts) { return SendMessage(this->WindowHwnd(), SB_SETTEXT, iParts, (LPARAM)(pszStatusText)); }
oTetrisMainV2.h文件的完整代码如下:
#ifndef OTETRISMAINV2_H_INCLUDED #define OTETRISMAINV2_H_INCLUDED #ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif #include <tchar.h> #include <windows.h> #include <commctrl.h> #include "oTetrisTopWindow.h" class myMainWindow : public cxMainFrame { public: protected: void WindowClassEx(PWNDCLASSEX wc)override; LRESULT HandleMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void OnCreate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); //// virtual void OnClose(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){}; void OnCommand(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // void OnDestroy(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){}; //// virtual void OnSize(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){}; void OnPaint(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); //// void OnSize(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){}; }; #endif // OTETRISMAINV2_H_INCLUDED
oTetrisMainV2.cpp文件的完整代码如下:
#ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif #include <tchar.h> #include <windows.h> #include <commctrl.h> #include "oTetrisMainV2.h" #include "oTetrisCommCtrlsV2.h" #include "aTetris.h" void myMainWindow::WindowClassEx(PWNDCLASSEX wc) { wc->hIcon = LoadIcon (wc->hInstance, _T("aaaa")); wc->hIconSm = LoadIcon (wc->hInstance, _T("aaaa")); wc->lpszMenuName = _T("aTetris"); wc->hbrBackground = (HBRUSH) COLOR_BACKGROUND; } LRESULT myMainWindow::HandleMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) /* 处理消息 handle the messages */ { case WM_CREATE: OnCreate(hwnd, uMsg, wParam, lParam); break; case WM_COMMAND: OnCommand(hwnd, uMsg, wParam, lParam); break; case WM_PAINT: OnPaint(hwnd, uMsg, wParam, lParam); break; case WM_DESTROY: PostQuitMessage(0); /* 将 WM_QUIT 发送到消息队列 send a WM_QUIT to the message queue */ break; default: /* 对于我们不处理的消息 for messages that we don't deal with */ return DefWindowProc(WindowHwnd(), uMsg, wParam, lParam); } return 0; } void myMainWindow::OnCreate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { cxStatusBar* stBar; //添加状态栏 stBar = new cxStatusBar(); stBar->Create(hwnd); /*设置分格的方法(1)*/ int paPartsa[]={300,-1}; stBar->SetStatusParts(2,paPartsa); //stBar->SetStatusParts(2); //设置分格的方法(2) stBar->SetStatusText(_T("欢迎使用多彩俄罗斯方块!"), 0); stBar->SetStatusText(_T("oTetris"), 1); } void myMainWindow::OnCommand(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(LOWORD(wParam)) { case IDM_EXIT: SendMessage(hwnd, WM_CLOSE, 0, 0); break; case IDM_ABOUT: MessageBox(hwnd, _T("This is my oTetris by Win32 GUI\n") _T("Copyright(c) lexyao,2024"), ClassName(), MB_ICONINFORMATION | MB_OK); break; } } void myMainWindow::OnPaint(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); //FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1)); EndPaint(hwnd, &ps); }
6.使用wWinMain函数打造可以接受Unicode命令行参数的应用程序入口
虽然在CodeBlocks携带的.h文件中找到了关于wWinMain的定义代码,无奈无论使用了wWinMain还是_tWinMain之后,编译运行时编译器总是提示“undefined reference to `WinMain'”,也就是说编译器只认识WinMain,其他的一概不认。这样,我们就只能使用WinMain了,这样无论是什么模式,命令行只能使用WinMain接受LPSTR类型的参数。
#define _tmain wmain #define _tWinMain wWinMain #define _tenviron _wenviron #define __targv __wargv
使用wxWidgets构建的项目的程序入口都是在xTetrisApp文件中,以wxApp的子类开始的,而Win32 GUI项目则是从入口函数WinMain开始的。
我们没有必要参照wxApp构建一个入口类,但可以创建一个oTetrisApp.cpp文件,将入口函数放在这个文件里。
操作步骤如下:
- 在 oTetris项目中新建oTetrisApp.cpp文件,
- 在oTetrisMain.cpp中添加Unicode支持的编译预处理指令,包含文件oTetrisMainV2.h
- 复制 oTetrisMain.cpp中WinMain函数的代码到oTetrisApp.cpp文件中
- 修改WinMain函数中使用MainWindow的代码,改为使用myMainWindow类,并myMainWindow的要求修改win.Create的参数
oTetrisApp.cpp文件的完整代码如下:
#ifndef UNICODE #define UNICODE #endif #if defined(UNICODE) && !defined(_UNICODE) #define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE) #define UNICODE #endif #include <tchar.h> #include <windows.h> #include "oTetrisMainV2.h" //int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow) //int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow) int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) { /* 类已注册,让我们创建程序 The class is registered, let's create the program*/ myMainWindow win; if (!win.Create(0,_T("oTetrisApp"),_T("多彩俄罗斯方块oTetris"), WS_OVERLAPPEDWINDOW)) { return 0; } // MainWindow win; // if (!win.Create(_T("多彩俄罗斯方块oTetris"), WS_OVERLAPPEDWINDOW)) // { // return 0; // } /* 使窗口在屏幕上可见 Make the window visible on the screen */ ShowWindow(win.WindowHwnd(), nCmdShow); /* 运行消息循环。它将一直运行,直到 GetMessage()返回 0 Run the message loop. It will run until GetMessage() returns 0 */ MSG msg = { }; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } /* 程序返回值为 0 - PostQuitMessage()给出的值 The program return-value is 0 - The value that PostQuitMessage() gave */ return msg.wParam; }
7.结束语
经过修订后oTetris项目的代码看起来有些繁琐,但结构更加合理,为今后添加新的功能留出了空间。
作为一个程序员,保持良好的编程习惯是很重要的。
一个好的程序,代码可读性好,可维护性好,便于以后的修改和扩展。从一开始就保持良好的编程风格,可以避免以后为了增加功能需要进行大范围代码的修改。
习惯是日积月累形成的,但只有从开始就注意养成好习惯,到最后才能形成好的习惯。
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现