由浅入深MFC学习摘记--第三部分

由浅入深MFC摘要

  • 工欲善其事必先利其器
    • debug工具
  • 5章 Application Framework
      • General Purpose classes
      • Windows API classes
      • Application framework classes
      • High level abstractions
      • Afx 全局函数
      • MFC 宏
      • MFC 数据类型
  • 6章 MFC 程序的生命周期
    • 层次结构
    • 所需函数库
    • 所需头文件
    • MFC简易程序
    • MFC 程序的来龙去脉
      • CWinApp 和 CFrameWnd
      • CWinApp
      • CFrameWnd
      • Application object
      • WinMain
      • 第一步 AfxWinInit
      • 第二步 CWinApp::InitApplication
      • 第三部 CMyWinApp::InitInstance
      • 第四步 CFrameWnd::Create
      • 窗口类名称
      • 窗口显示与更新
      • 第五步 CWinApp::Run
      • Message Map 机制
      • 小结
    • CallBack 函数
    • 空闲时间(idle time)的处理:OnIdle
    • Dialog 与 Control
    • 通用对话框(Common Dialogs)
    • 章节总结
  • 7章 MFC主程序
    • 牢记 MFC 类层级架构
    • UI接口
    • Document、View
    • 文档模板
    • view
    • 主窗口
    • 工具栏、状态栏
    • 鼠标拖放
    • 消息映射
    • 标准菜单
    • 关于对话框
    • CEditView

读书笔记,记录要点

工欲善其事必先利其器

mfc开发流程:
由浅入深MFC学习摘记--第三部分_第1张图片

debug工具

debug版本的 mfc 程序,可以使用 trace 输出调试信息。
或者使用afxDump
由浅入深MFC学习摘记--第三部分_第2张图片
try catch的使用,catch有两个参数,第一个为错误类型,第二个是错误对象。

5章 Application Framework

简而言之就是通过使用应用框架(Application Framework)快速开发。

General Purpose classes

CObject:

  • RTTI(执行时期型别鉴识)
  • Persistence(对象保存)
  • Dynamic Creation(动态生成)
  • Diagnostic(错误诊断)

数据处理类别(collection classes)
理解为一些容器类
由浅入深MFC学习摘记--第三部分_第3张图片
其他类

  • CRect
  • CSize
  • CPoint
  • CTime 绝对时间
  • CTimeSpan 以秒数表示时间
  • CString

异常处理类
由浅入深MFC学习摘记--第三部分_第4张图片

Windows API classes

CWinThread:MFC 程序中的一个执行线程

CWinApp:整个MFC 应用程序。派生于CWinThread

CWnd: 所有窗口,不论是主框窗口、子框窗口、对话框、控制组件、view 视窗,都有一个对应的C++ 类。这些C++ 类统统派生于CWnd,也就是说,派生于CWnd 的类才能收到WM_ 窗口消息(WM_COMMAND 除外)。CWnd 对象有一个成员变量m_hWnd,就放着对应的窗口handle。所以,只要有一个CWnd 对象或CWnd 对象指针,就可以轻易获得其窗口handle

HWND hWnd = pWnd->m_hWnd;

CCmdTarget:- CWnd 的父类。继承它的类才能够处理命令消息WM_COMMAND
GDI 类
DC 类
Menu 类

Application framework classes

Document:文档
View:界面

High level abstractions

视图UI 对象,例如工具栏CToolBar、状态栏CStatusBar、对话框CDialogBar。

Afx 全局函数

函数 功能
AfxWinInit 被WinMain调用的一个函数,用做MFC GUI程序初始化的一部份
AfxBeginThread 开始一个线程
AfxEndThread 结束一个线程
AfxFormatString1 类似printf 将字符串格式化
AfxFormatString2 类似printf 将字符串格式化
AfxMessageBox 类似Windows API 函数MessageBox
AfxOutputDebugString 把调试信息输出到编译器的输出窗口
AfxGetApp 取得application object(CWinApp 衍生对象)的指针
AfxGetMainWnd 取得程序主窗口的指针。
AfxGetInstance 取得程序的instance handle
AfxRegisterClass 以自定的WNDCLASS 注册窗口类别;用来注册窗口类的函数,用已注册的窗口类可以创建一个窗口

MFC 宏

RTTI:(Run-Time Type Identification,运行时类型识别)
Serialization:序列化
Dynamic object creation:动态的对象生成

宏名称 提供机能
DECLARE_DYNAMIC 执行时期类别信息
IMPLEMENT_DYNAMIC 执行时期类别信息
DECLARE_DYNCREATE 动态生成
IMPLEMENT_DYNCREATE 动态生成
DECLARE_SERIAL 对象内容的文件读写
IMPLEMENT_SERIAL 对象内容的文件读写
DECLARE_OLECREATE OLE 对象的动态生成
IMPLEMENT_OLECREATE OLE 对象的动态生成

Message Mapping:消息映射
Command Routing: 命令传递

宏名称 提供机能
DECLARE_MESSAGE_MAP 声明消息映射表数据结构
BEGIN_MESSAGE_MAP 开始消息映射表的建立
ON_COMMAND 增加消息映射表中的项目
ON_CONTROL 增加消息映射表中的项目
ON_MESSAGE 增加消息映射表中的项目
ON_OLECMD 增加消息映射表中的项目
ON_REGISTERED_MESSAGE 增加消息映射表中的项目
ON_REGISTERED_THREAD_MESSAGE 增加消息映射表中的项目
ON_THREAD_MESSAGE 增加消息映射表中的项目
ON_UPDATE_COMMAND_UI 增加消息映射表中的项目
END_MESSAGE_MAP 结束消息映射表的建立

MFC 数据类型

和Win32 程序(SDK 程序)共同使用的数据类型

数据类型 含义
BOOL Boolean 值(布尔值,不是TRUE 就是FALSE)
BSTR 32-bit 字符指针(LPWSTR)
BYTE 8-bit 整数,无符号
COLORREF 32-bit 数值,代表一个颜色值
DWORD 32-bit 整数,无符号
LONG 32-bit 整数,带符号
LPARAM 32-bit 数值,做为窗口函数或callback 函数的一个参数
LPCSTR 32-bit 指针,指向一个常数字符串
LPSTR 32-bit 指针,指向一个字符串
LPCTSTR 32-bit 指针,指向一个常数字符串。此字符串可移植到Unicode 和DBCS(双字节字集)
LPTSTR 32-bit 指针,指向一个字符串。此字符串可移植到Unicode 和DBCS(双位组字集)
LPVOID 32-bit 指针,指向一个未指定类型的资料
LPRESULT 32-bit 数值,做为窗口函数或callback 函数的回返值
UINT 在Win16 中是一个16-bit 无符号整数,在Win32 中是一个32-bit 无符号整数。
WNDPROC 32-bit 指针,指向一个窗口函数
WORD 16-bit 整数,无符号
WPARAM 窗口函数的callback 函数的一个参数。在Win16 中是16 bits,在Win32中是32 bits。

下面这些是MFC 独特的数据类型:

