MFC应用程序设计(第二版)学习笔记

第一章, Windows应用开发基础

基本概念

windows API函数按功能大体分为以下:

窗口管理函数 WUI

图形设备管理函数 GUI

系统服务函数 SUI

 

早期开发使用软件开发工具包sdk,softwaredevelopment kit 就是直接使用API函数来开发应用程序

自面向对象发展以来,人们对WindowsAPI进行了封装,从而使Windows应用程序结构和开发工具都发生了很大的变化

 

MFC::对API进行类封装

 

Winsows的一个特殊类型——句柄

常用句柄类型

HWND / HINSTANCE / HCURSOR光标 / HFONT字体 / HPEN / BRUSH / HDC / HBITMAP / HICON图标 / HMENU菜单 / HFILE

 

Windows自定义的数据类型都要大写

数据类型:

Typedef unsigned long         DWORD

Typedef unsigned int            UINT

Typedef unsigned short WORD

 

消息与消息循环

typedefstruct tagMSG {

  HWND hwnd;

  UINT message;  //标识码比如WM_CLOSE/ WM_CREATE

  WPARAM wParam;

  LPARAM lParam;

  DWORD time;

  POINT pt;

} MSG;

当发生某事件时,系统会把该事件的信息填写到MSG中,并发到应用程序,应用根据MSG中的hwndmessage来确定哪个窗口的哪一段代码来处理(窗口函数中的switch对应)

 

常用Winsows消息的消息标识码

WM_CREATE 由CreateWindow产生的消息

WM_DESTROY 消除窗口,单击右上角关闭,调用了API函数PostQuitMessage()

WM_CLOSE  关闭窗口

WM_LBUTTONDOWN 按下鼠标左键

 

系统为每个应用程序建立了一个消息队列的存储空间

    while (GetMessage(&msg, NULL, 0, 0))  // 获取消息--->队列消息(键盘鼠标等的以及进程消息队列)

{

       if (!TranslateAccelerator(msg.hwnd,hAccelTable, &msg))

       {

           TranslateMessage(&msg); // 把键盘消息翻译成字符消息

           DispatchMessage(&msg);  // 把消息派发给系统而不是应用程序,系统再传给窗口函数,窗口函数是由系统调用

       }

    }

还有一种消息是非队列消息(一般是系统产生的),它直接给了窗口函数

另外,窗口函数在运行过程中也会发出消息,由getmessage获得

 

windows系统把应用程序分成两部分:一个是以消息循环为主的获取和发送消息的部分,另一个是专门处理消息的窗口函数部分。这两部分都是系统调用的函数,这是与DOS系统应用程序的区别

 

Wsindows应用程序结构

intWINAPI WinMain(HINSTANCE hInstance,

                   HINSTANCE hPrevInstance, // 前一个应用程序的句柄

                   LPTSTR    lpCmdLine, // 指向命令行的指针

                   int       nCmdShow // 应用程序窗口显示方式的标志

);

系统调用用户编写的函数过程叫做回调WINAPI CALLBACK

主函数的两个任务是建立应用程序的窗口建立消息循环,至少调用四个+三个=七个API:

RegisterClass(&wc);// 参数是窗口类。作用:把定义好的窗口属性向系统登记

CreateWindow(szWindowClass(窗口类型的名字), szTitle标题, WS_VISIBLE窗口风格, CW_USEDEFAULT左上角x, CW_USEDEFAULT左上角y, CW_USEDEFAULT宽度, CW_USEDEFAULT高度, NULL父窗口句柄, NULL主菜单句柄, hInstance应用程序实例, NULL设为NULL);

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

 

GetMessage(&msg, NULL, 0, 0)

TranslateMessage(&msg);

DispatchMessage(&msg);

 

 

typedefstruct _WNDCLASS {

  UINT style;               //窗口样式,一般为0

  WNDPROC lpfnWndProc;  //窗口函数 // 一个窗口只能有一个窗口函数,多个窗口可以共用一个窗口函数

  int cbClsExtra;  // 0

  int cbWndExtra;  // 0

  HANDLE hInstance; // 应用程序实例句柄

  HICON hIcon;  // 窗口图标

  HCURSOR hCursor; //窗口光标

  HBRUSH hbrBackground; //窗口背景颜色

  LPCTSTR lpszMenuName;  // 窗口菜单资源名

  LPCTSTR lpszClassName; // 窗口类名字

 } WNDCLASS;

例如:

       wc.style         = CS_HREDRAW | CS_VREDRAW;

       wc.lpfnWndProc   = WndProc; //  同一个窗口过程可以被多个窗口类型使用

       wc.cbClsExtra    = 0;

       wc.cbWndExtra    = 0;

       wc.hInstance     = hInstance;

       wc.hIcon         = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_T683));

       wc.hCursor       = 0;

       wc.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);

       wc.lpszMenuName  = 0;

       wc.lpszClassName = szWindowClass; // 窗口类型的名字

 

不同的窗口类型可以使用相同的窗口函数

每个窗口都要有一个窗口类型, 窗口在CreateWindow的时候把窗口类型的名字作为第一个参数传进去

一个窗口类型可以用来创建多个窗口,CreateWindow的第一个参数相同就行,第二个参数是窗口实例的标题(通过它来区别),每个窗口实例都要showwindow和updatewindow

 

窗口函数

LRESULTCALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,LPARAM lParam)  // 附加参数32位

