创建MFC工程,可选择界面:
对话框、单文档、多文档、多顶级文档
详情:
https://www.cnblogs.com/hao-hong-sheng/p/8513280.html
在资源视图打开对话框节点,从工具箱中拖入新增控件后,vs会将新控件自动添加到Resource.h中定义。但在cpp中还显示不出新添加控件的ID,需要在项目==》重新扫描解决方案,才能自动补全新控件名。
双击button自动添加了单机事件,会在cpp文件中添加消息映射message map。如:
BEGIN_MESSAGE_MAP(CMFCmessage3Dlg, CDialogEx)
ON_BN_CLICKED(IDC_bt_open, &CMFCmessage3Dlg::OnBnClickedbtopen);
END_MESSAGE_MAP()
添加控件后,右键为这个控件添加变量,变量类别为控件(不是值),才能通过这个变量去调用函数操作控件。
VAR_EDIT1.SetWindowTextW(_T("abc"));
控件变量名为VAR_EDIT1,不同于控件的ID为IDC_EDIT1
_T是一个适配宏,把字符串转为适合工程的编码。
当#if def _UNICODE时,_T会把字符以UNICODE双字节存储。
否则会以ANSI单字节存储。
函数SetwindowTextW接受的参数类型是LPCTSTR
它是32-bit指针指向一个常字符串,每字符可能占1字节或2字节,取决于是否定义为Unicode。
控件内数据换行需要 \r\n
在资源视图,为控件右键添加变量,类型为值。
变量VALUE_EDIT1为控件的值变量。
UpdateData(1);
可以让数据从控件,传输到变量,VAULE_EDIT1的值被更新。
UpdateData(0);
可以让数据从变量,传输到控件,控件中的值被更新。
其实相当于,新建一个CString变量,对控件取值,运算,再赋值回去。
VAR_EDIT1.GetWindowTextW(ms1);
ms1.Append(_T("hhh"));
VAR_EDIT1.SetWindowTextW(ms1);
HWND txtHWND = ::FindWindow(_T("Notepad"), NULL);
while (txtHWND != NULL)
{
::SendMessage(txtHWND, WM_CLOSE, NULL, NULL);
txtHWND = ::FindWindow(_T("Notepad"), NULL);
VALUE_EDIT1.Append(_T("已关闭记事本\r\n"));
}
VALUE_EDIT1.Append(_T("没有找到记事本"));
UpdateData(0);
return;
有3种发送消息的方式,send、post、广播。区别在于,send是阻塞的,要等到程序返回才继续执行,post是送到即刻返回。记得要::再调用系统的SendMessage,否则调的是作用域内的同名函数,不一样。
::SendMessage函数:
第一个参数是句柄
第二个参数是发送的常量消息,具体可发送:
https://blog.csdn.net/rbmwjyc/article/details/78981751
第三个参数wParam:指定附加的消息特定信息。通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。
第四个参数IParam:指定附加的消息特定信息。通常是一个指向内存中数据的指针。
用来获取窗口句柄
参数1:LPCTSTR lpClassName 窗口类名
参数2:LPCSTR lpWindowName 窗口名
窗口名有时经常变,比如记事本的名字可能是无标题-记事本。
实际应用1:
通过窗口类名获取txt句柄
HWND txtHWND = ::FindWindow(_T("Notepad"), NULL);
实际应用2:
通过窗口名获取winrar句柄
HWND txtHWND = ::FindWindow(NULL, _T("Downloads (评估版本)"));
::为域名解析符,表示名字冲突时引用的是全局变量,或不在当前类中的函数,在命名空间中的函数。 或者域作用符,在没声明name space std时,仍可用std::cout << "";
静默运行:
WinExec("notepad.exe",SW_HIDE);
参数详解:
----SW_HIDE 隐藏
----SW_MAXIMIZE 最大化
----SW_MINIMIZE 最小化,并把Z order顺序在此窗口之后(即窗口下一层)的窗口激活
----SW_RESTORE 激活窗口并还原为初始化大小 SW_SHOW 以当前大小和状态激活窗口
----SW_SHOW 用当前的大小和位置显示一个窗口,同时令其进入活动状态
----SW_SHOWDEFAULT 以默认方式运行
----SW_SHOWMAXIMIZED 激活窗口并最大化
----SW_SHOWMINIMIZED 激活窗口并最小化
----SW_SHOWMINNOACTIVE 最小化但不改变当前激活的窗口
----SW_SHOWNA 以当前状态显示窗口但不改变当前激活的窗口
----SW_SHOWNOACTIVATE 以初始化大小显示窗口但不改变当前激活的窗口
----SW_SHOWNORMAL 激活并显示窗口,如果是最大(小)化,窗口将会还原。第一次运行程序 时应该使用这个值
也是发送一个消息,利用函数SendMessage
但是,总是出现乱码,需要利用SendMessageA函数。
函数后的A表示ANSI字符,单字节;W表示wide宽字节Unicode。
::SendMessageA(hTxt, WM_SETTEXT, WPARAM(0), (LPARAM)newTitle2);
发送修改标题的参数要用WM_SETTEXT
第三个参数不起作用,把0强行转换为WPARAM类型。
第四个参数就是要修改成的标题字符,强制转换为LPARAM类型,但它只接受char *类型转换。
常用_T("abc")宏,可以有效解决编码问题,建议每个字符常量都使用它,再做参数。
它根据程序定义的,自动将字符串转为ANSI或Unicode
还能将char *类型转为LPCWSTR 宽字节。
用GetWindowText读取到的窗体标题文字是CString类型,需要转换为char *时,可以:
USES_CONVERSION;
char * mTitle = T2A(mCString);
或
LPCSTR ps1 = "ggg";
CString t7 = CString(ps1);
反之,char *转CString可以用A2T函数。
(注意 USES_CONVERSION在堆栈分配空间,函数结束才释放,不要用在循环中,否则不停声明它,堆栈会占满)
CString是MFC封装的字符串,操作起来非常方便,在MFC中尽量用它就好了。
CString的构造方法非常多
CString t1("aaa");
CString t2 = CString("bbb");
CString t3 = _T("ccc");
CStringA t4 = "ddd";
CString t5 = _T("e"); t5 += "ee";
CString t6 = CString("ff") + CString("f");
它附带的操纵字符串的方法也非常多,支持各种运算符 != < > +=
本质上是一个封装好的Class
LPCSTR中
L是long;P是指针;C是constant常量
它本质上是,字符数组常量指针。
(LPCWSTR是宽字符的数组常量指针。)
补充:const常量修饰的变量,表示变量的值将不能改变,若改变则报错。
1字节的字符是char
2字节的字符是wchar_t
TCHAR是根据实际环境编码自行调节。
主程序在RegisterClassEX()注册窗口类之后,不断循环GetMessage()获取消息,获取到消息后,DispatchMessage()分发消息。
消息分发后,并非直接在窗口过程中对消息做处理,而是先由系统模块查找这个窗口的窗口类,通过窗口类再找到窗口过程地址,送达后,窗口过程再实际处理消息。
WinMain函数中,
参数1:HINSTANCE hInstance 是实例句柄(handler instance),和普通句柄不一样,实例句柄是程序加载入内存后的起始地址。可以通过GetModuleHandle()获得。
参数2:HINSTANCE hPrevInstance 已弃用。
参数3:LPSTR lpCmdLine 是启动参数,如notepad c:\1.txt 其中这个路径就是启动参数。
参数4:int nCmdShow 是启动方式,隐藏启动、最大化、最小化等。
WinMain()主函数的流程是,先注册窗口类、创建并显示窗口、不断收Message、分发、直到收到消息WM_QUIT结束。
摘录注册窗口类和初始化显示:
//注册窗口类
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{ return FALSE; }
摘录一段主消息循环:
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
发现在dispatch分发消息前,还有一个translate消息。
TranslateMessage(CONST MSG *lpMsg)
它是用来处理键盘消息的,得到的参数是message,若发现是按下键盘的msg,它会自动处理,与下个抬起键盘的msg合成为,按下了某键。
WM_KEYDONW + WM_KEYUP ==>> WM_CHAR
参数仅仅为进程的实例句柄 HINSTANCE hInstance
共2步流程:
填充WNDCLASSEX结构体
调用RegisterClassEx()进行注册
WNDCLASSEX WndCls;
对WndCls的各个属性填充。
包括:
结构体大小、窗口过程的地址、实例句柄、窗口类类名、窗口类风格、窗口类背景色、鼠标句柄、图标句柄。
(赋值窗口过程地址的方法是 WndCls.lpfnwndProc = WindowProc; 把WindowProc函数名赋过去,即是地址。)
在最后,填充完结构体,把结构体地址&wcex作为注册类的参数,异步操作。
return RegisterClassExW(&wcex);
InitInstance(HINSTANCE hInstance, int nCmdShow)
传入参数是实例句柄、显示模式。
分别用:
CreateWindowEX
ShowWindow
UpdateWindow
完成初始化。
WindowProc函数,在结构体中注册了地址,表示由此函数处理各种消息。
参数1:HWND hWnd 主窗体的句柄
参数2:UINT uMsg 消息 WM_XXX
参数3:WPARAM wParam 消息附带的数据放入此参数
参数4:LPARAM lParam
注意,没有case的消息类别都在最后default中由函数DefWindowProc处理。
比如CreateWindow()时发送的WM_CREATE消息就自动处理了。只有需要特殊处理写代码的消息类别,我们用case拦下来,自定义。
两种方式,一是向目标控件发送消息;二是利用系统API函数keybd_event()、mouse_event()。
模拟键鼠最好用PostMessage,非阻塞的,比send成功率大很多。这种方法可以窗口在后台运行。
移动鼠标消息:WM_MOUSEMOVE
左键按下消息:WM_LBUTTONDOWN
左键释放消息:WM_LBUTTONUP
键盘按下消息:WM_KEYDOWN
键盘释放消息:WM_KEYUP
常用键盘击键消息:WM_CHAR
此外还有很多。
::PostMessage(hTarget, WM_KEYDOWN, VK_F5, 1);
Sleep(50);
::PostMessage(hTarget, WM_KEYUP, VK_F5, 1);
MFC中的函数比起原生API,基本都少了句柄参数,不需要填入句柄,因为MFC自动维护了句柄,想获取它,可以通过GetSafeHwnd()函数。
从输入框中直接获取Int类型,可以:
int i = GetDlgItemInt(IDC_EDIT3, FALSE, TRUE);
也可以获取字符串,存入预先定好的CString类型变量var_cs中
GetDlgItemText(IDC_EDIT2, var_cs);
总结:从输入框获取文字有3种,第一种如上,第二种是IDC_EDIT2.GetWindowText,第三种是对输入框添加value型变量VALUE_EDIT3,正向更新数据updateData(1)
VAR_EDIT2.EnableWindow(FALSE);
可以用来把编辑框暂时锁死。
用函数设定定时器,和加入定时器控件效果相同。
MFC中不需要写第一个参数句柄,所以::SetTimer是4个参数,直接SetTimer是3个参数
其余也有略微不同。
3个参数时,第一个参数是定时器ID,UINT_PTR nIDEvent 后边监听的OnTimer的参数就是它,可以switch case对不同定时器的触发,做出不同处理。
第二个参数是间隔时间,5秒就是5*1000
第三个参数写NULL就行 (MFC中)
添加函数OnTimer(UINT nIDEvent)去处理时间触发后要做的事。
需要在制作界面处,右键打开类向导,不是添加函数,而是在消息菜单中,为WM_TIMER添加处理程序,就会自动生成此函数。
停止计时,要用KillTimer(1) 参数是定时器ID
必须确保窗口处于激活状态。
::SetForegroundWindow(hTop);
函数调用失败了
SetWindowPos不能替代它,不光是最上层的问题,必须还是焦点。
先把窗口从最小化恢复显示也没用
::ShowWindow(hTop, SW_SHOWNORMAL);
直接调用键盘吧
keybd_event(0x40, 0, 0, 0);
0x40就是a 字母不需要VK_xx
鼠标:
定义一个坐标,获取当前位置,设定新位置。
POINT pt = { 0 };
::ClientToScreen(hTop, &pt);
SetCursorPos(pt.x + 100, pt.y + 200);
这样会往左上方移动,坐标起始是右下角。
两次按键之间,最好Sleep(200)
实际应用中发挥模拟鼠标操作不容易,有些游戏做到:
Hook了mouse_event。
过滤了来自PostMessage的部分消息。
使用DX响应键鼠操作。
前提:必须有窗口,有Handle才行,否则不是基于消息驱动。
有两种方法。
必须用SendMessage不能Post,而且不是太靠谱,不能数据过多。
四个参数中,
第一个填目标窗口句柄,
第二个是消息类型,WM_COPYDATA,
第三个填本窗口句柄(其实可以省略为NULL),
第四个填结构体指针。
结构体是COPYDATASTRUCT,包含:
ULoing_PTR dwData, //可以设为0
DWORD cbData, //获取长度strText.GetLength()+1
PVOID lpData。//要发送的实际内容
接收方,可以在图形化界面中添加对WM_COPYDATA消息的处理函数,类向导==》消息中添加。
(通过句柄取得进程号:GetWindowThreadProcessId)
只能用第3、4个参数传递简单的数值型数据。
系统定好的WM消息是0到0x3ff 自定的消息从WM_USER往后加就行。
如自定的第一个消息可以为:
#define WM_MYMSG1 WM_USER + 1
(在发送端、接收端都要有此定义)
在接收消息端,要补充映射,把ON_WM_MYMSG1添加进map里。
在BEGIN_MESSAGE_MAP(CUserWMDlg, CDialog)
和END_MESSAGE_MAP()之间
加入:
ON_WM_MYMSG1(WM_MYMSG1, RevcMsg)
也就是收到消息后,交给自定的RevcMsg函数去处理。
VOID XXX::RevcMsg(WPARAM wParam, LPARAM lParam)
{
xxx
}
两个参数收的是什么,完全可以自定,比如发来两个数字,由接收端计算后发回。
直接强制类型转换就好。
查看错误代码工具:
E:\VisualStudio\program\Common7\Tools\errlook.exe
用GetLastError()即可看到错误号码,打开工具,输入查询,即可获得解释。
(从vs的工具==》外部工具中可以看到,里面还有Spy++)
关于堆:
释放后的堆空间,在内存中的值会变成:
EE FE EE FE EE FE ......
C中,malloc和free是申请堆和释放堆
C++中 new关键字就是申请堆,delete关键字释放堆。
关于调试:
VC6需要在View中Debug windows中手动调出各种调试窗口
VS2019 可以在菜单==》调试==》窗口中找出:
调试线程的窗口、内存状况的窗口、寄存器窗口、看模块pdb、看汇编码的反汇编窗口
特别是反汇编窗口,有C++语句和汇编语句的对应!