数据类型 含义
POSITION 一个数值,代表collection 对象(例如数组或串行)中的元素位置。常使用于MFC collection classes。
LPCRECT 32-bit 指针,指向一个不变的RECT 结构。

常用的宏定义:

#define NULL 0
#define far //Win32 不再有far 或near memory model,而是使用所谓的flat model。pascall 函数调用习惯
#define near //也被stdcall 函数调用习惯取而代之。
#define pascal __stdcall 
//在Windows programming演化过程中曾经出现的PASCAL、CALLBACK、WINAPI、APIENTRY
//现在都代表相同的意义,就是stdcall函数调用习惯。
#define cdecl _cdecl
#define CDECL _cdecl
#define CALLBACK __stdcall //
#define WINAPI __stdcall //
#define WINAPIV __cdecl //
#define APIENTRY WINAPI //
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
#define FAR far
#define NEAR near
#define CONST const
typedef unsigned long DWORD;
typedef int BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef float FLOAT;
typedef FLOAT *PFLOAT;
typedef BOOL near *PBOOL;
typedef BOOL far *LPBOOL;
typedef BYTE near *PBYTE;
typedef BYTE far *LPBYTE;
typedef int near *PINT;
typedef int far *LPINT;
typedef WORD near *PWORD;
typedef WORD far *LPWORD;
typedef long far *LPLONG;
typedef DWORD near *PDWORD;
typedef DWORD far *LPDWORD;
typedef void far *LPVOID;
typedef CONST void far *LPCVOID;
typedef int INT;
typedef unsigned int UINT;
typedef unsigned int *PUINT;
/* Types use for passing & returning polymorphic values */
typedef UINT WPARAM;
typedef LONG LPARAM;
typedef LONG LRESULT;
typedef DWORD COLORREF;
typedef DWORD *LPCOLORREF;
typedef struct tagRECT
{
 LONG left;
 LONG top;
 LONG right;
 LONG bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;
typedef const RECT FAR* LPCRECT;
typedef struct tagPOINT
{
 LONG x;
 LONG y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;
typedef struct tagSIZE
{
 LONG cx;
 LONG cy;
} SIZE, *PSIZE, *LPSIZE;

6章 MFC 程序的生命周期

层次结构

由浅入深MFC学习摘记--第三部分_第5张图片

所需函数库

Windows C Runtime 函数库
(runtime 是一个通用抽象的术语,指的是计算机程序运行的时候所需要的一切代码库,框架,平台等)

文件名称 说明
LIBC.LIB C Runtime 函数库的静态联结版本
MSVCRT.LIB C Runtime 函数库的动态联结版本
MSVCRTD.LIB ‘D’ 表示使用于Debug 模式

DLL Import 函数库

文件名称 说明
GDI32.LIB for GDI32.DLL
USER32.LIB for USER32.DLL
KERNEL32.LIB for KERNEL32 DLL

MFC 函数库 (AFX 函数库)

所需头文件

WINDOWS.H:大合集。
STDAFX.H:预编译头文件,用于包含其他头文件
AFXWIN.H:MFC程序必须包含的头文件,内含所有MFC类。(其中包含了AFX.HAFX.H又包含了AFXVER_.HAFXVER_.H又包含了AFXV_W32.HAFXV_W32.H又包含了WINDOWS.H
AFXEXT.H:使用工具栏、状态栏需要包含的头文件
AFXDLGS.H:对话框
AFXCMN.H:win95通用组件
AFXCOLL.H:Collections Classes
AFXDLLX.H:extension DLL
AFXRES.H:MFC标准资源

预编译头 就是将.H 文件第一次编译后的结果贮存起来,第二次再编译时就可以直接从磁盘中取出来用。有效减少大型应用程序的编译时间。

MFC简易程序

新建空项目,增加如下文件:
resource.h
需要在最后面增加一个回车

#define IDM_ABOUT 100

hello.rc

#include "resource.h"
#include "afxres.h"

JJHouRIcon ICON DISCARDABLE "JJHOUR.ICO"
AFX_IDI_STD_FRAME ICON DISCARDABLE "JJHOUR.ICO"

MainMenu MENU DISCARDABLE
{
	POPUP "&Help"
		{
		 MENUITEM "&About HelloMFC...", IDM_ABOUT
		}
}

AboutBox DIALOG DISCARDABLE 34, 22, 147, 55
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About Hello"
{
	ICON "JJHouRIcon", IDC_STATIC, 11, 17, 18, 20
	LTEXT "Hello MFC 4.0", IDC_STATIC, 40, 10, 52, 8
	LTEXT "Copyright 1996 Top Studio", IDC_STATIC, 40, 25, 100, 8
	LTEXT "J.J.Hou", IDC_STATIC, 40, 40, 100, 8
	DEFPUSHBUTTON "OK", IDOK, 105, 7, 32, 14, WS_GROUP
}

stdafx.h

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently,
// but are changed infrequently

#include  // MFC core and standard components

stdafx.cpp

// stdafx.cpp : source file that includes just the standard includes
// Hello.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

hello.h


class CMyWinApp : public CWinApp
{
public:
	BOOL InitInstance(); // 
};

//---------------------------------------------------------------
class CMyFrameWnd : public CFrameWnd
{
public:
	 CMyFrameWnd(); // constructor
	 afx_msg void OnPaint(); // for WM_PAINT
	 afx_msg void OnAbout(); // for WM_COMMAND (IDM_ABOUT)
	
private:
	 DECLARE_MESSAGE_MAP() // Declare Message Map
	 static VOID CALLBACK LineDDACallback(int, int, LPARAM);//注意: callback 函数必须是"static",才能去除隐藏的'this' 指针。

};

hello.cpp


#include "stdafx.h"
#include "hello.h"
#include "resource.h"

CMyWinApp theApp; // application object

//---------------------------------------------------------------
// CMyWinApp's member
//---------------------------------------------------------------
BOOL CMyWinApp::InitInstance()
{
	m_pMainWnd = new CMyFrameWnd();
	m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}
//---------------------------------------------------------------
// CMyFrameWnd's member
//---------------------------------------------------------------
CMyFrameWnd::CMyFrameWnd()
{
	 Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault, NULL, "MainMenu"); // "MainMenu" 定义于 RC 档
}
//---------------------------------------------------------------
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	ON_COMMAND(IDM_ABOUT, OnAbout)
	ON_WM_PAINT()
END_MESSAGE_MAP()
//---------------------------------------------------------------
void CMyFrameWnd::OnPaint()
{
	CPaintDC dc(this);
	CRect rect;
	
	GetClientRect(rect);
	dc.SetTextAlign(TA_BOTTOM | TA_CENTER);
	//沿着定义的起始点与结束点组成的直线,重复执行指定的LINEDDAPROC回调函数。在线条的每个坐标点(结束点除外)上执行一次回调函数。
	::LineDDA(rect.right / 2, 0, rect.right / 2, rect.bottom / 2,(LINEDDAPROC)LineDDACallback, (LPARAM)(LPVOID)&dc);
}
//---------------------------------------------------------------
VOID CALLBACK CMyFrameWnd::LineDDACallback(int x, int y, LPARAM lpdc)
{
	static char szText[] = "Hello, MFC,textdraw";
	
	((CDC*)lpdc)->TextOut(x, y, szText, sizeof(szText) - 1);
	 for (int i = 1; i < 5000000; i++); // 纯粹是为了延迟下降速度,以利观察
}
//---------------------------------------------------------------
void CMyFrameWnd::OnAbout()
{
	CDialog about("AboutBox", this); //"AboutBox" 定义于RC 文档
	about.DoModal();
}

需要在工程目录中增加一个图标文件JJHOUR.ICO。
工程配置修改如下:
由浅入深MFC学习摘记--第三部分_第6张图片
预处理器的内容不增加也可以编译通过
由浅入深MFC学习摘记--第三部分_第7张图片

其中说明:

#define CALLBACK __stdcall // 一种函数调用习惯
#define afx_msg // intentional placeholder故意安排的一个空位置。也许以后版本会用到。

MFC 程序的来龙去脉

CWinApp 和 CFrameWnd

CWinApp 代表程序本体,包含了WinMain
CFrameWnd代表一个主框窗口(Frame Window),包含了WndProc

CWinApp

CWinApp部分成员:

class CWinApp : public CWinThread
{
// Attributes
 	// Startup args (do not change)
	HINSTANCE 	m_hInstance;
 	HINSTANCE 	m_hPrevInstance;
 	LPTSTR 		m_lpCmdLine;
 	int 		m_nCmdShow;
 	// Running args (can be changed in InitInstance)
 	LPCTSTR 	m_pszAppName; // human readable name
 	LPCTSTR 	m_pszRegistryKey; // used for registry entries
public: // set in constructor to override default
 	LPCTSTR		m_pszExeName; // executable name (no spaces)
 	LPCTSTR 	m_pszHelpFilePath; // default based on module path
 	LPCTSTR 	m_pszProfileName; // default based on app name
public:
 	// hooks for your initialization code
 	virtual BOOL InitApplication();
 	// overrides for implementation
 	virtual BOOL InitInstance();
 	virtual int ExitInstance();
 	virtual int Run();
 	virtual BOOL OnIdle(LONG lCount);
};

传统上SDK 程序的WinMain(windows程序入口) 所完成的工作现在由CWinApp的三个函数完成:

virtual BOOL InitApplication();
virtual BOOL InitInstance();
virtual int Run();

CWinThread部分成员

class CWinThread : public CCmdTarget
{
// Attributes
 	CWnd* 	m_pMainWnd; // main window (usually same AfxGetApp()->m_pMainWnd)
 	CWnd* 	m_pActiveWnd; // active main window (may not be m_pMainWnd)
 	// only valid while running
	HANDLE 	m_hThread; // this thread's HANDLE
 	DWORD 	m_nThreadID; // this thread's ID
 	int GetThreadPriority();
 	BOOL SetThreadPriority(int nPriority);
// Operations
 	DWORD SuspendThread();//挂起线程
 	DWORD ResumeThread();//唤醒线程
// Overridables
 // thread initialization
 	virtual BOOL InitInstance();
 // running and idle processing
 	virtual int Run();
 	virtual BOOL PreTranslateMessage(MSG* pMsg);
 	virtual BOOL PumpMessage(); // low level message pump
 	virtual BOOL OnIdle(LONG lCount); // return TRUE if more idle processing
public:
 	// valid after construction
 	AFX_THREADPROC m_pfnThreadProc;
...
};

CFrameWnd

CFrameWnd实现窗口过程的函数如下:

class CMyFrameWnd : public CFrameWnd
{
public:
 	CMyFrameWnd();
 	afx_msg void OnPaint();//处理	WM_PAINT
 	afx_msg void OnAbout();//处理 	WM_COMMAND 的 IDM_ABOUT
 	DECLARE_MESSAGE_MAP()
};

通过宏DECLARE_MESSAGE_MAPBEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
END_MESSAGE_MAP()实现消息与对应函数的映射。

Application object

首先查看mfc的代码WinMain.cpp

int AFXAPI AfxWinMain (...)
{
 	CWinApp* pApp = AfxGetApp();
 	AfxWinInit(...);
 	pApp->InitApplication();
 	pApp->InitInstance();
 	nReturnCode = pApp->Run();
 	AfxWinTerm();
}

然后是我们模拟的例子hello.cpp

CMyWinApp theApp; // application object
BOOL CMyWinApp::InitInstance()
{
 m_pMainWnd = new CMyFrameWnd();
 m_pMainWnd->ShowWindow(m_nCmdShow);
 m_pMainWnd->UpdateWindow();
 return TRUE;
}
CMyFrameWnd::CMyFrameWnd()
{
 Create(NULL, "Hello MFC", ...,"MainMenu");
}
void CMyFrameWnd::OnPaint() { ... }
void CMyFrameWnd::OnAbout() { ... }
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
 ON_COMMAND(IDM_ABOUT, OnAbout)
 ON_WM_PAINT()
END_MESSAGE_MAP()

theApp就是一个应用程序对象(Application object)。每一个MFC 应用程序都有一个,而且也只有这么一个。当执行hello.cpp时,这个全局对象被创建,执行其构造函数,此构造函数在父类CWinApp中:

CWinApp::CWinApp(LPCTSTR lpszAppName)
{
 m_pszAppName = lpszAppName;
 // initialize CWinThread state
 AFX_MODULE_THREAD_STATE* pThreadState = AfxGetModuleThreadState();
 pThreadState->m_pCurrentWinThread = this;
 m_hThread = ::GetCurrentThread();
 m_nThreadID = ::GetCurrentThreadId();
 // initialize CWinApp state
 AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
 pModuleState->m_pCurrentWinApp = this;
 // in non-running state until WinMain
 m_hInstance = NULL;
 m_pszHelpFilePath = NULL;
 m_pszProfileName = NULL;
 m_pszRegistryKey = NULL;
 m_pszExeName = NULL;
 m_lpCmdLine = NULL;
 m_pCmdInfo = NULL;
 ...
}

WinMain

theApp创建后就会调用WinMain函数。WinMain函数实际上是通过调用AfxWinMain函数来完成它的功能的。
_tWinMain 调用 AfxWinMain(APPMODUL.CPP)。

int AFXAPI AfxWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
    ASSERT(hPrevInstance == NULL);
    int nReturnCode = -1;
    CWinApp* pApp = AfxGetApp();
    // AFX internal initialization
    if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
        goto InitFailure;
    // App global initializations (rare)
    ASSERT_VALID(pApp);
    if (!pApp->InitApplication())
        goto InitFailure;
    ASSERT_VALID(pApp);
    // Perform specific initializations
    if (!pApp->InitInstance())
    {
        if (pApp->m_pMainWnd != NULL)
        {
            TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
            pApp->m_pMainWnd->DestroyWindow();
        }
        nReturnCode = pApp->ExitInstance();
        goto InitFailure;
    }
    ASSERT_VALID(pApp);
    nReturnCode = pApp->Run();
    ASSERT_VALID(pApp);
    InitFailure:
    AfxWinTerm();
    return nReturnCode;
}

以上代码简化下就是:

int AFXAPI AfxWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
          LPTSTR lpCmdLine, int nCmdShow)
{
 int nReturnCode = -1;
 CWinApp* pApp = AfxGetApp();
 AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
 pApp->InitApplication();
 pApp->InitInstance();
 nReturnCode = pApp->Run();
 AfxWinTerm();
 return nReturnCode;
}