switch (message) { // 消息标识码

        case WM_COMMAND:

            wmId    = LOWORD(wParam);// 低两字节

            wmEvent = HIWORD(wParam);

            // 分析菜单选择:

            switch (wmId)

            {

                case IDM_HELP_ABOUT:

                    DialogBox(g_hInst,(LPCTSTR)IDD_ABOUTBOX, hWnd, About);

                    break;

                case IDM_OK:

                    SendMessage (hWnd,WM_CLOSE, 0, 0);             

                    break;

                default:

                    return DefWindowProc(hWnd, message, wParam, lParam);

            }

            break;

        case WM_CREATE:

            ..........

        case WM_PAINT:

            hdc = BeginPaint(hWnd, &ps);

           ..............

            EndPaint(hWnd, &ps);

            break;

        case WM_DESTROY:

            CommandBar_Destroy(g_hWndMenuBar);

            PostQuitMessage(0);

            break;

        default:

            return DefWindowProc(hWnd,message, wParam, lParam); // 对没有处理的消息进行默认处理   

}

 

windows系统,主函数,窗口函数间的关系 

主函数,窗口函数都是由Wiindows系统来调用的函数,只不过主函数是程序启动之后,系统首先调用的函数,而窗口函数是主函数在获得消息并把消息发给系统之后,由系统依据产生事件的窗口(MSG中记录的hwnd所使用的窗口类&wc提供的函数指针WndProc调用的函数

 

使用函数封装Windows程序

ATOM          MyRegisterClass(HINSTANCE, LPTSTR);

BOOL          InitInstance(HINSTANCE, int);

LRESULTCALLBACK  WndProc(HWND, UINT, WPARAM,LPARAM);

INT_PTRCALLBACK  About(HWND, UINT, WPARAM,LPARAM);

WinMain函数的返回值 return (int)msg.wParam;

见原书代码1-3

 

窗口函数的另一种结构:使用消息映射表见自已写的代码d:\mfc_projects 下的1-4

structdecodeUINT {                            

    UINT nMessage;                                    // 消息标识

    LRESULT (*pfn)(HWND, UINT, WPARAM, LPARAM);   // 消息处理的函数指针

};

structdecodeUINT MainMessages[] = {

    WM_CREATE, DoCreateMain, // 成对出现的

    WM_COMMAND, DoCommandMain,

    MYMSG_ADDLINE, DoAddLineMain,

    WM_DESTROY, DoDestroyMain,

};

窗口函数可以这样设计:

#definedim(x) (sizeof(x) / sizeof(x[0]))

LRESULCALLBACK WndProc(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam){

for (int i = 0; i <dim(MainMessages); i++) {

        if (message== MainMessages[i].nMessage) {

(*MainMessages[i].pfn)(hWnd,wMsg, wParam, lParam);

}  

}

return DefWindowProc (hWnd, wMsg,wParam, lParam);

}

可以将MainMessages和填写消息映射表荐的代码写成宏的形式,见原书代码1-4

如下:

#defineDECLARE_MESSAGE_MAP( ) \

structMSGMAP_ENTRY  _messageEntres[ ];\ //接着下一行

 

#defineBEGIN_MESSAGE_MAP( ) \

structMSGMAP_ENTRY  _messageEntres[ ] = \

{ \ // 接着下一行

 

#defineON_WM(messageID,msgFuc) \

    messageID,msgFuc,

#defineEND_MESSAGE_MAP( ) \

};\   // 接着下一行

 

填写表荐内容:

DECLARE_MESSAGE_MAP()

BEGIN_MESSAGE_MAP()

ON_WM(WM_LBUTTONDOWN,On_LButtonDown)

ON_WM(WM_PAINT,On_Paint)

ON_WM(WM_DESTROY,On_Destroy)

END_MESSAGE_MAP()

第二章,Windows应用程序的类封装

MFC:将大多windowsAPI和相应的数据进行封装

最大优点是将程序问题分解成若干对象,将大型程序模块化。 利用类的继承,能快速完成程序设计

程序框架:应用程序类+窗口类     

应用程序类中含有一个窗口类对象的指针CFrameWnd* m_pMainWnd

应用程序类对象是一个全局对象CWinApp theApp;

本书1-3的程序用类封装后见程序2-1

看一下应用程序类的构造函数

BOOLCWinApp::InitInstance(int nCmdShow)

{

    m_pMainWnd=new CFrameWnd;

    m_pMainWnd->Create(NULL,"封装的Windows程序");

    m_pMainWnd->ShowWindow(nCmdShow);

    m_pMainWnd->UpdateWindow();

    return TRUE;

}

成员函数的定义

voidCFrameWnd::ShowWindow(int nCmdShow)

{

    ::ShowWindow(hWnd, nCmdShow); // 前面的::说明是系统函数,不属于任何类

}

 

程序2-3(2-2不用看了)

应用程序的派生类

通过上面的类封装,增强了代码复用性,减少了程序员的负担。

更牛X的,MFC希望程序设计向导自动生成主函数,自动生成的标志符就不变的。

下面纠结的是,主函数中标志符的不变性与全局变量theApp名字可变性的矛盾。主函数中不能使用全局对象theApp的名字。

我们约定,在主函数中使用指针pApp,怎么也让它指向那个名字可以随意变的全局对象呢?

解决办法: 在应用程序类CWinApp中增加一个protected *m_pCurrentApp指针变量,在构造函数中将this赋给它。在WinMain函数中使用全局函数pApp = AfxGetApp();  定义如下

CWinApp*AfxGetApp(){

    return myApp.m_pCurrentWinApp;

}

 

利用C++的继承和多态  丰富和完善基类 满足用户多样性的要求

程序2-2只是在2-1的CWinApp中派生CMyApp  而将父类CWinApp的InitInstance成员函数设为virtual

程序2-3包括了2-2且解决了上面纠结的问题。

消息映射表

顺着思路去想:为了封装,可不可以把窗口函数封装在窗口类中,然后在WndProc中调用这个成员函数?

带来的问题:只有窗口类和其派生类才有消息处理的能力。但实际中,按钮,对话框等都有消息处理能力。所以最好封装在单独的一个类CCmdTarget中,作为高端类,以后凡是有消息处理能力的类都从它来派生。

这种封装参考2-4

 

继续讨论。上面带来的问题有两个,一是消息处理函数通常不在CCmdTarget中,而是在它的某个派生类中。另一个是可读性差。前一个问题可以通过虚函数解决,但需要维护一个大的虚函数表。可读性需要使用消息映射来解决。

MFC要求在定义一个有消息处理能力的类时,要从CCmdTarget类或其基类继承,然后声明消息处理函数,比如
afx_msg void OnDraw();

afx_msgvoid OnLButtonDown();

而类的消息映射表项结构如下:

struct AFX_MESSAGE_ENTRY { // 多了好几个成员

    UINT nMessage; // 消息标识码

    UINT nCode; // 控制消息的通知码

    UINT nID; //WindowsControlID

    UINT nLastID; // 指定消息被映射的范围

    UINT nSig; // 消息的动作标识

    AFX_PMSGpfn; // 指向CCmdTarget或其派生类的成员函数,就是上面的OnDraw等

};

每个具有消息处理能力的类都有struct AFX_MSGMAP_ENTRY_messageEntries[]这个数组(在类外填表内容),而且,为了形成总表,要外加两个指针pBaseMap(指向父类对象)和lpEntries(指向数组_messageEntries[])。

 

形成一个总表,不管CCmdTarget类簇中另个类对象接到消息,都可以在总表中寻找消息处理函数。A派生B,如果两个类有相同的消息函数,B收到消息只会执行它自己的那个消息处理函数,此乃多态。

 

注意到,MFC在解决类似消息映射表这种包含在类中,又具有全局意义的信息时,多用使用了这种表结构。见笔记本第1页。

 

宏定义负责填写_messageEntries的各项内容,并负责建立链表中的结点,把类的消息映射表加入到链表中。在类的声明文件(头文件)中声明消息映射。下文会再细说

 

MFC把消息分为三类:

(1)标准消息WM_XXX

就是最先认识的那些WM_XXX类的消息。Switch case判断的那种。

消息处理函数是OnXXX. 而宏格式为ON_WM_XXX

举个例子:消息WM_LBUTTONDOWN   函数afx_msg void OnLButtonDown() 宏ON_WM_LBUTTONDOWN()没有参数

(2)命令消息 WM_COMMAMD

WM_COMMAMD来自菜单,另外还有工具条,加速键等用户接口对象的消息。属于应用程序自己定义的消息,系统没有默认的消息处理函数。

宏格式:ON_COMMAND(IDM_FILENEW, OnFileNew)

ON_COMMAND(IDM_FILEOPEN, OnFileOpen)

(3)Notification消息

由按钮、文本编辑框等控件产生的消息。由于控件的种类很多,因此宏格式不尽相同。

按钮的宏格式:ON_BN_CLICKED(<消息标识>, <对应的消息处理函数>)

组合控件(ComboBox)双击事件的宏格式:ON_CBN_DBLCLK(<消息标识>, <对应的消息处理函数>)

文本编辑控件(Edit)的双击事件的宏格式为:ON_EBN_DBLCLK(<消息标识>, <对应的消息处理函数>)

 

参考2-5:

classCMyWnd:public CFrameWnd

{

private:

    char*ShowText;           //声明一个字符串为数据成员

public:

    afx_msg void OnPaint();//声明WM_PAINT消息处理函数

    afx_msg void OnLButtonDown();//声明鼠标左键按下消息处理函数

    DECLARE_MESSAGE_MAP()    //声明消息映射,在类中

};

//消息映射的实现,在类外----------------------------------------------------------------------

BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd) // 类名称,基类名称

       ON_WM_PAINT()

       ON_WM_LBUTTONDOWN()

