MFC常用技巧
-
- 1、 MFC中如何获取窗口的句柄
- 2、字符串
- 3、Visual C++ 64 位迁移的常见问题(数据类型、指针类型的长度问题)
- 4、c++ - 将_beginthread返回的uintptr_t转换为HANDLE是否安全
- 5、Visual Studio显示行号
- 6、VS插件Indent Guides显示大括号之间对应关系的垂直虚线
- 7、优化VS启动运行速度
- 8、根据进程句柄获取PID
- 9、双击项目中的*.rc文件,打开vs后无反应,无法打开.rc文件
- 10、复制别人项目中的界面资源
- 11、合并两个已有的.sln项目
- 12、通过vs添加成员变量向导,添加数组
- 13、头文件互相包含错误(C2061、C2146、C4430、C2079)
1、 MFC中如何获取窗口的句柄
获取主窗口的句柄
在VC的窗口类中有一个成员变量:m_hWnd,它代表这个窗口的句柄。因此在VC中通过一些得到窗口指针的函数,然后再访问这个的成员变量,应该可以得到所要的句柄。
比如用这个函数得到窗口指针,然后访问它的m_hWnd;
例如在窗口类中:this->m_hWnd
或者this-》GetSafeHwnd()
。
所以,无论在主窗口类内,还是子窗口类内,获取主窗口句柄的方法:
AfxGetMainWnd()->m_hWnd;
获取子窗口的句柄
用FindWindow,只要知道子窗口的名字即可,例如:
HANDLE h返回找到的窗口的句柄 = ::FindWindow(NULL/*窗口的类名*/, lpszWindowName/*窗口的标题*/);
举例,在MFC程序中,寻找是否在运行窗口名字为“清芝–支票机”的程序窗口:
CWnd* pWnd=FindWindow(NULL,_T(“清芝--支票机“));
FindWindowEx 比 FindWindow 多出两个句柄参数:
HWND WINAPI FindWindowEx(
In_opt HWND hWndParent, //要查找子窗口的父窗口句柄
In_opt HWND hWndChildAfter, //子窗口句柄
In_opt LPCTSTR lpszClass,
In_opt LPCTSTR lpszWindow);
获取某个窗口对象(CWnd的派生对象)指针的句柄(HWND)时,最安全的方法是使用GetSafeHwnd()函数。
通过下面的例子来看其理由:
CWnd *pWnd = FindWindow(“ExploreWClass”,NULL); //希望找到资源管理器
HWND hWnd = pwnd->m_hwnd; //得到它的HWND
执行这样的代码时,当得到的pwnd为空的时候就会出现一个“General protection error”,并关闭应用程序,因为一般不能对一个NULL指针访问其成员。
如果用下面的代码就不会出现问题(警告弹窗、中止程序),因为尽管当pWnd是NULL时,GetSafeHwnd仍然可以用,只是返回NULL而已。
CWnd *pwnd = FindWindow(“ExploreWClass”,NULL); //希望找到资源管理器
HWND hwnd = pwnd->GetSafeHwnd(); //得到它的HWND
获取窗口中控件的句柄
用 GetDlgItem函数,语法为:GetDlgItem(IDC_COMBO3)->m_hWnd;
参数为该控件的ID,返回值为:CWnd* 类型,但它是CWnd类内的函数,因此要先获取控件所在窗口的指针,例如:
CString str(_T(“ddoutput“));
//在指定控件上绘制文字
::SendMessage(AfxGetMainWnd()->GetDlgItem(IDC_COMBO3)->m_hWnd, WM_SETTEXT , 0 , (LPARAM)str.GetBuffer(0));
从句柄获得指针
用 FromHandle函数。
语法:CWnd* pWnd = CWnd::FromHandle(hWnd);
总结:获取窗口句柄(全集) vc&MFC
this->m_hwnd
HWND GetForegroundWindow(VOID);
GetSafehWnd //取你程序所在窗口类的句柄
GetActiveWindow //取当前活动窗口句柄
AfxGetMainWnd //取主窗口句柄
GetForegroundWindow //取前台窗口句柄
FindWindow
EnumWindow
参考:http://www.ekangw.net/news/2022/0924/194873.html
2、字符串
CString转char*
1、Unicode下 CString转换为char *
方法1:使用函数 T2A、W2A
CString cstr = _T("test")
//声明标识
USES_CONVERSION;
//函数T2A和W2A均支持ATL和MFC中的字符
char * pFileName = T2A(cstr);
//char * pFileName = W2A(cstr); //也可实现转换
注意:有时候可能还需要添加引用#include <afxpriv.h>
方法2:使用API:WideCharToMultiByte进行转换
CString str = _T("test");
//注意:以下n和len的值大小不同,n是按字符计算的,len是按字节计算的
int n = str.GetLength();
//获取宽字节字符的大小,大小是按字节计算的
int len = WideCharToMultiByte(CP_ACP,0,str,str.GetLength(),NULL,0,NULL,NULL);
//为多字节字符数组申请空间,数组大小为按字节计算的宽字节字节大小
char * pFileName = new char[len+1]; //以字节为单位
//宽字节编码转换成多字节编码
WideCharToMultiByte(CP_ACP,0,str,str.GetLength(),pFileName,len,NULL,NULL);
pFileName[len+1] = '/0'; //多字节字符以'/0'结束
Unicode下char *转换为CString
方法1:使用API:MultiByteToWideChar进行转换
char * pFileName = "test";
//计算char *数组大小,以字节为单位,一个汉字占两个字节
int charLen = strlen(pFileName);
//计算多字节字符的大小,按字符计算。
int len = MultiByteToWideChar(CP_ACP,0,pFileName,charLen,NULL,0);
//为宽字节字符数组申请空间,数组大小为按字节计算的多字节字符大小
TCHAR *buf = new TCHAR[len + 1];
//多字节编码转换成宽字节编码
MultiByteToWideChar(CP_ACP,0,pFileName,charLen,buf,len);
buf[len] = '/0'; //添加字符串结尾,注意不是len+1
//将TCHAR数组转换为CString
CString pWideChar;
pWideChar.Append(buf);
//删除缓冲区
delete []buf;
方法二:使用函数 A2T、A2W
char * pFileName = "test";
USES_CONVERSION;
CString s = A2T(pFileName);
//CString s = A2W(pFileName);
方法三:使用_T宏,将字符串转换为宽字符
//书写代码使用TEXT("")或_T(""),文本在UNICODE和非UNICODE程序里都通用
AfxMessageBox(_T("test string"));
注意:直接转换在基于MBCS的工程可以,但在基于Unicode字符集的工程中直接转换是不可行的,CString会以Unicode的形式来保存数据,强制类型转换只会返回第一个字符。
3、Visual C++ 64 位迁移的常见问题(数据类型、指针类型的长度问题)
为了顺利实现两种平台的源代码级可移植性,程序员应按照以下规则来编写程序或者修改已有程序:
A、不能将指针转换成 int、uint、long、ulong、dword等字长固定为32位的类型 ,如果需要对指针做运算,应把指针转换为int-ptr或 uint-ptr,这两种类型在不同平台上才有正确的字长。另外,由于handle实质上是一个指针(void *),因此把handle转换成long或ulong等类型也是不正确的。
B、如果确定需要对指针进行截断,那么应使用ptrtolong()和ptrtoulong()两个函数(在basetsd.h中定义)来进行 ,它们可以屏蔽掉指针截断警告,不过截断的结果不能够再当指针使用了。
C、当某个api函数的 out参数能返回一个指针时,应小心谨慎处理参数 ,在win32中,可以把一个ulong变量的地址进行强制转换后传递给api函数,返回的指针就保存在 ulong变量中,但在win64中,返回的指针有64位,如果使用ulong变量的话就会破坏其他变量的内容,正确并且简单的方法是直接定义一个指针变 量,把指针变量的地址作为参数传递给api函数。
参考链接:https://blog.csdn.net/u011135902/article/details/50571955
4、c++ - 将_beginthread返回的uintptr_t转换为HANDLE是否安全
在x64构建中,uintptr_t定义为64位值:
typedef unsigned __int64 uintptr_t;
因此,在这种情况下将其强制转换为HANDLE是安全的
参考链接:https://www.lmlphp.com/user/163619/article/item/3254239
5、Visual Studio显示行号
工具、选项、文本编辑器、所有语言,勾选上行号。
6、VS插件Indent Guides显示大括号之间对应关系的垂直虚线
工具、选项、文本编辑器、常规,勾选上显示结构参考线。
这是VS自带的功能(vs2017、vs2019、vs2020),不同版本的vs有的有(高版本),有的没有(低版本),此时可以再试试其他方法:
- 选择编辑->高级->View Indent Guides
- 选择“工具”菜单下的“Extension Manager”,或者扩展与更新、联机,搜索Indent Guides,下载安装重启vs即可。
- 如果扩展与更新里没有这个vs插件的话,可以点击链接下载安装:Indent Guides下载链接
- 安装完成上述链接中的Indent Guides插件后,可以在工具、选项、IndentGuide里面进行相应设置(默认设置就行)。
安装好后的插件如图所示:
备注:Go To Definition 这个插件也挺好用的,方便ctrl+鼠标左击
函数名转到定义处。
参考:https://blog.iccfish.com/2015/08/25/release_vsext_indent_guides_chs/
7、优化VS启动运行速度
对于机器配置不好的同学,可以在设置中这么设置:
工具、选项,环境、常规,禁止基于客户端性能自动调整视觉体验,然后禁止丰富客户端视觉体验,但打开请使用硬件图性加速,这样一来VS的特效虽然少了一些,但运行时就会流畅许多。
8、根据进程句柄获取PID
在R3层,可以通过OpenProcess传入参数PID获取对应的句柄;
也可以通过::GetProcessId传入参数进程句柄获取对应的进程PID,或者通过::GetThreadId传入参数进程句柄获取对应的线程TID。
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwProcessId
);
DWORD WINAPI GetProcessId(
_In_ HANDLE Process
);
DWORD WINAPI GetThreadId(
_In_ HANDLE Thread
);
9、双击项目中的*.rc文件,打开vs后无反应,无法打开.rc文件
搜索rc.exe和rcdll.dll这两个文件(本地搜索或百度搜索),拷贝到
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin
重新打开*.rc文件,如果还是无反应,点击vs菜单中的文件、打开、文件,选择.rc文件即可打开。
10、复制别人项目中的界面资源
首先,按照上面第9项用一个visual studio打开自己和别人项目中的.rc文件,如下图所示:
拖动成上图所示样式,方便操作,然后复制对方相关资源,粘贴到自己项目中即可:
这样,别人项目中的资源就成你自己的了,还不用修改资源ID,这样做非常非常方便,节省我们很多时间。
当然,在你不知道资源分别代表什么的情况下,可以双击打开相应的资源,复制该资源上的控件,然后粘贴到自己的对话框上,复制过来的资源ID不变。
还有一种方法
在打开我们自己的项目后,,你再点击vs菜单中的文件、打开、文件,打开别人项目中的.rc文件,此时你是复制不了资源的,也拖动不了,怎么办呢?
打开自己项目的解决方案资源管理器,鼠标右击源文件、添加、现有项:
将别人项目中的.rc文件添加进来,这样我们的项目中就有两个.rc资源文件了:
拖动或者复制资源都可以了,记得拖完了之后把刚才添加进来的别人的资源文件删掉了,不保存别人的东西。
然后就是常规操作了,将别人项目中相应的功能cpp文件和.h文件粘贴到自己项目的目录中,点击vs菜单中的项目、添加现有项,将自己项目目录中刚粘贴的.cpp和.h文件添加进来,这样类视图中就有对应的类了,然后再做适当修改通过编译,我们就能够很轻松的把别人项目中的功能一个一个的移植到我们的项目当中了。
11、合并两个已有的.sln项目
用vs打开其中一个项目,在解决方案上面鼠标右击、添加、现有项目,打开另一个项目的文件目录,这时候可能看不到.sln文件,如下图所示,选择文件名编辑框右边的扩展名组合框,选择解决方案文件(*.sln):
打开另一个项目的.sln,就会自动把该项目中的多个工程自动添加进来了,类视图、资源文件中也自动包含了另一个项目中的内容。
12、通过vs添加成员变量向导,添加数组
鼠标右击某个类,添加,添加成员变量,这样就打开了添加成员变量向导,例如要添加数组char StrPath[MAX_PATH]
,你在类型那里输入char,在名称那里输KeyPath[MAX_PATH]的话,会报错:
其实我们知道这个数组的类型是char[MAX_PATH],变量名是KeyPath,所以我们按照这样填写就可以了:
13、头文件互相包含错误(C2061、C2146、C4430、C2079)
一个对话框类,一个数据处理类,它们的头文件互相包含(因为要互相使用对方的类生成的对象,对话框类定义中定义了一个数据处理类对象),很容易导致很多错误:
error C2061: 语法错误: 标识符“XXDlg”
这是一个互相包含的错误,编译器不知道应该先生成哪一个类,后生成哪一个类了,这样就导致了这样一个错误。
解决方法
我们可以在没有编译的时候,在数据处理类的定义前面先声明一下对话框类(前置声明),这样就能够避免这种错误。
前置声明解决了C2061错误后,编译后还是有其他错误:
error C2146: 语法错误: 缺少“;”(在标识符“m_XXXBase”的前面)
error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int
我们在对话框类的定义前面声明一下数据处理类(前置声明),结果错误又变成了对话框类使用了未定义的数据处理类
(对话框类定义中定义了一个数据处理类对象):
error C2079: “XXXDlg::m_XXXBase”使用未定义的 class“XXXBase”
这样我们就不能将在对话框类定义中定义的这个数据处理类对象写到.h文件里面来了,我们先把对话框类定义前面那个数据处理类的前置声明删除,在.cpp中将这个数据处理类对象变成一个全局变量,并修改使用这个对象的代码即可解决这个错误。。。。。。