AfxGetApp 是一个全局函数,定义于(afxwin1.inl)中

_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
 { return afxCurrentWinApp; }

afxCurrentWinApp 定义于(afxwin.h*)中

#define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp

AfxGetApp 其实就是取得CMyWinApp 对象指针theApp
所以afxwinmain就相当于:

CWinApp* pApp = AfxGetApp();
pApp->InitApplication();
pApp->InitInstance();
nReturnCode = pApp->Run();

也就是顺序调用:

CMyWinApp::InitApplication();
CMyWinApp::InitInstance();
CMyWinApp::Run();

考虑重写就是:

CWinApp::InitApplication(); //因为 CMyWinApp 并没有改写InitApplication
CMyWinApp::InitInstance(); //因为 CMyWinApp 改写了 InitInstance
CWinApp::Run(); 			//因为 CMyWinApp 并没有改写Run

第一步 AfxWinInit

AfxWinInit 是winmain中首先调用的,代码如下

BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,
 LPTSTR lpCmdLine, int nCmdShow)
{
    ASSERT(hPrevInstance == NULL);
    // set resource handles
    AFX_MODULE_STATE* pState = AfxGetModuleState();
    pState->m_hCurrentInstanceHandle = hInstance;
    pState->m_hCurrentResourceHandle = hInstance;
    // fill in the initial state for the application
    CWinApp* pApp = AfxGetApp();
    if (pApp != NULL)
    {
        // Windows specific initialization (not done if no CWinApp)
        pApp->m_hInstance = hInstance;
        pApp->m_hPrevInstance = hPrevInstance;
        pApp->m_lpCmdLine = lpCmdLine;
        pApp->m_nCmdShow = nCmdShow;
        pApp->SetCurrentHandles();
    }
    // initialize thread specific data (for main thread)
    if (!afxContextIsDLL)
        AfxInitThread();
    return TRUE;
}