END_MESSAGE_MAP( )

第三章 MFC应用程序框架

早期应用程序框架

如MFCexp2_3.cpp,主函数如下:

intAPIENTRY WinMain(...) {

    int ResultCode=-1;

    CWinApp *pApp;

    pApp=AfxGetApp();

    pApp->InitInstance(nCmdShow); // 实例化

    return ResultCode=pApp->Run(); // 运行

}

MFC希望把程序的主函数的函数体也做作一个对象来处理 CWinApp, 笔记本第3页

从第3页图中知道,CWnd *m_pMainWnd定义在了CWinThread中,在2-3程序中,此成员是在CWinCpp中,CFrameWnd * m_pMainWnd;

CWinApp这个类有三个virtual成员函数InitApplication、InitInstance和Run。其中InitInstance是为程序创建和显示窗口所设置的。因此在设计程序时,必须在CWinApp类的基础上派生自己的应用程序类,并对函数InitInstance进行重写。

使用早期MFC应用程序框架类设计的一个最简单的Windows应用程序:MFCexp3_1.cpp 里面作了笔记

 

文档/视图结构

使用Visual C++生成框架,根据需要添加代码。

    原来的应用程序主窗口对象任务繁重,所以被拆分成了窗口框架类CFrameWnd对象视图类CView对象文档类CDocument对象三个对象

1)  窗口框架类CFrameWnd对象: 窗口框大小,标题,菜单,状态条的窗框部分,总之,只管边框

2)  视图类CView对象: 数据显示与用户交互部分,总之,窗口用户区

CView把原来窗口框架类承担数据显示和接受用户对用户区操作的代码单独分出来,形成的一个单独的类,这的对象是应用程序与用户进行交互的界面

3)  文档类CDocument对象: 数据存储与管理、运算

 

以上三个对象由文档模板对象统一创建和管理

一库多店的结构: CFrameWnd相当于窗框,CView相当于窗框上的玻璃,而CDocument相当于室内物品

 

单文档界面SDI和多文档界面MDI(MultipleDocument Interface)结构

    多文档界面要提供一个Close用于关闭当前打开的文档

   

 

三个对象的派生类:

CFrameWnd的派生类CMainFrame

使用MFC App Wizard来创建应用程序,向导会为程序员自动从CFrameWnd派生一个叫做CMainFrame的派生类。

