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()的。

  1. 参数:bSaveAndValidate 用于指示数据传输的方向,TRUE 表示从 控件 传给 变量,FALSE 表示从 变量 传给 控件。默认值是 TRUE,即从 控件 传给 变量。
  2. 返回值: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类库

  1. afx.h,将各种MFC头文件包含在内
  2. afxwin.h,包含各种MFC窗口类,包含afx.h和windows.h
  3. afxext.h,提供扩展窗口类的支持,如工具栏,状态栏等

MFC控制台程序

通过Windows Desktop Wizard可以创建带MFC库的控制台程序。同样也可以通过这个窗口创建带MFC库的静态或动态库,但是对于MFC的dll,可能更多的是用MFC Dynamic-Link Library项目模板创建。

日志文件夹
日志文件夹

MFC架构程序

单文档视图架构程序

  1. CWinApp -> CCmdTarget -> CObject,应用程序类,负责管理应用程序流程
  2. CFrameWnd -> CWnd -> CCmdTarget -> CObject,框架窗口类,负责管理框架窗口
  3. CView -> CWnd -> CCmdTarget -> CObject,视图窗口类,负责显示数据
  4. CDocument -> CCmdTarget -> CObject,文档类,负责管理数据
  5. CSingleDocTemplate -> CDocTemplate,单文档模板类
  6. CDocManager,文档管理类

多文件视图架构程序

  1. CWinApp,应用程序类
  2. CMDIFrameWnd,多文档主框架窗口类
  3. CMDIChildWnd,多文档子框架窗口类
  4. CView,视图窗口类,负责显示数据
  5. CDocument,文档类,负责管理数据
  6. CMultiDocTemplate -> CDocTemplate,多文档模板类
  7. CDocManager,文档管理类

对话框应用程序

  1. CWinApp,应用程序类
  2. 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六大核心机制

  1. MFC程序的初始化。
  2. 运行时类型识别(RTTI)。
  3. 动态创建。
  4. 永久保存。
  5. 消息映射。
  6. 消息传递。

程序创建过程

  1. 利用框架类对象地址(pFrame)调用LoadFrame函数,创建框架窗口
  2. 在处理框架窗口的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
  3. 在处理视图窗口的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

日志文件夹