其中 AfxInitThread

void AFXAPI AfxInitThread()
{
    if (!afxContextIsDLL)
    {
        // attempt to make the message queue bigger
        for (int cMsg = 96; !SetMessageQueue(cMsg) && (cMsg -= 8); )
        ;
        // set message filter proc
        _AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
        ASSERT(pThreadState->m_hHookOldMsgFilter == NULL);
        pThreadState->m_hHookOldMsgFilter = ::SetWindowsHookEx(WH_MSGFILTER,
            _AfxMsgFilterHook, NULL, ::GetCurrentThreadId());
        // intialize CTL3D for this thread
        _AFX_CTL3D_STATE* pCtl3dState = _afxCtl3dState;
        if (pCtl3dState->m_pfnAutoSubclass != NULL)
            (*pCtl3dState->m_pfnAutoSubclass)(AfxGetInstanceHandle());
        // allocate thread local _AFX_CTL3D_THREAD just for automatic termination
        _AFX_CTL3D_THREAD* pTemp = _afxCtl3dThread;
    }
}

第二步 CWinApp::InitApplication

BOOL CWinApp::InitApplication()
{
 	if (CDocManager::pStaticDocManager != NULL)
 	{
 		if (m_pDocManager == NULL)
 			m_pDocManager = CDocManager::pStaticDocManager;
 		CDocManager::pStaticDocManager = NULL;
	 }
 	if (m_pDocManager != NULL)
 		m_pDocManager->AddDocTemplate(NULL);
 	else
 		CDocManager::bStaticInit = FALSE;
 	return TRUE;
}

第三部 CMyWinApp::InitInstance

即调用hello.cpp中重写的 InitInstance, 在该处创建主窗口。
由浅入深MFC学习摘记--第三部分_第8张图片
应用程序一定要改写虚拟函数InitInstance,因为它在CWinApp 中只是个空函数。

第四步 CFrameWnd::Create

由浅入深MFC学习摘记--第三部分_第9张图片
new CMyFrameWnd 对象时,会进入构造函数:

CMyFrameWnd::CMyFrameWnd
{
	//调用据CFrameWnd::Create
 	Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault, NULL,"MainMenu");
}

CFrameWnd::Create 的参数如下

BOOL Create( 
	LPCTSTR 			lpszClassName,
 	LPCTSTR 			lpszWindowName,
 	DWORD 				dwStyle = WS_OVERLAPPEDWINDOW,
 	const RECT& 		rect = rectDefault,
 	CWnd* 				pParentWnd = NULL,
 	LPCTSTR 			lpszMenuName = NULL,
 	DWORD 				dwExStyle = 0,
 	CCreateContext* 	pContext = NULL 
 );

参数说明:

  1. lpszClassName:指定 WNDCLASS 窗口类型,可以为NULL。
  2. lpszWindowName: 指定窗口标题。
  3. dwStyle: 指定窗口风格。
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | WS_CAPTION |WS_SYSMENU | WS_THICKFRAME |WS_MINIMIZEBOX | WS_MAXIMIZEBOX)
  1. rect:指定窗口的位置与大小,如:
Create(NULL,"Hello MFC",WS_OVERLAPPEDWINDOW,
 	CRect(40, 60, 240, 460), // 起始位置 (40,60),寬 200,高 400)
 	NULL,
 	"MainMenu");
  1. dwExStyle:扩充风格。
  2. pParentWnd: 指定父窗口。对于一个顶级 窗口而言,此值应为NULL,表示没有父窗口(其实是有的,父窗口就是desktop 窗口)。
  3. lpszMenuName: 指定菜单。
  4. pContext: 是一个指向CCreateContext 结构的指针,用来指定在具备Document/View 架构的程序中初始化外框窗口的样式。

其简略实现如下:

//WINFRM.cpp
BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
 	LPCTSTR lpszWindowName,
 	DWORD dwStyle,
 	const RECT& rect,
 	CWnd* pParentWnd,
 	LPCTSTR lpszMenuName,
 	DWORD dwExStyle,
 	CCreateContext* pContext)
{
 	HMENU hMenu = NULL;
 	if (lpszMenuName != NULL)
 	{
 		// load in a menu that will get destroyed when window gets destroyed
 		HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
 		hMenu = ::LoadMenu(hInst, lpszMenuName);
 	}
 	m_strTitle = lpszWindowName; // save title for later
 	CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
 		rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
 		pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext);
 	return TRUE;
}

CFrameWnd 没有 CreateEx 函数,所以调用继承来的 CWnd::CreateEx;