classCMainFram: public CFrameWnd // 这个类基本不需要用户进行编码,任务都分给下面两个类了

CFrameWnd的部分成员函数如下:

ClassCFrameWnd: public CWnd {

    DELCARE_DYNCREATE(CFrameWnd)

Public:

    CWnd* CreateView();

 

    Virtual CDocument *GetActiveDocument();

    CView* GetActiveVirw();

    Virtual CFrameWnd* GetActiveFrame();

 

    Void SetActiveView();// 设置活动视图

    ...

};

CView的派生类CMyView(工程名My)

class CMyView: public CView

{

protected:

    CMyView();

    DECLARE_DYNCREATE(CYmView);

public:

    CMyDoc* GetDocument(); // 获得文档类对象的指针,与文档类联系的通道

    virtual void OnDraw(CDC *pDC); // 重画函数,当容器出现及其大小发生变化时,系统会自动调用它,进行重画。pDC相当于工具箱

    ……

protected:

    DECLARE_MESSAGE_MAP();// 类的定义,最后一个没有分号,加不加都行

};

例:

VoidCMyView::OnDraw(CDC *pDC) {

    CRect rt(50,50,150,150);

    pDC->Rectangle(&rt);

//如果用到文档对象则使用CMyDoc* pDoc = GetDocument();

}

CDocument的派生类CMyDoc(工程名My)

    Class CMyDoc:public CDocument // 该文档类派生自CCmdTarget类,可以接收来自菜单或工具条发来的命令消息:WM_COMMAMD

    {

Private:

    IntArray[5]; // 数据存储

protected:

    CMyDoc();

    DLARE_DYNCREATE(CMyDoc);

public:

    voidSetMem(int I, int x); //自己定义的

    intGetMem(int i);

public:

    virtualBOOL OnNewDocument();

    virtualvoid Serialize(CArchive& ar); //这是用户编码多的地方。当用户菜单对文件进行新建,打开,保存等操作时,应用程序会自动调用这个函数。它既负责由文件读数据也负责向文件写数据

    virtual~CMyDoc();

    DECLARE_MESSAGE_MAP();

};

CMyDoc::CMyDoc(){

    for(int I = 0; i < 3; ++i)

       Array[i]= 0;

}

void CMyDoc::SetMem(int I, int x) {

    Array[i]= x;

}

int GetMem(int i) {

    returnArray[i];

}

文档模板类CDocTemplate

CDocTemplate把视图对象,框架窗口对象和文档对象组装在一起并统一进行管理

分为单文档模板类CSingleDocTemplate和多文档模板类CMultiDocTemplate, 笔记本第5页

CSingleDocTemplate有个成员CDocument *m_pOnlyDoc //文档指针

CMultiDocTemplate有个成员CPtrList m_docList //文档链表,可以同时打开多个文档

使用MFC App Wizard来创建应用程序,向导会为你生成一个合适的,用户基本不用编码

 

应用程序的派生类

需要一个应用程序类对象作为上面各类对象的容器

classCMyApp: public CWinApp // 定义自己的应用程序对象

{

public:

    CMyApp();

public:

    virtual BOOL InitInstance(); // 生成和显示窗口

    afx_msg void OnAppAbout();

    DECLARE_MESSAGE_MAP();

};

 

BOOLCMyApp::InitInstance()

{

    CSingleDocTemplate* pDocTemplate;

    pDocTemplate = newCSingleDocTemplate(IDR_MAINFRAME,  // 模板使用的资源ID

RUNTIME_CLASS(CMyDoc), //使用创建文档对象

RUNTIME_CALSS(CMainFrame),

RUNTIME_CLASS(CMyView));

    AddDocTemplate(pDocTemplate); // 将该模板加入模板链表

    ……

    m_pMainWnd->ShowWindow(SW_SHOW);

    m_pMainWnd->UpdateWindow();

    return TRUE;

}

 

程序员的工作

使用MFC App Wizard来创建应用程序,向导会自动生成各个派生类,并同时定义应用程序全局对象和生成主函数,程序员做如下工作
1)重写CWinApp派生类的虚函数InitInstance(),按自己的需要创建和显示窗口

2)在CDocument的派生类中,声明程序所需的数据和对这些数据进行必要操作的接口

3) 在CView的派生为中编写消息处理的代码,如果消息处理需要用到文档中的数据,则通过GetDocument来获取文档对象指针,然后操作数据接口函数; 在CView中编写OnDraw重绘代码

4) 用宏实现类的消息映射表BEGIN_MESSAGE_MAP(类名称,基类名称)

 

MFC文档/视图应用程序框架中各个对象的关系

各对象创建的顺序

全局应用程序对象在WinMain函数之前创建,程序启动后,主函数首先调用应用全局对象的InitInstance(),在该函数中创建文档模板对象

    pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,  // 模板使用的资源ID

RUNTIME_CLASS(CMyDoc), //静态对象成员的地址 形参类型一定是CRuntimeClass*

RUNTIME_CALSS(CMainFrame), // &CMainFrame::classCMainFrame

RUNTIME_CLASS(CMyView));    //&CMyView::classCMyView

注:宏定义体#define RUNTIME_CLASS(class_name) (CRuntimeClass*)(&class_name::class##class_name)

    这里,用MFC的宏RUNTIME_CLASS传递了文档类,框架窗口类,视图类的类信息表,构造函数根据资源和类信息表动态地创建文档,视图,窗口框架三个对象,其中视图对象是由框架窗口对象创建并管理,各对象创建顺序见第8页

应用程序各对象之间的关系图,笔记本第7页

QQ日志中:VC的应用程序框架中各类之间的访问方法

SDI应用程序框架对象之间的联系方法

解释:

(1)   应用程序类对象

CExamlpleApp*p = (CExamlpleApp*)AfxGetApp(); 

(2)   窗口框架对象CMainFrame

