MFC - 03 视图、文档

视图窗口

图中的圆和框代表了宏定义展开后的静态变量消息处理数组

覆盖在主框架客户区的窗口,提供了一个专门用于显示数据的窗口。当不创建 CView 对象时,在窗口客户区看见的就是客户区,不是视图类。

相关类:CView 及其相关子类,父类为CWnd类,封装了关于视图窗口的各种操作,以及和文档类的数据交互

使用:

  1. 定义一个自己的视图类(CMyView),派生自 CView,并重写父类成员纯虚函数 OnDraw (可用于绘图)
  2. 其余框架类和应用程序类代码不变
  3. 处理主框架窗口的 WM_CREATE 消息时,定义 CMyView 类对象,并调用 Create 函数创建视图窗口。
    视图窗口ID为 AFX_IDW_PANE_FIRST
#include <afxwin.h>

class CMyView : public CView
{
public:
    void OnDraw(CDC* pDc);
};

void CMyView::OnDraw(CDC* pDC)
{
    pDC->TextOutA(100, 100, "CMyView::OnDraw"); // 父类中用于处理 WM_PAINT 的纯虚函数
}

// --------------------------------------------------------------

class CMyFrameWnd : public CFrameWnd
{
    DECLARE_MESSAGE_MAP()
public:
    afx_msg int OnCreate(LPCREATESTRUCT);
private:
    CMyView *pView;
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
    ON_WM_CREATE()
END_MESSAGE_MAP()

int CMyFrameWnd::OnCreate(LPCREATESTRUCT lps)
{
    this->pView = new CMyView;
    pView->Create(
        NULL, 
        "MFCView",   // 窗口标题,View窗口不会显示,故窗口标题无所谓
        WS_CHILD | WS_VISIBLE | WS_BORDER, // 显示风格
        CRect(0, 0, 200, 200),   // View 窗口大小,大小随父窗口变化时此参数无用
        this,                    // 父窗口指针
        1001                     // 窗口ID,大于等于 AFX_IDW_PANE_FIRST 时大小随父窗口变化
                                 // 当窗口框架内有不止一个View时,其他ID递增 AFX_IDW_PANE_FIRST+1  AFX_IDW_PANE_FIRST+2
    );
    return CFrameWnd::OnCreate(lps);
}

// --------------------------------------------------------------

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

BOOL CMyWinApp::InitInstance()
{
    CMyFrameWnd* pFrame = new CMyFrameWnd;
    this->m_pMainWnd = pFrame;
    pFrame->Create(NULL, "title");
    pFrame->ShowWindow(SW_SHOW);
    pFrame->UpdateWindow();
    return TRUE;
}

CMyWinApp theApp;

结果:

 

命令消息处理顺序

  1. 视图类
  2. 框架类
  3. 应用程序类

 

对象关系

应用程序对象持有框架类对象地址,框架类对象持有视图对象地址。

文档类

CDocument 提供了一个用于管理数据的类,封装了关于数据的管理(数据提取,数据转换,数据存储等),并和视图类进行数据交互。

程序创建过程

  1. 利用框架类对象地址(pFrame)调用LoadFrame函数,创建框架窗口
  2. 处理框架窗口的 WM_CREATE 消息时,动态创建视图类对象,并创建视图窗口
    视图类实现了动态创建,由框架类的父类 CFrameWnd 动态创建视图类
  3. 处理视图窗口的 WM_CREATE 消息时,将文档类对象和视图类对象建立关联关系
查看代码
#include <afxwin.h>
#include <afxext.h>

#include "resource.h"


class CMyDoc : public CDocument
{

};

// ---------------------------------------------------

class CMyView : public CView
{
    DECLARE_DYNCREATE(CMyView)       // 动态创建
public:
    virtual void OnDraw(CDC*);
};
IMPLEMENT_DYNCREATE(CMyView, CView)  // 实现动态创建
void CMyView::OnDraw(CDC* pDC)
{

}

// ---------------------------------------------------

class CMyFrameWnd : public CFrameWnd
{

};

// ---------------------------------------------------

class CMyWinApp : public CWinApp
{
public:
    virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance()
{
    CMyFrameWnd* pFrame = new CMyFrameWnd;

    CCreateContext cct;
    cct.m_pNewViewClass = RUNTIME_CLASS(CMyView); // 拿到 &CMyView::classCMyView 用于动态创建
    cct.m_pCurrentDoc = new CMyDoc;

    pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);