//WINCORE.CPP
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
 LPCTSTR lpszWindowName, DWORD dwStyle,
 int x, int y, int nWidth, int nHeight,
 HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
 // allow modification of several common create parameters
 CREATESTRUCT cs;
 cs.dwExStyle = dwExStyle;
 cs.lpszClass = lpszClassName;
 cs.lpszName = lpszWindowName;
 cs.style = dwStyle;
 cs.x = x;
 cs.y = y;
 cs.cx = nWidth;
 cs.cy = nHeight;
 cs.hwndParent = hWndParent;
 cs.hMenu = nIDorHMenu;
 cs.hInstance = AfxGetInstanceHandle();
 cs.lpCreateParams = lpParam;
 PreCreateWindow(cs);
 AfxHookWindowCreate(this); //
 HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
 cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
 cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
 ...
}

PreCreateWindow 是虚函数,CWndCFrameWnd 之中都有定义,这里调用是CFrameWnd::PreCreateWindow

//WINFRM.CPP
// CFrameWnd second phase creation
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
 if (cs.lpszClass == NULL)
 {
 AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);
 cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
 }
 ...
}

中AfxDeferRegisterClass定义如下:

//AFXIMPL.H
#define AfxDeferRegisterClass(fClass) \
 ((afxRegisteredClasses & fClass) ? TRUE : AfxEndDeferRegisterClass(fClass))

如果变量afxRegisteredClasses 的值显示系统已经注册了fClass 这种窗口类别,MFC 就啥也不做;否则就调用AfxEndDeferRegisterClass(fClass)注册。

// in AFXWIN.H
#define afxRegisteredClasses AfxGetModuleState()->m_fRegisteredClasses

注册函数如下:

BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)
{
    BOOL bResult = FALSE;
    // common initialization
    WNDCLASS wndcls;
    memset(&wndcls, 0, sizeof(WNDCLASS)); // start with NULL defaults
    wndcls.lpfnWndProc = DefWindowProc;
    wndcls.hInstance = AfxGetInstanceHandle();
    wndcls.hCursor = afxData.hcurArrow;
    AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
    if (fClass & AFX_WND_REG)
    {
        // Child windows - no brush, no icon, safest default class styles
        wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
        wndcls.lpszClassName = _afxWnd;
        bResult = AfxRegisterClass(&wndcls);
        if (bResult)
            pModuleState->m_fRegisteredClasses |= AFX_WND_REG;
    }
    else if (fClass & AFX_WNDOLECONTROL_REG)
    {
        // OLE Control windows - use parent DC for speed
        wndcls.style |= CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
        wndcls.lpszClassName = _afxWndOleControl;
        bResult = AfxRegisterClass(&wndcls);
        if (bResult)
            pModuleState->m_fRegisteredClasses |= AFX_WNDOLECONTROL_REG;
    }
    else if (fClass & AFX_WNDCONTROLBAR_REG)
    {
        // Control bar windows
        wndcls.style = 0; // control bars don't handle double click
        wndcls.lpszClassName = _afxWndControlBar;
        wndcls.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
        bResult = AfxRegisterClass(&wndcls);
        if (bResult)
            pModuleState->m_fRegisteredClasses |= AFX_WNDCONTROLBAR_REG;
    }
    else if (fClass & AFX_WNDMDIFRAME_REG)
    {
        // MDI Frame window (also used for splitter window)
        wndcls.style = CS_DBLCLKS;
        wndcls.hbrBackground = NULL;
        bResult = RegisterWithIcon(&wndcls, _afxWndMDIFrame,AFX_IDI_STD_MDIFRAME);
        if (bResult)
            pModuleState->m_fRegisteredClasses |= AFX_WNDMDIFRAME_REG;
    }
    else if (fClass & AFX_WNDFRAMEORVIEW_REG)
    {
        // SDI Frame or MDI Child windows or views - normal colors
        wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
        wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
        bResult = RegisterWithIcon(&wndcls, _afxWndFrameOrView,AFX_IDI_STD_FRAME);
        if (bResult)
            pModuleState->m_fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
    }
    else if (fClass & AFX_WNDCOMMCTLS_REG)
    {
        InitCommonControls();
        bResult = TRUE;
        pModuleState->m_fRegisteredClasses |= AFX_WNDCOMMCTLS_REG;
    }
    return bResult;
}

其中的的常数定义:

#define AFX_WNDCLASS(s) \
_T("Afx") _T(s) _T("42") _STATIC_SUFFIX _UNICODE_SUFFIX _DEBUG_SUFFIX
#define AFX_WND AFX_WNDCLASS("Wnd")
#define AFX_WNDCONTROLBAR AFX_WNDCLASS("ControlBar")
#define AFX_WNDMDIFRAME AFX_WNDCLASS("MDIFrame")
#define AFX_WNDFRAMEORVIEW AFX_WNDCLASS("FrameOrView")
#define AFX_WNDOLECONTROL AFX_WNDCLASS("OleControl")

所以debug编译时这5个窗口的名称是

"AfxWnd42d"
"AfxControlBar42d"
"AfxMDIFrame42d"
"AfxFrameOrView42d"
"AfxOleControl42d"

AfxEndDeferRegisterClass注册中使用的函数:

static BOOL AFXAPI RegisterWithIcon(WNDCLASS* pWndCls,
 LPCTSTR lpszClassName, UINT nIDIcon)
{
 	pWndCls->lpszClassName = lpszClassName;
 	HINSTANCE hInst = AfxFindResourceHandle(
 	MAKEINTRESOURCE(nIDIcon), RT_GROUP_ICON);
 	if ((pWndCls->hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDIcon))) == NULL)
 	{
 		// use default icon
 		pWndCls->hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
 	}
 	return AfxRegisterClass(pWndCls);
}

BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)
{
 	WNDCLASS wndcls;
	if (GetClassInfo(lpWndClass->hInstance,
 		lpWndClass->lpszClassName, &wndcls))
 	{
 		// class already registered
 		return TRUE;
 	}
 	::RegisterClass(lpWndClass);
 	...
 	return TRUE;
}

不同类的PreCreateWindow 成员函数都是在窗口产生之前一刻被调用,准备用来注册窗口类。如果我们指定的窗口类是NULL,那么就使用系统默认类。

// in WINCORE.CPP
BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
{
 {
 	AfxDeferRegisterClass(AFX_WND_REG);
 	...
 	cs.lpszClass = _afxWnd; //(这表示CWnd 使用的窗口类别是_afxWnd)
 }
 return TRUE;
}

// in WINFRM.CPP
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
 	if (cs.lpszClass == NULL)
 	{
 		AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);
 		...
 		cs.lpszClass = _afxWndFrameOrView; //(这表示CFrameWnd 使用的窗口类别是_afxWndFrameOrView)
 	}     
 	...
}

// in WINMDI.CPP
BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
 	if (cs.lpszClass == NULL)
 	{
 		AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG);
 		//(这表示CMDIFrameWnd 使用的窗口类别是_afxWndMDIFrame)
 		...
 		cs.lpszClass = _afxWndMDIFrame; 
 	}     
 	return TRUE;
}

// in WINMDI.CPP
BOOL CMDIChildWnd::PreCreateWindow(CREATESTRUCT& cs)
{
 	...
 	return CFrameWnd::PreCreateWindow(cs); 
 	//(这表示CMDIChildWnd 使用的窗口类别是_afxWndFrameOrView)
}  
   
