MFC
MFC项目模板安装
Visual Studio c++ 缺省的安装是没有MFC项目模板的,需要安装MFC项目模板。Optional components为C++ MFC for latest v143 build tools.
MSDN Download
如果希望Visual Studio在开发时能查看本地的SDK手册,需要下载Windows Driver Kit (WDK)手册。
头文件
如果需要使用MFC,简单的的头文件方式是引用afxwin.h
#include <afxwin.h>
最简单的MFC窗口程序
一个最基本的MFC程序,只需要CWinApp和CFrameWnd类对象就可以实现了。在CWinApp的InitInstance函数中,需要创建CFrameWnd对象,并赋值给CWinApp->m_pMainWnd属性就可以了启动运行了。
#include <afxwin.h>
class CMyFrameWnd :public CFrameWnd {
};
class CMyWinApp :public CWinApp {
public:
CMyWinApp() {
}
virtual BOOL InitInstance() {
CMyFrameWnd* pFrame = new CMyFrameWnd;
pFrame->Create(NULL, TEXT("MFCBase"));
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
};
CMyWinApp theApp;
全局函数 AfxGetApp()
AfxGetApp( )是全局的。AfxGetApp( )这个函数可以得到当前应用进程的指针,是CWinApp*类型的,通过这个指针可以访问到这个进程中的对象。
对象关系图
theApp -> m_pMainWnd(pFrame) -> m_pViewActive(pView) -> m_pDocument(pDoc) -> m_viewList
命令消息处理顺序
视图类->文档类->框架类->应用程序类 (默认顺序)
MyApp.h
#pragma once
#include<afxwin.h>
class MyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
class MyFrame :public CFrameWnd {
public:
MyFrame();
DECLARE_MESSAGE_MAP()
afx_msg void OnLButtonDown(UINT, CPoint);
afx_msg void OnChar(UINT, UINT, UINT);
afx_msg void OnPaint();
};
MyApp.cpp
cpp文件中需要在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间进行消息映射。
#include "MyApp.h"
MyApp app;
BOOL MyApp::InitInstance()
{
MyFrame* frame = new MyFrame();
frame->ShowWindow(SW_SHOWNORMAL);
frame->UpdateWindow();
this->m_pMainWnd = frame;
return TRUE;
}
BEGIN_MESSAGE_MAP(MyFrame, CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_CHAR()
ON_WM_PAINT()
END_MESSAGE_MAP()
MyFrame::MyFrame()
{
Create(NULL, TEXT("MFC WINDOW"));
}
void MyFrame::OnLButtonDown(UINT, CPoint point)
{
//TCHAR buf[1024];
//wsprintf(buf,TEXT("x=%d, y=%d"),point.x,point.y);
//MessageBox(buf);
CString str;
str.Format(TEXT("x=%d, y=%d"), point.x, point.y);
MessageBox(str);
}
void MyFrame::OnChar(UINT key, UINT, UINT)
{
TCHAR buf[1024];
wsprintf(buf, TEXT("Print: %c"), key);
MessageBox(buf);
//Multi-Byte Character Set
char p[100] = "aaaa";
int n = 0;
n = strlen(p);
//Unicode Character Set
wchar_t p2[100] = L"bbbb";
int m = wcslen(p2);
}
void MyFrame::OnPaint()
{
CPaintDC dc(this);
dc.TextOutW(100, 100, TEXT("Hello World"));
dc.Ellipse(10, 10, 100, 100);
}
ICON
ICON图标文件复制到项目\res文件夹下,然后右击资源视图的Icon文件夹 -> Add Resources -> Import导入图标资源。
//设置程序图标
SetClassLongPtr(this->m_hWnd, GCLP_HICON, (LONG)AfxGetApp()->LoadIconW(IDI_ICON_WIN));
Tree Control
//.h
CImageList imageList;
//.cpp
HICON icon[4];
icon[0] = AfxGetApp()->LoadIconW(IDI_ICON1);
icon[1] = AfxGetApp()->LoadIconW(IDI_ICON2);
icon[2] = AfxGetApp()->LoadIconW(IDI_ICON3);
icon[3] = AfxGetApp()->LoadIconW(IDI_ICON4);
imageList.Create(30,30,ILC_COLOR32,4,4);
for (size_t i = 0; i < 4; i++)
{
imageList.Add(icon[i]);
}
m_Tree.SetImageList(&imageList, TVSIL_NORMAL);
HTREEITEM rootItem= m_Tree.InsertItem(TEXT("根节点"), 0, 0);
HTREEITEM parentItem= m_Tree.InsertItem(TEXT("子节点"), 1, 1, rootItem);
HTREEITEM subItem1= m_Tree.InsertItem(TEXT("子节点"), 2, 2, parentItem);
HTREEITEM subItem2=m_Tree.InsertItem(TEXT("子节点"), 3, 3, parentItem);
m_Tree.SelectItem(subItem1);
void CMFCApplication2Dlg::OnTvnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
// TODO: Add your control notification handler code here
HTREEITEM subItem2 = m_Tree.GetSelectedItem();
CString str = m_Tree.GetItemText(subItem2);
MessageBox(str);
*pResult = 0;
}
Tab Control
Dialog.Border=None
Dialog.Style=Child
//.h
CDlg01 cdlg1;
CDlg1 cdlg2;
//.cpp
m_tab.InsertItem(0, TEXT("第一页"));
m_tab.InsertItem(1, TEXT("第二页"));
cdlg1.Create(IDD_DIALOG1, &m_tab);
cdlg2.Create(IDD_DIALOG2, &m_tab);
CRect rs;
m_tab.GetClientRect(&rs);
rs.top += 20;//调整子对话框在父窗口中的位置
rs.bottom -= 1;
rs.left += 1;
rs.right -= 2;
cdlg1.MoveWindow(&rs);//设置子对话框尺寸并移动到指定位置
cdlg2.MoveWindow(&rs);
cdlg1.ShowWindow(true);//分别设置隐藏和显示
cdlg2.ShowWindow(false);
m_tab.SetCurSel(0);//设置默
void CMFCApplication2Dlg::OnTcnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: Add your control notification handler code here
int CurSel = m_tab.GetCurSel();
switch (CurSel)
{
case 0:
cdlg1.ShowWindow(SW_SHOW);
cdlg2.ShowWindow(SW_HIDE);
break;
case 1:
cdlg1.ShowWindow(SW_HIDE);
cdlg2.ShowWindow(SW_SHOW);
break;
default:
;
}
*pResult = 0;
}
数据交换 BOOL UpdateData(BOOL bSaveAndValidate = TRUE);
控件的属性改变后MFC会相应修改控件关联变量的值。这种同步的改变是通过MFC为对话框类自动生成的成员函数DoDataExchange()来实现的,这也叫做对话框的数据交换和检验机制。DoDataExchange() 并不是被自动调用的,而是需要我们在程序中调用 CDialogEx::UpdateData()函数,由 UpdateData() 函数再去自动调用 DoDataExchange()的。
- 参数:bSaveAndValidate 用于指示数据传输的方向,TRUE 表示从 控件 传给 变量,FALSE 表示从 变量 传给 控件。默认值是 TRUE,即从 控件 传给 变量。
- 返回值:CDialogEx::UpdateData()函数的返回值表示操作是否成功,成功则返回TRUE,否则返回FALSE。
控件事件
通过控件的属性,可以切换到事件选项卡下添加控件的事件处理函数。事件函数添加完后,会在.cpp文件中自动创建一个空的函数实现,并把该函数关联到消息宏BEGIN_MESSAGE_MAP中。
BEGIN_MESSAGE_MAP(CMFCAdditionLearnDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_ADD_BUTTON, &CMFCAdditionLearnDlg::OnBnClickedAddButton)
END_MESSAGE_MAP()
void CMFCAdditionLearnDlg::OnBnClickedAddButton()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
m_editSum = m_editSummand + m_editAdded;
UpdateData(FALSE);
}
模式对话框
INT_PTR nRes;
CTipDlg tipDlg;
nRes = tipDlg.DoModal();
if (IDCANCEL == nRes)
return;
非模式对话框
if (NULL == m_pTipDlg)
{
m_pTipDlg = new CTipDlg();
m_pTipDlg->Create(IDD_TIP_DIALOG, this);
}
m_pTipDlg->ShowWindow(SW_SHOW);
MFC类库
- afx.h,将各种MFC头文件包含在内
- afxwin.h,包含各种MFC窗口类,包含afx.h和windows.h
- afxext.h,提供扩展窗口类的支持,如工具栏,状态栏等
MFC控制台程序
通过Windows Desktop Wizard可以创建带MFC库的控制台程序。同样也可以通过这个窗口创建带MFC库的静态或动态库,但是对于MFC的dll,可能更多的是用MFC Dynamic-Link Library项目模板创建。
MFC架构程序
单文档视图架构程序
- CWinApp -> CCmdTarget -> CObject,应用程序类,负责管理应用程序流程
- CFrameWnd -> CWnd -> CCmdTarget -> CObject,框架窗口类,负责管理框架窗口
- CView -> CWnd -> CCmdTarget -> CObject,视图窗口类,负责显示数据
- CDocument -> CCmdTarget -> CObject,文档类,负责管理数据
- CSingleDocTemplate -> CDocTemplate,单文档模板类
- CDocManager,文档管理类
多文件视图架构程序
- CWinApp,应用程序类
- CMDIFrameWnd,多文档主框架窗口类
- CMDIChildWnd,多文档子框架窗口类
- CView,视图窗口类,负责显示数据
- CDocument,文档类,负责管理数据
- CMultiDocTemplate -> CDocTemplate,多文档模板类
- CDocManager,文档管理类
对话框应用程序
- CWinApp,应用程序类
- CDialog,对话框窗口类
查看MFC源码
当设置MFC库为静态链接时,Project Property -> Advanced -> Use of MFC -> Use MFC in a Static Library,此时单步执行F11可以直接定位到MFC的源码进行调试。如果使用动态链接库则不能查看MFC源码。Visual Studio 2022创建的MFC项目,设置为静态链接库时,Debug且X64模式编译报错,Release或X86模式编译不会报错,不清楚为什么。
CWinApp构造函数
将theApp对象的地址保存到线程状态信息和模块状态信息中:
AFX_MODULE_STATE aaa;//当前程序模块状态信息
AFX_MODULE_THREAD_STATE bbb;//当前程序线程状态信息
CWinApp::CWinApp()
{
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();//获取全局变量当前程序模块状态信息aaa的地址
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;//获取全局变量bbb的地址
pThreadState->m_pCurrentWinThread = this;//将theApp的地址保存到全局变量bbb的成员变量中。
//返回theApp
AfxGetThread()
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState(); //获取bbb的地址
CWinThread* pThread = pState->m_pCurrentWinThread;
return pThread;//返回theApp的地址
}
pModuleState->m_pCurrentWinApp = this;//将theApp保存到aaa的一个成员变量中。
//返回theApp
AfxGetApp()
{
return AfxGetModuleState()->m_pCurrentWinApp;
}
}
Winmain
通过Call Stack可以查看theApp->InitInstance()的调用堆栈。_tWinMain -> AfxWinMain -> theApp->InitInstance()。
- 获取theApp地址
- 调用theApp->InitApplication,初始化应用程序数据
- 调用theApp->InitInstance,创建窗口并显示
- 调用theApp->Run,进入消息循环
- 如果没有消息,调用theApp->OnIdle实现空闲处理
-
程序退出调用theApp->ExitInstance善后处理。
WinMain() { AfxWinMain() { CWinThread* pThread = AfxGetThread(); CWinApp* pApp = AfxGetApp();
pApp->InitApplication(); pThread->InitInstance();//创建并显示窗口 pThread->Run() //消息循环 { for(;;) { while(没有消息时) OnIdle(); do{ if(GetMessage抓到WM_QUIT) return ExitInstance();//程序结束前,销毁善后处理。 }while() } } } }
窗口创建过程
CMyFrameWnd* pFrame = new CMyFrameWnd;
pFrame->Create(NULL, TEXT("MFCBase"))
{
//加载菜单
CreateEx(...,NULL,...)
{
CREATESTRUCT cs;
...
cs.lpszClass = NULL;//在PreCreateWindow中更改
...
cs.hInstance = AfxGetInstanceHandle();
PreCreateWindow(cs)
{
AfxEndDeferRegisterClass()
{
WNDCLASS wndcls;
...
wndcls.lpfnWndProc = DefWindowProc;
...
_AfxRegisterWithIcon(&wndcls, "AfxFrameOrView140sud",...
{
pWndCls->lpszClassName = "AfxFrameOrView140sud";
RegisterClass(lpWndClass)
}
}
cs.lpszClass = _afxWndFrameOrView; //"AfxFrameOrView140sud"
}
AfxHookWindowCreate(pFrame)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); //获取全局变量ccc,当前程序线程信息
::SetWindowsHookEx(WH_CBT,
_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());//利用Win32的API函数,埋下一个类型为WH_CBT的钩子
pThreadState->m_pWndInit = pFrame;//将自己new的框架类对象pFrame保存到全局变量ccc的一个成员变量中。
}
::CreateWindowEx(...);//此函数一旦执行成功,立即转到钩子处理函数。
}
}
_AfxCbtFilterHook(..., WPARAM wParam, ...)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); //获取全局变量ccc
CWnd* pWndInit = pThreadState->m_pWndInit;//获取pFrame
HWND hWnd = (HWND)wParam; //刚刚创建的窗口句柄
pWndInit->Attach(hWnd)//函数内部this为pFrame,参数为窗口句柄。
{
CHandleMap* pMap = afxMapHWND(TRUE)
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState(); //获取bbb
pState->m_pmapHWND = new CHandleMap(...);//new了一个映射类对象,并将对象地址保存到bbb的一个成员变量中。
return pState->m_pmapHWND;//返回映射类对象地址。
}
}
pMap->SetPermanent(m_hWnd = hWnd, pFrame)
{
m_permanentMap[(LPVOID)h] = permOb;
}
}
(WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,
AfxWndProc);//将窗口处理函数更改为AfxWndProc,这才是真正的窗口处理函数
消息处理函数CFrameWnd::WindowProc & AfxWndProc
如果想重写消息处理函数,MFC消息处理函数应该在CFrameWnd::WindowProc中定义,但是应该使用消息映射机制,而不是使用重写,MFC会使用钩子把消息处理函数设置为AfxWndProc,AfxWndProc会调用AfxCallWndProc,AfxCallWndProc又会调用CFrameWnd::WindowProc
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd)
{
CHandleMap* pMap = afxMapHWND()
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState(); //获取bbb
return pState->m_pmapHWND; //之前保存在bbb中的映射类对象。
}
pWnd = (CWnd*)pMap->LookupPermanent(hWnd)
{
return (CObject*)m_permanentMap.GetValueAt((LPVOID)h); 返回pFrame
}
AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam)
{
pWnd->WindowProc(nMsg, wParam, lParam);
}
}
}
LRESULT CMyFrameWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
switch (message) {
case WM_CREATE:
AfxMessageBox(_T("WM_CREATE 消息被处理"));
break;
}
return CFrameWnd::WindowProc(message, wParam, lParam);
}
MFC六大核心机制
- MFC程序的初始化。
- 运行时类型识别(RTTI)。
- 动态创建。
- 永久保存。
- 消息映射。
- 消息传递。
程序创建过程
- 利用框架类对象地址(pFrame)调用LoadFrame函数,创建框架窗口
- 在处理框架窗口的WM_CREATE消息时,动态创建视图类对象,并创建视图窗口。CFrameWnd::OnCreate -> CFrameWnd::OnCreateHelper -> CFrameWnd::OnCreateClient -> CFrameWnd::CreateView ->
CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();
->pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0), this, nID, pContext)
-> CWnd::Create -> CWnd::CreateEx - 在处理视图窗口的WM_CREATE消息时,将文档类对象和视图类对象建立关联关系。CView::OnCreate -> CDocument::AddView ->
m_viewList.AddTail(pView);pView->m_pDocument = this;
afx
afx是 application framework
afx_msg,对类向导具有重要意义
没什么意思.只是定义了这个符号而已. 这个对编译器来说,相当于什么都没有,对于人来说,我们可以看到这样的符号. 对于类向导来说.这个符号才是有意义的.它是一个消息处理函数的前缀. 类向导生成的消息函数,分发函数,事件响应函数都以这个为前缀. 如果去掉了,向导将不能识别
// Type modifier for message handlers
#ifndef afx_msg
#define afx_msg // intentional placeholder
#endif
MFC new classes & Visual C++ 2008功能包
MFC基础框架有两套类,一套是旧的,一套是在原来的类基础上继承而来,新的类会在原来的类末尾添加Ex字符,如CWinApp & CWinAppEx,暂时不清楚具体的区别。
许多新功能都依赖于新版本的CwinApp、CFrameWnd和CMDIFrameWnd类;这些类代表着大多数MFC应用程序的基础。CWinAppEx由CwinApp派生而来,应该用作应用程序对象的基类。CFrameWndEx由CframeWnd派生而来,应该用作单文档界面(SDI)框架窗口的基类。同样,CMDIFrameWndEx由CMDIFrameWnd派生而来,应该用作MDI框架窗口的基类。这些新的基类提供了支持众多新用户界面功能(如可停靠、可调整大小的窗口窗格以及工作区持久性等)所需的全部要素。
管理员权限运行
Configuration Properties -> Linker -> Manifest File -> UAC Execution Level: requireAdministrator (/level=’requireAdministrator’)
MFC中启用cout输出
editbin /SUBSYSTEM:CONSOLE $(OUTDIR)\$(ProjectName).exe