通过AfxGetMainWnd或通过应用类的成员函数GetMainWnd()   成员函数或  m_pMainWnd   成员变量获得指针

对于MID, 通过子框架窗口对象的GetMIDFrame

获得指针需要强制转换

 

GetActiveDocument是获得活动视图的文档对象

框架窗口对象调用CView::OnActiveView()使一个视图对象为活动视图

(3)   文档对象

获得视图链表中的对象first next

更新所有视图

通过GetDocTemplate获得文档模板对象

(4)   视图对象

视图类对象是由主边框窗口(SDI   应用程序)或子边框窗口(MDI   应用程序)构造生成

三个get

另外还有一个get: getDocTemplate()获得文档模板对象

CView::OnUpdate()更新一个视图对象的显示

 

应用类对象通常只生成一个文档模板类对象   ,不过用户可以自己生成多个文档模板类对象,从而使SDI  也可以打开多个文档,具有了MDI的特征.所有的文档模板类对象组   成了一个链表,应用类m_pDocManger   成员变量指向该链表。  
用户可以通过应用类的成员函数  GetFirstDocTemplatePosition()和GetNextDocTemplate(POSITION   &   pos)来访问该链表:  
                         POSITION   pos =GetFirstDocTemplatePosition();  
                         CDocTemplate   *pDocTemplate =GetNextDocTemplate(pos);  
         pDocTemplate即是指向第一个文档模板类对象的指针,用户还可以继续调用 GetNextTemplate()得到下一个文档模板类对象. 

MDI应用程序框架对象之间的联系方法

(1)   子框架窗口对象

主边框窗口类的  CFrameWnd   *   GetActiveFrame()   得到指向该对象的指针

(2)   单文档模板类只能生成一个文档类对象,并 用成员变量   m_pOnlyDoc   指向该对象。多文档模板类可以生成多个文档类对象,另用成 员变量   m_docList   指向文档对象组成的链表

用户可以通过多文档模板类对象的成员函数  GetFirstDocPosition()  和 GetNextDoc(POSITION   &pos)   来访问文档对象组成的链表:  
                         POSITION   pos =GetFirstDocPosition();  
                         CDocument   *pDoc =GetNextDoc(pos);  
              另外,文档类还可以通过其成员函数   CDocTemplate  *   GetDocTemplate()   返过来 访问文档模板类对象

QQ那篇文章最后写道:(以后再理解)

对于频繁性地访问某一固定对象或对运行速度要求较高的场合,我们可以 通过保存该对象的窗口句柄(只限于派生于 CWnd   的类),在需要的时候,通过函数 CWnd::FromHandle(HWND   hwnd)   来得到该对象的指针.

对象的动态创建

MDI程序可以建立多个文档模板,在程序启动后,应该建立哪种类型的文档模板对象呢? 打开或新建时向用户提出询问,得到答案后,按要求创建模板对象.

每个需要动态创建对象的类定义一个可以创建本类对象的创建函数

CObject*CreateObject() { return new Myclass; } // 要求Myclass必须要有无参构造函数

然后再制作一个全局表,凡是需要动态创建对象的类都在这个表中占一项,把类名称对象创建函数关联起来,当程序拿到类名后,可以根据类名在表中找到与其对应的对象构建函数指针,通过它来调用对象构建函数创建对象.

 

例子3-5演示了一个动态创建Myclass类对象。  注意虚函数的使用

类信息表

MFC在许多不同场合要用到类的其他信息,MFC就把这些信息统统放在上面所说的映射表项中,并叫作类信息表.

structCRuntimeClass

{

    LPCSTR m_lpszClassName; // 类名字

    ……

    CObject* (PASCAL* m_pfnCreateObject)();

    CObject* _stdcall CreateObject();

    ……

    CRuntimeClass* m_pBaseClass; // 向上查询,该表就类族谱系表(有助于辨别一个对象是哪个类族的——运行期对象类型识别RTTI)

    CRuntimeClass* m_pNextClass; // 沿着查询,该表就是类信息表 指针

};

 

使用DECLARE_DYNCREATE封装了类信息表的声明代码,使用IMPLEMENT_DYNCREATE封装了类信息表及其链表的实现代码。处理有动态创建对象能力的类时,必须要在类中使用这两个宏:

class A:puclic B

{

public:

    DECLARE_DYNCREATE(A)

};

IMPLEMENT_DYNCREATE(A,B);

 

例子3-5中,通过

    CRuntimeClass* p=Myclass::GetRuntimeClass();

CRuntimeClass*Myclass::GetRuntimeClass() {

    return &Myclass::classMyclass;  //返回静态对象成员的地址

}

来获得对象所属类的类信息表指针,而在MFC中,这部分代码封装成了宏:

#defineRUNTIME_CLASS(class_name) \

    ((CRuntimeClass*)(&class_name::class##class_name))

而这个宏在上面CSingleDocTemplate的构造函数中使用

第四章 图形

Windows为图形显示设备进行了软件封装,用户在这个虚拟设备上绘图,而把它转化为物理设备图形的任务由系统去完成。

图形设备描述表DC,可以看成是一个画板

应用程序->DC / 图形设备驱动 -> 物理显示设备

 

把DC的相关操作封装成函数:GDI (graphical device interface)

绘图工具:画笔,画刷,字体Font,位图,调色板Palette(绘图时的颜色集 )

 

windows要求绘图环境在任何时候都应该存有一套完整的绘图工具,这意味着不能从绘图环境中删除工具,只能用一个换掉另一个

 

把DC与GDI封装成一个类,就是CDC类。

它派生了几种特定的设备描述环境类:

CClientDC         窗口客户区的,用在WM_PAINT消息之外的消息处理函数中, OnDraw之外的地方也可以使用

CMetaFileDC       图元文件的,在创建可以回放的图像时使用

CPaintDC          窗口用户区的,在OnDraw的函数中处理WM_PAINT消息。它的对象作实参调用OnDraw(CDC* pDC)

CWindowDC         在整个窗口内(而不只是用户区)绘图的

 

Cpen类

继承: CObject -> CGdiObject ->CPen

 

PS_NULL: 笔画不可见的画笔

 

4-1例子就在视图类的OnDraw中调用了一句TextOut // OnDraw函数是自动调用的

4-2在视图类中使用SelectObject MoveTo LineTo画了八条逐粗的线

4-3同样在OnDraw画了几条线,注意DASH与DOT的区别

        CPennewPen(style[s],1,RGB(0,0,0));

       CPen*oldPen=pDC->SelectObject(&newPen);

       pDC->MoveTo(20,row);

       pDC->LineTo(300,row);

       pDC->SelectObject(oldPen);

 

CBrush类

继承: CObject -> CGdiObject ->CBrush

 

· HS_BDIAGONAL    左下到右上45度填充

· HS_CROSS         十字交叉

· HS_DIAGCROSS      交叉的45度线

· HS_FDIAGONAL    左上到右下45度

· HS_HORIZONTAL     Horizontal hatch

· HS_VERTICAL    Vertical hatch

以上的使用参见

4-4例, 也是在视图类的OnDraw中

4-5中,是用系统默认的画笔及画刷来画图

 

绘图模式SetROP2

intSetROP2(int nDrawMode); // 返回值是原来的绘图模式

R2_BLACK               无论画笔颜色怎样,只用黑色绘图

R2_WHITE              无论画笔颜色怎样,只用白色绘图

R2_NOP               无论画笔颜色怎样,只用无色绘图

R2_COPYPEN          用画笔颜色绘图

R2_NOT               用与背景色的反色绘图

R2_NOTCOPYPEN      用画笔的反色绘图

R2_XORPEN        用画笔色与背景色异或后的颜色进行画图

 

文本和CFont类

4-6:文本颜色与背景颜色(仅仅是字的背景)

    pDC->SetTextColor(RGB(255,0,0)); // 红字

    pDC->TextOut(130,30,"文本的颜色");

 

    pDC->SetTextColor(RGB(255,255,255)); // 白字

    pDC->SetBkColor(RGB(0,0,255)); // 蓝底

    pDC->TextOut(230,30,"文本的颜色");

 

还有一个获得当前背景色的成员函数:

COLOREFGetBkColor()const;

 

4-7: 字符间距

       pDC->SetTextCharacterExtra(s*4);

       pDC->TextOut(20,20+s*20,"文本字符的间距");

 

获得此值:

intGetTextCharacterExtra() const;

 

4-8: 文本对齐方式

pDC->SetTextAlign(TA_LEFT);

还有TA_RIGHT, TA_CENTER, TA_TOP, TA_BOTTOM,TA_BASELINE(基线对齐)

 

字体信息:TEXTMETRIC结构

OnDraw中

TEXTMETRICtm;

pDC->GetTextMetrics(&tm);

 

CreateFont

使用预存的字体格式创建Font: CreateFontIndirect(const LOGFONT lpLogFont)

 

CClientDC类

4-9: 在OnLButtonDown中使用CClientDC类

    CClientDCdc(this);      //定义一个CClientDC的对象dc

    CRectrc;                //定义一个描述矩形的对象rc

    GetClientRect(&rc);      //获得用户区的尺寸,并存入rc

    //以下是绘制菱形的代码

    dc.MoveTo(0,(rc.bottom+rc.top)/2);// 画线的起点位置

    dc.LineTo((rc.right+rc.left)/2,0);

    dc.LineTo(rc.right,(rc.bottom+rc.top)/2);

    dc.LineTo((rc.right+rc.left)/2,rc.bottom);

    dc.LineTo(0,(rc.bottom+rc.top)/2);

    CView::OnLButtonDown(nFlags,point); // 调用父类中同名函数,上面是对父类同名函数重写而扩展的部分

CMetaFileDC类

有些图形是需要经常重复显示的,这种图形事先绘制好一个文件放在内在中,用的时候直接打开就行了。这种图形文件叫用图元文件

制作这种文件需要CMetaFileDC类

一般地,在视图类的OnCreate中创建图元文件

过程是:

1)先创建一个CMetaFileDC对象,再调用成员函数Create(LPCTSTR lpszFilenam=NULL), 参数是文件名,再调用绘制函数绘制图形,最后调用Close函数返回HMETAFILE类型的句柄给m_hMetaFile数据成员。

2)要显示时,调用CDC的成员函数PlayMetaFile(m_hMetaFile);

3)最后,不再使用的时候要::DeleteMetaFile(m_hMetaFile);进行删除

以上实例见4-10。在视图类的OnCreate中创建并保存到m_hMetaFile中;在OnLButtonDown函数中显示PlayMetaFile;在OnDestroy()中删除DeleteMetaFile

 

 

第五章 MFC的通用类

CPoint与CRect都有重载的+=, -=, !=, ==

尺寸类CSize(int initCX,int initCY), 有一个x宽和y高

 

5-1是对Cstring类的使用,并调用 了AfxMessageBox();

5-2单会出现一个随机大小的矩形, 每次重画都要画出所有先前的矩形,那么,就要用数组存放之前生成的所有矩形

    1)类成员:CArray<CRect,CRect&> m_Rectag;

    2)OnLButtonDown 中每单击一下就要把生成的Ret加入m_Rectag.Add(Ret);并且InvalidateRect(Ret,FALSE);//触发OnDraw()函数

    3)在OnDraw中for显示:

for(inti=0;i<m_Rectag.GetSize();i++)

           pDC->Rectangle(m_Rectag[i]);

5-3上面的例子把数据放在了视图类中,不合理,本例中把数组放在文档类中,OnDraw中通过GetDocument来获取文档类的指针

    在文档类的构造函数中定义数组的大小  m_Rectag.SetSize(256,256);

 