// in VIEWCORE.CPP
BOOL CView::PreCreateWindow(CREATESTRUCT & cs)
{
 	if (cs.lpszClass == NULL)
 	{
 		AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);
 		//(这表示CView 使用的窗口类别是_afxWndFrameOrView)
 		...     
 		cs.lpszClass = _afxWndFrameOrView; 
 	} 
 	...
}

窗口类名称

选择vs自带安装工具:
由浅入深MFC学习摘记--第三部分_第10张图片
选中并拖动 查找工具 图标到指定窗口上,可以查看窗口的信息
由浅入深MFC学习摘记--第三部分_第11张图片
但是这里没有看到示例的Afx:x:y:z:w 格式窗口名称。
此格式中:

x: 窗口风格(window style)的hex 
y: 窗口鼠标光标的hex 值 值
z: 窗口背景颜色的hex 值
w: 窗口图标(icon)的hex 值

这种情况如果修改窗口名称,需要在BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)中修改。

窗口显示与更新

由浅入深MFC学习摘记--第三部分_第12张图片
调用ShowWindow 显示窗口
调用UpdateWindow向新窗口发送WM_PAINT消息

第五步 CWinApp::Run

由浅入深MFC学习摘记--第三部分_第13张图片

窗口注册→窗口产生并显示→调用UpdateWindow→产生WM_PAINT 消息,等待被处理。

此时该执行pApp->run(),由于没有重写该方法(一般也不用重写),所以相当于调用CMyWinApp::Run()