    this->m_pMainWnd = pFrame;
    pFrame->ShowWindow(SW_SHOW);
    pFrame->UpdateWindow();

    return TRUE;
}

CMyWinApp theApp;

文档类对象用一个链表成员变量保存视图类对象地址:一对多
视图类对象用一个普通成员变量保存文档类对象地址:多对一

在视图类窗口中处理 WM_COMMAND 消息时要先点击以下某个视图类,让 pFrame->m_pViewActive 被赋值,然后视图类窗口才能够处理消息。

手动激活视图窗口的方式:

// 重写的用于创建不规则框架类和视图类的虚函数
BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext)
{
    this->split.CreateStatic(this, 1, 2); // 创建1行2列的布局,两个视图相同
    this->split.CreateView(0, 0, RUNTIME_CLASS(CMyView), CSize(0, 0), pContext);
    this->split.CreateView(0, 1, pContext->m_pNewViewClass, CSize(0, 0), pContext);

    // 通过不规则框架类获得指定位置窗口,并将其设置为激活窗口
    this->m_ViewAcitve = (CView*)split.GetPane(0,0);
    
    return TRUE;
}

刷新窗口

当文档类数据发生变化时,调用 UpdateAllViews 刷新和文档类对象关联的视图类对象(视图窗口)。也就是向视图窗口发送绘图消息。

	// Update Views (simple update - DAG only)
	void UpdateAllViews(
        CView* pSender, 
        LPARAM lHint = 0L,
		CObject* pHint = NULL
    );

刷新窗口的例子:

查看代码
#include <afxwin.h>
#include <afxext.h>
#include "resource.h"

class CMyDoc : public CDocument
{
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnFileNew();
    CString str = "default string";
};
BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
    ON_COMMAND(ID_FILE_NEW, OnFileNew)
END_MESSAGE_MAP()

void CMyDoc::OnFileNew()
{
    str = "new string";
    //this->UpdateAllViews(NULL); // 刷新所有窗口,参数传递的窗口不会被刷新
    
    POSITION pos = this->GetFirstViewPosition(); // 获得 viewList 迭代器的头节点的前一个
    CView* pView = this->GetNextView(pos);       // 得到第一个窗口
    this->UpdateAllViews(pView);                 // 刷新第一个窗口以外的窗口

}



class CMyView : public CView
{
    DECLARE_DYNCREATE(CMyView)
public:
    virtual void OnDraw(CDC*);
};
IMPLEMENT_DYNCREATE(CMyView, CView)
void CMyView::OnDraw(CDC* pDC)
{
    // CMyDoc* pDoc = (CMyDoc*)this->m_pDocument;   // 变量是保护性的,非视图类无法访问
    CMyDoc* pDoc = (CMyDoc*)this->GetDocument(); // 有公共的访问函数
    pDC->TextOut(100, 100, pDoc->str);
}



class CMyFrameWnd : public CFrameWnd
{
public:
    virtual BOOL OnCreateClient(LPCREATESTRUCT, CCreateContext*);
private:
    CSplitterWnd split;
};
BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext)
{
    this->split.CreateStatic(this, 1, 2); // 创建1行2列的布局,两个视图相同
    this->split.CreateView(0, 0, RUNTIME_CLASS(CMyView), CSize(0, 0), pContext);
    this->split.CreateView(0, 1, pContext->m_pNewViewClass, CSize(0, 0), pContext);

    // 设置活动窗口
    this->m_pViewActive = (CView*)this->split.GetPane(0, 0);

    return TRUE;
}


class CMyWinApp : public CWinApp
{
public:
    virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance()
{
    CMyFrameWnd* pFrame = new CMyFrameWnd;

    CCreateContext cct;
    cct.m_pNewViewClass = RUNTIME_CLASS(CMyView); // 拿到 &CMyView::classCMyView 用于动态创建
    cct.m_pCurrentDoc = new CMyDoc;

    pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);