第六章 界面设计

6-1关于框架类中的PreCreateWindow,设置样式style

6-2拆分窗口的同步更新

void CMFCexp6_2View::OnLButtonDown(UINT nFlags, CPoint point)

{

    CMFCexp6_2Doc*pDoc=GetDocument();//获取文档指针

    intr=rand()%50+5;

    CRectRet(point.x-r,point.y-r,point.x+r,point.y+r);

    pDoc->m_Rectag.Add(Ret);//向文档中数组添加元素

    pDoc->UpdateAllViews(NULL);//使用了更新所有视图的函数

    CView::OnLButtonDown(nFlags,point);

}

函数原型:

voidUpdateAllViews(

   CView* pSender,// 该函数的调用者

   LPARAM lHint = 0L,

   CObject* pHint= NULL

);

6-3提高效率,

void CMFCexp6_3View::OnLButtonDown(UINT nFlags, CPoint point)

{

    CMFCexp6_3Doc*pDoc=GetDocument();//获取文档指针

    intr=rand()%50+5;

    CRectRet(point.x-r,point.y-r,point.x+r,point.y+r);

    pDoc->m_Rectag.Add(Ret);//向文档中数组添加元素

    m_ViewDrRect->m_DrawRect=Ret;//将矩形参数赋予重绘区类成员

    InvalidateRect(Ret,FALSE);//触发OnDraw()函数,传递无效区类对象

    pDoc->UpdateAllViews(this,0L,m_ViewDrRect);   //传递this则重绘其他视图

    CView::OnLButtonDown(nFlags,point);

}

分析1)只对变化的区域重绘:InvalidateRect(LPCRECT lpRect, BOO. bErase =TRUE),是触发OnDraw对屏幕进行重绘,第一个参数是指定无效区域,只对这个区域进行重绘。

2)通知其他区域也只对变化的区域重绘:voidUpdateAllViews(CView* pSender, LPARAM lHint = 0L,CObject* pHint = NULL),它调用了各视图对象的OnUpdate(CView*pSender, LPARAM lHint, CObject* pHint)方法,而且第三个参数对应第三个参数传递,OnUpdate是个虚函数,重写OnUpdate,在里面调用InvalidateRect(Rect,FALSE);这个Rect通过第三个参数带着无效区域进来的,这第三个参数通过定义一个CObject的子类CDrawRect(在CDrawRect.h头文件),这个子类中有CRect m_DrawRect数据成员。CDrawRect在视图类中作为对象成员:CDrawRect* m_ViewDrRect; 它是以指针形式存在,它的初始化在视图类的构造函数中。最后在OnDraw中进行绘图,for中画的椭圆pDC->Ellipse(pDoc->m_Rectag[i]);

本程序其他细节:

在StdAfx.h中加#include <afxtempl.h>

 

书中没有给出6-4的例子,经过多次调试,终于搞出来了,搞的是6-5名字

新建一个类CDrawRect类: 两种方法可以做到,一种是在MFCexp6_5 classes右击,new class新建一个类,在VC6.0中弹出的对话框中,新建类为MFC类时父类选择下拉中没有CObject,所以不能满足需要,经实验,在VS2005中是可以的。另一种方法是自己新加一个头文件DrawRect.h,里面要有一行: #pragma once

    关于它的解释:只include一次,加快编译的速度,跟ifndef define endif并不多,好像能在VC的编译器上使用

另外,为了能响应单击消息,添加如下黑体字部分

BEGIN_MESSAGE_MAP(CMFCexp6_5View,CScrollView)

    //{{AFX_MSG_MAP(CMFCexp6_5View)

       // NOTE - the ClassWizard will add andremove mapping macros here.

       //   DO NOT EDIT what you see in these blocks of generated code!

    ON_WM_LBUTTONDOWN()

    //}}AFX_MSG_MAP

    // Standard printing commands

    ON_COMMAND(ID_FILE_PRINT,CScrollView::OnFilePrint)

    ON_COMMAND(ID_FILE_PRINT_DIRECT,CScrollView::OnFilePrint)

    ON_COMMAND(ID_FILE_PRINT_PREVIEW,CScrollView::OnFilePrintPreview)

END_MESSAGE_MAP()

最后一点,在重写OnUpdate与OnLButtonDown的时候要保证在类中先在头文件的类中声明(这样才能在类视图中列出来),源文件中实现

还有,视图类是从CScrollView继承来的,有个虚成员函数OnInitialUpdate,在它里面加以下

    CSize sizeTotal(3000, 2000);

    CSize sizeLine(10, 10);

    CSize sizePage(50, 50);

    SetScrollSizes(MM_TEXT, sizeTotal, sizePage,sizeLine);

6-5有了,现在的问题是:先使用滚动条把窗口向右移动后,要在圆1的右上角画一个圆,而在圆1的左上角却出现了这个圆。原因中移动滚动条后,窗口向右发生移动,文档的原点已不再与视图的原点重合。视图类不能对此自动修正。程序还当作跟原来的重合,故而错误

为了解决这个问题,就要在画图时,把视图的坐标转换成文档的坐标,而在显示文档数据时则需要所文档的坐再转换为视图的坐标

用到DC类的成员函数:

voidDPtoLP(): 设备坐标(视图)转逻辑坐标(文档坐标)

voidLPtoDP()

修改OnLButtonDown与OnUpdate

 

OnInitUpdateOnDraw之前调用,因此象ScrollView等设置可在此完成

一般在OnPrepareDC里设置DC映射方式然后在函数里这样调用

CClientDC dc(this);
OnPrepareDC(&dc);

当在SCrollView里设置映射方式时,OnPrepareDC设置将无效

 

 

第七章 鼠标和键盘