//APPCORE.CPP
int CWinApp::Run()
{
 if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
 {
 // Not launched /Embedding or /Automation, but has no main window!
 TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting
         application.\n");
 AfxPostQuitMessage(0);
 }
 return CWinThread::Run();
}
// THRDCORE.CPP
int CWinThread::Run()
{
    // for tracking the idle time state
    BOOL bIdle = TRUE;
    LONG lIdleCount = 0;
    // acquire and dispatch messages until a WM_QUIT message is received.
    for (;;)
    {
        // phase1: check to see if we can do idle work
        while (bIdle &&
               !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
        {
            // call OnIdle while in bIdle state
            if (!OnIdle(lIdleCount++))
                bIdle = FALSE; // assume "no idle" state
        }
        // phase2: pump messages while available
        do
        {
            // pump message, but quit on WM_QUIT
            if (!PumpMessage())
                return ExitInstance();
            // reset "no idle" state after pumping "normal" message
            if (IsIdleMessage(&m_msgCur))
            {
                bIdle = TRUE;
                lIdleCount = 0;
            }
        } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
    }
    ASSERT(FALSE); // not reachable
}

BOOL CWinThread::PumpMessage()
{
    if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
    {
        return FALSE;
    }
    // process this message
    if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
    {
        ::TranslateMessage(&m_msgCur);
        ::DispatchMessage(&m_msgCur);
    }
    return TRUE;
}

获得的消息后SDK 程序的作法是调用DispatchMessage,把消息丢给窗口函数。而MFC在AfxEndDeferRegisterClass 源代码中注册四种窗口类之前已经指定窗口函数为:
wndcls.lpfnWndProc = DefWindowProc;

虽然窗口函数被指定为DefWindowProc 成员函数,但事实上消息是一个名为AfxWndProc 的全局函数处理。

WinMain 由MFC 提供,窗口类由MFC 注册完成,窗口函数也由MFC提供。

Message Map 机制

由浅入深MFC学习摘记--第三部分_第14张图片
mfc的消息映射步骤:

  1. 在头文件的CMyFrameWnd 加上DECLARE_MESSAGE_MAP
class CMyFrameWnd : public CFrameWnd
{
public:
 CMyFrameWnd();
 afx_msg void OnPaint();
 afx_msg void OnAbout();
 DECLARE_MESSAGE_MAP()
};
  1. 在cpp文件任何位置(当然不能在函数之内)使用宏:
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
 ON_WM_PAINT()
 ON_COMMAND(IDM_ABOUT, OnAbout)
END_MESSAGE_MAP()

如上,就把WM_PAINT 导到OnPaint 函数, 把WM_COMMAND(IDM_ABOUT)导到OnAbout 函数去了。

Message Map 机制中对于消息与函数间的映射关系也规定以下三种:

  • 标准Windows 消息(WM_xxx)的映射规则:
宏名称 映射的消息 消息处理函数(名称已由系统定好)
ON_WM_CHAR WM_CHAR OnChar
ON_WM_CLOSE WM_CLOSE OnClose
ON_WM_CREATE WM_CREATE OnCreate
ON_WM_DESTROY WM_DESTROY OnDestroy
ON_WM_LBUTTONDOWN WM_LBUTTONDOWN OnLButtonDown
ON_WM_LBUTTONUP WM_LBUTTONUP OnLButtonUp
ON_WM_MOUSEMOVE WM_MOUSEMOVE OnMouseMove
ON_WM_PAINT WM_PAINT OnPaint
  • 命令消息(WM_COMMAND)的一般映射规则:
    ON_COMMAND(,)
    例如:
    ON_COMMAND(IDM_ABOUT, OnAbout)
    ON_COMMAND(IDM_FILENEW, OnFileNew)
    ON_COMMAND(IDM_FILEOPEN, OnFileOpen)
    ON_COMMAND(IDM_FILESAVE, OnFileSave)

  • Notification 消息(由控件产生,例如BN_xxx)的映射机制的宏分为几种(因为控件本就分为几种):

控件 宏名称 消息处理函数
Button ON_BN_CLICKED(,) memberFxn
ComboBox ON_CBN_DBLCLK(,) memberFxn
Edit ON_EN_SETFOCUS(,) memberFxn
ListBox ON_LBN_DBLCLK(,) memberFxn

如果没有指定消息映射函数,消息会在基类中处理,这个消息在基类流窜动作称为「Message Routing」

小结

  1. 程序的诞生
  • 创建application object (即CMyWinApp theApp
  • 执行程序入口函数,调用Afx WinMain ,调用 AfxWinInit,又调用AfxInitThread
  • Afx WinMain 调用 InitApplication。这是CWinApp 的虚函数,我们通常不改写它。
  • AfxWinMain 调用 InitInstance。这是CWinApp 的虚函数,我们必须改写它。
  • CMyWinApp::InitInstance new 一个 CMyFrameWnd 对象
  • CMyFrameWnd 构造中调用Create,产生主窗口。我们在Create 参数中指定的窗口类别是NULL, 于是MFC 根据窗口种类, 自行为我们注册一个名为"AfxFrameOrView42d" 的窗口类。
  • 回到 InitInstance 中继续执行ShowWindow,显示窗口
  • 执行UpdateWindow,于是发出 WM_PAINT。
  • 回到AfxWinMain,执行Run,进入消息循环
  1. 程序的运行
  • 程序获得WM_PAINT 消息(通过CWinApp::Run 中的::GetMessage 循环)
  • WM_PAINT 经由::DispatchMessage 送到窗口函数CWnd::DefWindowProc 中。
  • CWnd::DefWindowProc 将消息发送到消息映射表格(Message Map)
  • 调用映射中对应的函数。此函数是应用程序利用BEGIN_MESSAGE_MAP 和END_MESSAGE_MAP 之间的宏映射的
  • 标准消息的处理函数有标准命名,例如WM_PAINT 必然由OnPaint 处理。
  1. 程序的死亡
  • 使用者选按【File/Close】,于是发出 WM_CLOSE。
  • CMyFrameWnd 并没有设置 WM_CLOSE 处理函数,于是交给默认处理函数
  • 默认函数对于WM_CLOSE 的处理方式是调用::DestroyWindow, 并因而发出 WM_DESTROY
  • 默认 WM_DESTROY 处理方式是调用::PostQuitMessage,因此发出WM_QUIT。
  • CWinApp::Run 收到 WM_QUIT 后会结束其内部之消息循环, 然后调用ExitInstance,这是CWinApp 的一个虚函数。
  • 如果 CMyWinApp 改写了 ExitInstance , 那么 CWinApp::Run 所调用的就是CMyWinApp::ExitInstance,否则就是 CWinApp::ExitInstance。
  • 最后回到 AfxWinMain,执行 AfxWinTerm,结束程序

CallBack 函数

LineDDA函数:
void WINAPI LineDDA(int, int, int, int, LINEDDAPROC, LPARAM);
利用前四个参数指定屏幕上任意两点的(x,y) 座标,此函数将以Bresenham 算法计算出通过两点之直线中的每一个屏幕图素座标;每计算出一个坐标,就通知由LineDDA 第五个参数所指定的callback 函数。
LineDDA 的第六个(最后一个)参数可以视应用程序的需要传递一个32 位指针.

LineDDA 的 callback 函数:
typedef void (CALLBACK* LINEDDAPROC)(int, int, LPARAM);

LineDDA 并不属于任何一个MFC 类别,因此调用它必须使用C++ 的"scope operator"(也就是::)
::LineDDA(rect.right/2, 0, rect.right/2, rect.bottom/2,(LINEDDAPROC) LineDDACallback, (LPARAM) (LPVOID) &dc);

其中LineDDACallback 是我们准备的callback 函数,必须在类中先有声明:

class CMyFrameWnd : public CFrameWnd
{
...
private:
 static VOID CALLBACK LineDDACallback(int,int,LPARAM);
};

类的成员函数是一个callback 函数, 你必须声明它为"static",才能把C++ 编译器加到函数的一个隐藏参数this 去掉

空闲时间(idle time)的处理:OnIdle

CWinThreadd::OnIdle函数:
virtual BOOL OnIdle(LONG lCount);
lCount 是系统传进来的一个值,表示自从上次有消息进来,到现在,OnIdle 已经被调用了多少次。
lCount 会持续累增,直到CWinThread::Run 的消息循环又获得了一个消息,此值才重置为0

测试重写onIdle
重写app的OnIdle函数:
由浅入深MFC学习摘记--第三部分_第15张图片
窗口类增加IdleTimeHandler函数:

class CMyFrameWnd : public CFrameWnd
{
public:
 CMyFrameWnd(); // constructor
 afx_msg void OnPaint(); // for WM_PAINT
 afx_msg void OnAbout(); // for WM_COMMAND (IDM_ABOUT)
 void IdleTimeHandler(LONG lCount); // we want it call by CMyWinApp::OnIdle
 ...
};

app OnIdle函数的定义:

BOOL ChelloworldApp::OnIdle(LONG lCount)
{
	// TODO: 在此添加专用代码和/或调用基类
	ChelloworldDlg* pWnd = (ChelloworldDlg*)m_pMainWnd;
	pWnd->IdleTimeHandler(lCount);
	return TRUE;

	return CWinApp::OnIdle(lCount);
}

窗口类函数CMyFrameWnd::IdleTimeHandler的实现

void CMyFrameWnd::IdleTimeHandler(LONG lCount)
{
 CString str;
 CRect rect(10,10,200,30);
 CDC* pDC = new CClientDC(this);
 str.Format(_T("%010d"), lCount);
 pDC->DrawText(str, &rect, DT_LEFT | DT_TOP);
}

运行CWinThread::Run,则需要pThread->InitInstance返回才行,但模态对话框程序有点特殊,会直接阻塞在DoMoDal函数中,然后运行自己的消息循环,CXXXApp::InitInstace在程序没结束时,不会返回,故OnIdle不会得到运行;我用的是基于文档的mfc向导。
由浅入深MFC学习摘记--第三部分_第16张图片

Dialog 与 Control

“关于”对话框的创建:
由浅入深MFC学习摘记--第三部分_第17张图片

当使用者按下【File/About 】菜单, 根据Message Map 的设定,WM_COMMAND(IDM_ABOUT)被送到OnAbout 函数去。我们首先在OnAbout 中产生一个CDialog 对象,名为about。接下来直接调用CDialog::DoModal,对话框就开始运行。

通用对话框(Common Dialogs)

描述
CCommonDialog 以下各类别的父类别
CFileDialog File 对话框(Open 或Save As)
CPrintDialog Print 对话框
CFindReplaceDialog Find and Replace 对话框
CColorDialog Color 对话框
CFontDialog Font 对话框
CPageSetupDialog Page Setup 对话框(MFC 4.0 新增)
COleDialog Ole 相关对话框

由浅入深MFC学习摘记--第三部分_第18张图片
如打开文件的操作:

char szFileters[] = "Text fiels (*.txt)|*.txt|All files (*.*)|*.*||"
CFileDialog opendlg (TRUE, "txt", "*.txt", OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, szFilters, this);
if (opendlg.DoModal() == IDOK)
 {
	filename = opendlg.GetPathName();
}

第五个参数szFilters 指定使用者可以选择的文件型态,
最后一个参数是父窗口。

当DoModal 回返,我们可以利用CFileDialog 的成员函数
GetPathName 取得完整的文件路径
也可以使用另一个成员函数GetFileName 取其不含路径的文件名称
GetFileTitle 取得既不含路径亦不含扩展名的文件名称。

章节总结

一开始是一个派生自CWinApp 的全局对象application object,然后是一个隐藏的WinMain 函数,调用application objectInitInstance 函数,将程序初始化。初始化动作包括构造一个窗口对象(CFrameWnd 对象),而其构造函数又调用CFrameWnd::Create 产生真正的窗口(并在产生之前要求MFC注册窗口类别)。窗口产生后WinMain 又调用Run 激活消息循环,将WM_COMMAND(IDM_ABOUT)WM_PAINT 分别交给成员函数OnAboutOnPaint 处理。

7章 MFC主程序

牢记 MFC 类层级架构

由浅入深MFC学习摘记--第三部分_第19张图片

UI接口

document窗口:如word、excel可以操作多份文件,每一个文件作为一个 document。
文件窗口
关于窗口
打印及预览窗口

Document、View

Document 是数据
View 是数据的表现、视图

文档模板

View 本身虽然已经是一个窗口,其外部却必须再包装一个外框窗口做为舞台。这样是为了让View 可以独立地放置于「MDI Document Frame 窗口」或「SDI Document Frame 窗口」或「OLE Document Frame 窗口」等各种应用之中。也可以说,Document Frame 窗口是View 窗口的一个容器。

文档内容、文档视图、文档的窗口是一个组合,程序打开一个资料,就会产生对应的三个对象:

  1. Document 对象
  2. View 对象
  3. CMDIChildWnd 对象(做为外框窗口)
    三个对象由 Document Template 对象来管理。
BOOL CScribbleApp::InitInstance()
{
 ...
 CMultiDocTemplate* pDocTemplate;
 pDocTemplate = new CMultiDocTemplate(
 IDR_SCRIBTYPE,
 RUNTIME_CLASS(CScribbleDoc),
 RUNTIME_CLASS(CChildFrame),
 RUNTIME_CLASS(CScribbleView));
 AddDocTemplate(pDocTemplate);
 ...
}

其中,构造函数:

CMultiDocTemplate::CMultiDocTemplate(
	UINT nIDResource,
	CRuntimeClass* pDocClass, 
	CRuntimeClass* pFrameClass, 
	CRuntimeClass* pViewClass
);

中,参数:
nIDResource:这是一个资源ID,表示此一文件类型(文件格式)所使用的资源。
pDocClass: 这是一个指针,指向Document 类
pFrameClass: 这是一个指针,指向Child Frame 类
pViewClass : 这是一个指针,指向View 类

可以获取CDocTemplate文件类型用到的七个常量,如 filterExt:

CDocTemplate *pDoc1 = this->m_pDocument->GetDocTemplate();
CString strDefExt;
pDoc1->GetDocString(strDefExt, CDocTemplate::filterExt);

由浅入深MFC学习摘记--第三部分_第20张图片

view

view中常用的相应函数:
OnDraw:画面重绘
OnLButtonDown:鼠标左键按下(作为鼠标事件的举例)

void CmultiDocTempView::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	//在类视图中,CmultiDocTempView类右键属性中,找到消息,选择需要重写的消息
	CView::OnLButtonDown(nFlags, point);
	MessageBox(_T("hello lbutton down"));
}

主窗口

创建过程:
由浅入深MFC学习摘记--第三部分_第21张图片
由 CMainFrame 的 LoadFrame 到 OnCreate。

工具栏、状态栏

工具栏相关 主要动作和设置:

  1. 产生一个隶属于this 的工具栏
//简单形式
m_wndToolBar.Create(this) 
//向导产生的
m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC)
  1. 将RC 文件中的工具栏资源导入。IDR_MAINFRAME 在RC 中代表与工具栏有关的图标、位置等资源。