    this->m_pMainWnd = pFrame;
    pFrame->ShowWindow(SW_SHOW);
    pFrame->UpdateWindow();

    return TRUE;
}

CMyWinApp theApp;

文档类和视图类的关系

  1. 视图类成员函数获取关联的文档类对象,调用 GetDocument()
  2. 当文档类数据发生变化时,调用 UpdateAllView() 刷新和文档类对象关联的视图类对象(视图窗口)
    获取视图对象并刷新:
        POSITION pos = this->GetFirstViewPosition(); // 获得 viewList 迭代器的头节点的前一个
        CView* pView = this->GetNextView(pos);       // 得到第一个窗口
        this->UpdateAllViews(pView);                 // 刷新第一个窗口以外的窗口

窗口切分

不规则窗口:有多个视图类

CSplitterWnd:不规则框架窗口类,封装了关于不规则框架窗口的操作

窗口切分的操作:

重写 CFrameWnd 类的成员虚函数 OnCreateClient

在虚函数中调用 CSplitterWnd::CreateStatic 创建不规则框架窗口,调用 CSplitterWnd::CreateView 创建视图窗口

结构如下:

主框架窗口的客户区覆盖了一个不规则框架窗口,这个窗口分出了两个视图窗口。

 

#include <afxwin.h>
#include <afxext.h>

#include "resource.h"


class CMyDoc : public CDocument
{

};

// ---------------------------------------------------

class CMyView : public CView
{
    DECLARE_DYNCREATE(CMyView)       // 动态创建
public:
    virtual void OnDraw(CDC*);
};
IMPLEMENT_DYNCREATE(CMyView, CView)  // 实现动态创建
void CMyView::OnDraw(CDC* pDC)
{
    pDC->TextOutA(100, 100, "View窗口输出的字符串");
}

// ---------------------------------------------------

class CMyFrameWnd : public CFrameWnd
{
    DECLARE_MESSAGE_MAP()
public:
    virtual BOOL OnCreateClient(LPCREATESTRUCT, CCreateContext*);
private:
    CSplitterWnd split;
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
END_MESSAGE_MAP()

BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext)
{
    this->split.CreateStatic(this, 1, 2); // 创建1行2列的布局,下面两个视图相同
    this->split.CreateView(0, 0, RUNTIME_CLASS(CMyView), CSize(0, 0), pContext);
    this->split.CreateView(0, 1, pContext->m_pNewViewClass, CSize(0, 0), pContext);

    return TRUE;
}

// ---------------------------------------------------

class CMyWinApp : public CWinApp
{
public:
    virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance()
{
    CMyFrameWnd* pFrame = new CMyFrameWnd;

    CCreateContext cct;
    cct.m_pNewViewClass = RUNTIME_CLASS(CMyView); // 拿到 &CMyView::classCMyView 用于动态创建
    cct.m_pCurrentDoc = new CMyDoc;

    pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);

    this->m_pMainWnd = pFrame;
    pFrame->ShowWindow(SW_SHOW);
    pFrame->UpdateWindow();

    return TRUE;
}

CMyWinApp theApp;


这两个视图对象对应一个文档类。对象视图类对象创建时会产生 WM_CREATE 消息,处理函数会将二者关联。

 

WM_COMMAND 消息处理顺序

处理顺序,在其中哪一个处理都可以:

  1. 视图类
  2. 文档类
  3. 框架类
  4. 应用程序类
// 主要对象组织顺序
theApp
    m_PMainWnd               // 框架类对象地址
        m_pViewActive        // 活动视图类对象地址 pView
            m_pDocument      // 文档类对象地址 pDoc
                m_viewList   // 所有视图类对象地址


图中的圈指宏展开后的静态变量,框指消息处理函数映射表。

在处理 WM_COMMAND 消息时,进入框架消息处理函数,调用虚函数 CFrameWnd::OnCmdMsg(可重写),内部先获得活动视图类对象调用其消息处理函数,然后获得视图绑定的文档类的消息处理函数,再查找自己的消息处理函数,最后通过全局变量获取应用程序类再获取其的消息处理函数。

 

posted @ 2022-08-03 11:56  某某人8265  阅读(140)  评论(0编辑  收藏  举报