7-1 给一类加比如左WM_LBUTTONDOWN消息时,在类视图下找到视图类,右击Add windows message handler, 选WM_LBUTTONDOWN,然后add and edit,就会加入这个函数的声明以及实现,并在BEGIN_MESSAGE_MAP() END_MESSAGE_MAP()之间加入相应代码

 

7-2 鼠标移动过的地方画线, 在OnMouseMove中添加东西,其中一细节

文档类中定义成员CPoint m_Point; // 没有在构造函数中初始化,它是默认初始化的。使用在OnLButtonDown中单击点位置初始化它

我把它改成如下了,(单击初始点和终止点画点),单击移动,放开后成线

//现在又明白了,不只是OnDraw里才能画图,清除上次画线的过程就是在这个函数中画线完成的

 

 

7-3是非用户区鼠标消息

windowS不希望用户使用这种消息,系统对这些消息都预置了默认的处理,用户最好不要干涉

WM_NCLBUTTONDOWN

, 需要手动添加,即三步走,第一步在类中声明方法,第二步在BEGIN_MESSAGE_MAP中添加ON_WM_NCMOUSEMOVE(),第三步实现方法

 

7-4

SetCapture();// 有与没有区别就在于鼠标是否出了用户区

它一旦调用,则应用程序窗口将是鼠标消息去向的唯一目的地

 

7-5处理键盘消息

键盘消息总是传给带有输入焦点的窗口

每键有个编码,叫做扫描码,是与设备相关的,为此window设计了虚拟码,与设备无关。编程直接使用虚拟码

VK_ADD

VK_CONTROL等等

7-5画一个圆,按下向左或向右,它就移动

响应的是ON_WM_KEYDOWN()

afx_msgvoid OnKeyDown(UINT nChar, UINTnRepCnt, UINT nFlags);

需要使用switch case判断 另外还要判断以防移出边界

 

一般键盘消息

WM_CAHR//

afx_msg voidOnChar(

   UINT nChar, // 是字符对应的ASCII

   UINT nRepCnt, // 键重复的次数

   UINT nFlags // 32

);

WM_KEYDOWN

WM_KEYUP

 

 

7-6增加一功能,按R或L,可使向左向右移动

 

系统按键消息,是ALT+其他键。

一般用户不处理,如果处理,则还要在后面添加DefWindowsProc以便不影响系统的处理

 

窗口焦点

获得焦点发出WM_SETFOCUS消息,失去时发出WM_KILLFOCUS消息

7-7

获得与失去焦点,只有在有焦点的时候,按键才有消息,这里只是让它响一声

 

第八章 资源

除了文档中的数据,用户界面UI还需要一些特殊数据,不一定在任何时候都用的上,一般在磁盘上,只有当需要时才载入内存

在世界范围内发布的软件,UI就有各种不同版本,以适应不同国家民族不同语言图标光标和风格需要

一个完善的程序要有多套UI数据。

资源就是一种可供window应用程序利用,可单独编辑,并可动态加载的数据

资源头文件Resourc.h + 资源描述文件(资源脚本文件)

 

资源标识符的前缀

IDR_       主菜单,工具栏,加速键表和应用程序图标

IDD_       对话框

IDC_       控件和光标

IDS_       字符串

IDP_       消息框提示符

ID_        菜单命令

 

发布Release版本

在VC6.0下,工具,定制,显示出编译工具栏,然后选择release   重新编译就行

 

CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(), 等等) // 加载所有标识符为它的各种资源

 

res文件

对于位图,图标,鼠标光标等这类图形数据,放在res文件夹中,在资源描述文件中只说明它们的名字和存储路径

在编译时,把上两个文件,生成一个二进制res文件。

res文件夹+ .lib库文件 + .obj文件 --- > 生成exe文件

 

8-1增加

MENUITEM"打印字符(&p)\tCtrl+P",         ID_FILE_PRT 菜单项

 

8-2增加IDR_MAINFRAME1 MENU PRELOADDISCARDABLE //资源名称

在应用程序类的InitInstance中

    pDocTemplate = new CSingleDocTemplate(

       IDR_MAINFRAME1,

       RUNTIME_CLASS(CMFCexp8_2Doc),

       RUNTIME_CLASS(CMainFrame),       // main SDI frame window

       RUNTIME_CLASS(CMFCexp8_2View));

加快捷键 8-2

IDR_MAINFRAME1ACCELERATORS PRELOAD MOVEABLE PURE

BEGIN

    "Z",            ID_ZOOM,            VIRTKEY,CONTROL

    "R",            ID_RE,           VIRTKEY,CONTROL

END

菜单MENU的选项

IDR_MAINFRAME1MENU PRELOAD DISCARDABLE

 

DISCARDABLE   应用程序不需要时可以丢弃

FIXEDP        保存在内存的固定位置

LOADONCALL    只有在应用程序需要时才加载菜单

MOVEABLE      在内存中可以移动位置

PRELOAD       主即加载

 

POPUP的选项

POPUP"选项(&F)" GRAYED

MENUBARBREAK  菜单纵向分隔标志

CHECKED       菜单项带有选中标志

INACTIVE      无效

GRAYED        无效且灰

 

8-3菜单命令选项的动态修改

选项前加了对号 

ON_COMMAND(ID_XUANX1,OnXuanx1)

    ON_UPDATE_COMMAND_UI(ID_XUANX1,OnUpdateXuanx1)

函数的声明就不说了,实现如下:

voidCMFCexp8_3View::OnXuanx1()

{

    m_nOption1=!m_nOption1;

}

voidCMFCexp8_3View::OnUpdateXuanx1(CCmdUI *pCmdUI)

{

    pCmdUI->SetCheck(m_nOption1);

}

其中OnUpdateXuanx1(CCmdUI *pCmdUI)是菜单选项的UI函数,参数是CCmdUI类的对象指针,这个类的四个方法如下:

Enable    

SetCheck// 标记

SetRadio   //参数为TURE,出现选中标记,其他不出现

SetText 设置文本

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值