m_wndToolBar.LoadToolBar(IDR_MAINFRAME)
//或者
m_wndToolBar.LoadToolBar(theApp.m_bHiColorIcons ? IDR_MAINFRAME_256 : IDR_MAINFRAME)

LoadToolBar 知道如何把BITMAP 资源和TOOLBAR 资源搭配起来,完成工具栏的设定。如果不是使用资源工具来编辑工具栏,BITMAP 资源和TOOLBAR 资源就可能格数不符,那是不被允许的。

状态栏相关的动作:

  1. 产生一个隶属于this 对象的状态栏
m_wndStatusBar.Create(this) 
  1. 状态栏设置最右侧的「指示窗口」,如图
m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT));

在这里插入图片描述

鼠标拖放

主要讲述在initInstance中的应用的三个函数:

BOOL CScribbleApp::InitInstance()
{
 // Enable drag/drop open
 m_pMainWnd->DragAcceptFiles();
 // Enable DDE Execute open
 EnableShellOpen();
 RegisterShellFileTypes(TRUE);
}

CWnd::DragAcceptFile(BOOL bAccept=TRUE); 参数TRUE 表示你的主窗口以及每一个子窗口(文件窗口)都愿意接受来自Shell 的拖放文件。CFrameWnd 内有一个OnDropFiles 成员函数,负责对WM_DROPFIELS 消息做出反应,它会通知 application 对象的OnOpenDocument,并夹带被拖放的文件的名称。

CWinApp::EnableShellOpen(); 当使用者在Shell 中对着本程序的文件文件快按两下时,本程序能够打开文件并读内容。通常此函数后面跟随着RegisterShellFileTypes。(我理解为文件默认打开的应用程序注册。)

CWinApp::RegisterShellFileTypes(); 此函数将向Shell 注册本程序的文件类型。有了这样的注册动作,使用者在Shell 的双击动作才有着力点。这个函数搜寻Document Template 串行中的每一种文件类型,然后把它加到系统所维护的registry中。

消息映射

每一个派生自CCmdTarget 的类别都可以有自己的Message Map 以处理消息。
需要在头文件中声明DECLARE_MESSAGE_MAP 宏,
定义中BEGIN_MESSAGE_MAPEND_MESSAGE_MAP 两个宏 之间,声明消息和对应的处理函数。

标准菜单

菜单消息及其映射函数

菜单内容 命令项ID 预设的处理函数
File
New ID_FILE_NEW CWinApp::OnFileNew
Open ID_FILE_OPEN CWinApp::OnFileOpen
Close ID_FILE_CLOSE CDocument::OnFileClose
Save ID_FILE_SAVE CDocument::OnFileSave
Save As ID_FILE_SAVEAS CDocument::OnFileSaveAs
Print ID_FILE_PRINT CView::OnFilePrint
Print Pre&view ID_FILE_PRINT_PREVIEW CView::OnFilePrintPreview
Print Setup ID_FILE_PRINT_SETUP CWinApp::OnFilePrintSetup
“Recent File Name” ID_FILE_MRU_FILE1~4 CWinApp::OnOpenRecentFile
Exit ID_APP_EXIT CWinApp::OnFileExit
Edit
Undo ID_EDIT_UNDO None
Cut ID_EDIT_CUT None
Copy ID_EDIT_COPY None
Paste ID_EDIT_PASTE None
View
Toolbar ID_VIEW_TOOLBAR FrameWnd::OnBarCheck Yes
Status Bar ID_VIEW_STATUS_BAR FrameWnd::OnBarCheck Yes
Window(MDI only)
New Window ID_WINDOW_NEW MDIFrameWnd::OnWindowNew Yes
Cascade ID_WINDOW_CASCADE MDIFrameWnd::OnWindowCmd Yes
Tile ID_WINDOW_TILE_HORZ MDIFrameWnd::OnWindowCmd Yes
Arrange Icons ID_WINDOW_ARRANGE MDIFrameWnd::OnWindowCmd Yes
Help
About AppName ID_APP_ABOUT None

关于对话框

响应函数

BEGIN_MESSAGE_MAP(CScribbleApp, CWinApp)
 	ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
END_MESSAGE_MAP()

CAboutDlg 是CDialog 的派生类

CEditView

替换基类从CView 改为CEditView

由浅入深MFC学习摘记--第三部分_第22张图片
界面变为可以输入。
由浅入深MFC学习摘记--第三部分_第23张图片

你可能感兴趣的:(MFC,c++,mfc,学习,windows)