1 引言
在MicrosoftVC++ 6.0 中,基于MFC 的應用程式一般分為以下幾種:多文檔界面(MDI)、單文檔界面(SDI)以及基于對話框的應用程式。其中單文檔又可分為單視圖的和多視圖的,一般情況下,單文檔僅需要單視圖就夠了,如Windows 自帶的記事本、畫圖程式等等,但在一些情況下,單文檔需要多視圖支援,比如同時觀察文檔的不同部分,同時從不同的角度觀察同一文檔等。在MFC 的架構下,文檔對象(CDocument)有一個儲存其所有視圖的清單,并提供了增加視圖(AddView)與删除視圖(RemoveView)函數,以及當文檔内容改變時通知其所有視圖的方法(UpdateAllViews)。通過多文檔架構的視窗複制機制和單文檔架構的分割視窗機制是實作單文檔多視圖的主要方法。
2 單文檔的多視圖
一般地,單文檔與多視圖有三種情況:
(1)在多文檔界面MDI 中,每個視圖位于MDI 的一個獨立子文檔架構中,視圖對象基于同一個視圖類。使用者可以通過“視窗| 新視窗”菜單,為同一文檔的視圖再建立一個視窗,通過新建立的視窗,可以編輯和觀察文檔的另一部分,同一文檔各個視圖之間自動實作同步,使用者修改一個視圖的内容,在另外的視圖中也自動更新。MFC 架構通過複制原來的子架構視窗和其中的視圖來實作上面的功能,并且是完全自動的。
(2)視圖對象基于同一視圖類,所有視圖位于同一文檔架構中。分割視窗将單文檔視窗的視圖區分割成幾個獨立的視圖,架構從同一視圖類建立多個視圖對象。Word 的子視窗即屬于這種類型。
(3)視圖對象基于不同的視圖類,所有的視圖位于同一文檔架構中。多個視圖共享同一文檔架構,但從不同的視圖類建立,每個視圖可以為文檔提供不同的觀察和編輯方法。比如在一個視窗裡觀察文檔的不同部分,或者是在一個視窗裡用不用類型的視圖觀察同一個文檔。這種類型的實作方法是通過重載架構類CMainFrame 的成員函數OnCreateClient 實作,使用者可以根據不同需要将視窗分為垂直或水準的多個分割視窗。
下面通過執行個體設計,介紹單文檔多視圖的視窗分割和多視圖之間的通信的實作方法。
執行個體為一個基于單文檔的MFC應用程式,通過靜态分割視窗的方式三叉切分視窗,即共有
三個窗格。程式實作的功能是使用者可以輸入學生的資訊,并添加到清單視圖中。程式最終運
行的結果如下圖:

其中左側的基本資訊輸入的窗格采用的是CFormView 類型的視圖,在使用者可以其中進行資訊的錄入,單擊“送出”按鈕,資料就添加道文檔中了,并在右側的清單視圖中顯示。右側資訊顯示的窗格采用的是CListView 類型的視圖,顯示文檔中存儲的所有學生資訊。而底部的窗格采用的是CEditView 類型的視圖,用于提示使用者上一步添加的資料。下面介紹具體的實作過程。
建立工程
使用AppWizard 建立一個基于單文檔的應用程式架構工程,工程名為“Guo”,其餘的現
象均采用預設設定。
添加視圖類
需要為 3 個窗格添加3 個視圖類。CLeftFormView、CTopListView、CBottomEditView,其
基類分别為CFormView、CListView 和CEditView。
1、CLeftFormView 類的實作
A、 添加對話框資源模闆:添加CLeftFormView 類之前,首先要向工程中添加
CLeftFormView視圖中對話框模闆,如下圖所示:
對話框模闆的ID 為“IDD_DIALOG1”,其Style 屬性設定為“Child”,Bolder 屬性設定為
“None”。
B、添加CLeftFormView 類。執行“Insert”→“New Class”菜單指令,彈出“New Class”對話
框,在其中的Name 編輯框中輸入類名“CLeftFormView”,在Base Class 清單框中選擇基類
“CFormView”選項,在Dialog ID 清單框中選擇“IDD_DIALOG1”對話框資源。單擊Ok 即可
實作CLeftFormView 類的添加。
C、添加CLeftFormView 類的相關資源:利用Class Wizard 在CLeftFormView 中,為對話框
模闆的4 個編輯控件分别添加CString 類型的成員變量m_Num、m_Name、m_Magor、
m_Home,并為“送出”按鈕添加BN_CLICKED 消息響應函數OnSubmit()。
2、CTopListView 類的實作
同樣使用“NewClass”對話框,添加CTopListView 類,将其基類選擇類型為CListView。
然後使用ClassWizard 重載該類的PreCreateWindow()函數,在其中定義清單視的類型,代碼如下:
BOOL CTopListView::PreCreateWindow(CREATESTRUCT& cs)
{
//TODO: 在此添加專用代碼和/或調用基類
cs.style= cs.style | LVS_REPORT;// 設定成報告清單的顯示形式
return CListView::PreCreateWindow(cs);
}
使用ClassWizard 重載CTopListView 類的OnInitialUpdate()函數,在其中添加清單的表頭,代碼如下:
void CTopListView::OnInitialUpdate()
{
CListView::OnInitialUpdate();
//TODO: 在此添加專用代碼和/或調用基類
CString m_ColumnLabelStr[] = { L"學号", L"姓名", L"專業", L"籍貫" };
//表頭字段
CListCtrl& listctrl = GetListCtrl();//擷取清單的控件
DWORD dwStyle = listctrl.GetExtendedStyle();
dwStyle |= LVS_EX_FULLROWSELECT;
// 選中某行使整行高亮(隻适用與report 風格的listctrl)
dwStyle |= LVS_EX_GRIDLINES;
dwStyle |= LVS_EX_UNDERLINEHOT;
listctrl.SetExtendedStyle(dwStyle);//清單風格
int width[6] = { 80, 80, 110, 150 };
for (int i = 0; i < 4; i++)
{
listctrl.InsertColumn(i,m_ColumnLabelStr[i], LVCFMT_LEFT,width[i]); // 設定表頭
}
}
3、CBottomEidtView 類的實作
同樣使用 NewClass 對話框,添加CBottomEditView 類,将其基類選擇為“CEditView”。
而後使用ClassWizard 重載該類的OnInitialUpdate()函數,在其中實作初始化設定,代碼
如下:
void CBottomEditView::OnInitialUpdate()
{
CEditView::OnInitialUpdate();
//TODO: 在此添加專用代碼和/或調用基類
CEdit &mEdit = GetEditCtrl(); //擷取編輯視圖的控件
mEdit.SetWindowText(L"等待使用者輸入學生的資訊!");//設定顯示資訊
mEdit.EnableWindow(FALSE); //編輯控件不可編輯
}
靜态分割視窗的實作
視窗的分割過程中是首先在主架構 CMainFrame 中,将視窗分割成上下兩個窗格,對應的視圖分别為CGuoView 和CBottomEditView。而後,再在CGuoView 視圖中将窗格分為左右兩個窗格,對應的視圖分别為CLeftFormView 和CTopListView,實作過程如下。
1、在 CMainFrame 類的頭檔案中,聲明一個CSplitterWnd 類的成員變量m_Splitterwnd1,用于第一個視窗的分割
protected: // 控件條嵌入成員
CMFCMenuBar m_wndMenuBar;
CMFCToolBar m_wndToolBar;
CMFCStatusBar m_wndStatusBar;
......
CSplitterWnd m_Splitterwnd1; //用于産生第一次的靜态的分割
2、使用 Class Wizard 重載CMainFrame 類的OnCreateClient()函數,在其中實作第一次的
視窗分割。
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
//TODO: 在此添加專用代碼和/或調用基類
CRect rect;
GetClientRect(&rect); //産生第一次靜态分割
m_Splitterwnd1.CreateStatic(this, //父視窗指針
2, 1); //行數與列數
m_Splitterwnd1.CreateView(0,0, //窗格的行列序數
RUNTIME_CLASS(CGuoView),//視圖類
CSize(rect.Width(), rect.Height() - rect.Height() / 5), pContext);//父視窗建立參數
m_Splitterwnd1.CreateView(1,0, RUNTIME_CLASS(CBottomEditView),
CSize(rect.Width(), rect.Height() / 5), pContext);
//不在調用基類的OncreateClient 函數
return true;
//returnCFrameWndEx::OnCreateClient(lpcs, pContext);
}
包含相應的頭檔案,在MainFrame.cpp 檔案的開始加入下列語句
#include"GuoView.h"
#include"BottomEditView.h"
3、在 視圖 窗 口 類 CGuoView 的頭檔案中聲明一個CSplitterWnd 類的成員變量m_Splitterwnd2,用于第二次視窗分割。
protected:
CSplitterWnd m_Splitterwnd2;//用于第二次視窗的分割
4、使用 Class Wizard 重載CGuoView 類的OnCreate()和OnSize()函數,實作視窗第二次分割并設定窗格的大小。
int CGuoView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct)== -1)
return -1;
//TODO: 在此添加您專用的建立代碼
CRect rect;
GetClientRect(&rect);
//獲得視窗的建立資訊指針
CCreateContext *pContext = (CCreateContext*)lpCreateStruct->lpCreateParams;
m_Splitterwnd2.CreateStatic(this,1,2);//産生第二次的靜态分隔
//為第一個窗格産生視圖
m_Splitterwnd2.CreateView(0,0, RUNTIME_CLASS(CLeftFormView), CSize(rect.Width() / 4, rect.Height()),pContext);
//為第二個窗格産生視圖
m_Splitterwnd2.CreateView(0,1, RUNTIME_CLASS(CTopListView), CSize(1, 1), pContext);
return 0;
}
void CGuoView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
//TODO: 在此處添加消息處理程式代碼
CRect rect;
GetClientRect(&rect);
int x = rect.Width();
int y = rect.Height();
m_Splitterwnd2.MoveWindow(-2,-2, x, y + 3);
m_Splitterwnd2.SetColumnInfo(0,x / 4, 0); //左邊窗格位置
m_Splitterwnd2.SetColumnInfo(1,x - x / 4, 0); //右邊窗格位置
m_Splitterwnd2.RecalcLayout();
}
至此,視窗的分割完成,編譯運作程式,就會發現三叉視窗已經實作。如果在編譯連接配接
程式的時候出現如下面的錯誤:
c:\documentsand settings\chenqi\桌面\guo\guoview.h(23) :error C2143: syntax error : missing
';'before '*'
c:\documentsand settings\chenqi\桌面\guo\guoview.h(23) :error C2501: 'CGuoDoc' : missing
storage-classor type specifiers
c:\documentsand settings\chenqi\桌面\guo\guoview.h(23) :error C2501: 'GetDocument' :
missingstorage-class or type specifiers
則可以在CGuoView 類頭檔案的前面加上這麼一句class CGuoDoc; 就沒有問題了。
窗格視圖與文檔的互動
視窗中分割的各窗格視圖對應着同一文檔對象CGuoDoc,每個CView 派生類都已經繼
承了GetDocument()函數,是以隻要在調用後進行類型的強制轉換就可以擷取文檔的對象。
如:CGuoDoc*pDoc=(CGuoDoc*)GetDocument();
本執行個體在文檔對象CGuoDoc 中,通過數組類對象存儲學生資訊,當在CLeftFormView
視圖中,輸入學生資訊單擊“送出”按鈕時,就将輸入資訊寫入文檔中的數組對象,并重繪
各視圖。
1、在 CGuoDoc 類的頭檔案中聲明數組對象和資料修改标記,如下:
Public:
CStringArrayinfoArray[4];
booladd;
并在構造函數中将add 的值初始化為FALSE。
2、在 CLeftFormView 類的按鈕響應函數OnSubmit()中,添加代碼實作控件資料的保
存并更新所有視圖。
void CLeftFormView::OnSubmit()
{
//TODO: 在此添加控件通知處理程式代碼
UpdateData(TRUE); // 擷取對話框的控件資料
if (m_Num.IsEmpty() || m_Name.IsEmpty()) //判斷是否為空
{
AfxMessageBox(L"學号和姓名不能為空!"); return;
}
CGuoDoc* pDoc = (CGuoDoc*)GetDocument();// 擷取文檔
pDoc->infoArray[0].InsertAt(0,m_Num); // 輸入資料插入資料
pDoc->infoArray[1].InsertAt(0,m_Name);
pDoc->infoArray[2].InsertAt(0,m_Magor);
pDoc->infoArray[3].InsertAt(0,m_Home);
pDoc->add= true; //添加了資料
pDoc->UpdateAllViews(NULL); //更新所有視圖
m_Num = _T("");
m_Name = _T("");
m_Magor = _T("");
m_Home = _T("");
UpdateData(FALSE); //各控件的内容清空
}
包含CGuoDoc 類的頭檔案,在CLeftFormView.cpp 檔案開始加入下列語句:
#include"GuoDoc.h"
3、重載視圖類 CTopListView 和CBottomEditView 中OnUpdate()函數,實作視圖更新。
void CTopListView::OnUpdate(CView* , LPARAM , CObject* )
{
//TODO: 在此添加專用代碼和/或調用基類
CGuoDoc* pDoc = (CGuoDoc*)GetDocument(); //擷取文檔指針
if (pDoc->add) //添加了資料
{
CListCtrl& listctrl = GetListCtrl(); // 擷取清單的控件
listctrl.DeleteAllItems(); //删除所有項
for (int i = 0; i < pDoc->infoArray[0].GetSize(); i++) //清單框中插入資料
{
listctrl.InsertItem(i,pDoc->infoArray[0].GetAt(i));
listctrl.SetItemText(i,1, pDoc->infoArray[1].GetAt(i));
listctrl.SetItemText(i,2, pDoc->infoArray[2].GetAt(i));
listctrl.SetItemText(i,3, pDoc->infoArray[3].GetAt(i));
}
}
}
void CBottomEditView::OnUpdate(CView* , LPARAM , CObject* )
{
//TODO: 在此添加專用代碼和/或調用基類
CGuoDoc* pDoc = (CGuoDoc*)GetDocument(); // 擷取文檔指針
if (pDoc->add) // 添加了資料
{
CString str;
str =L"添加了學号為" + pDoc->infoArray[0].GetAt(0) + L"的學生資訊!";
CEdit &mEdit = GetEditCtrl(); //擷取編輯視圖控件
mEdit.SetWindowText(str); //顯示資訊
}
}
同樣需要在這兩個視圖類的資源檔案中包含文檔對象的頭檔案,如下:
#include"GuoDoc.h"
至此,執行個體開發結束,編譯運作工程,即可實作要求的結果。
注意:編譯可能會報:
errorC2143: 文法錯誤 : 缺少“;”(在“*”的前面)
errorC2501: “CTestView::CTestDoc” : 缺少存儲類或類型說明符
errorC2501: “CTestView::GetDocument” : 缺少存儲類或類型說明符
warningC4183: “GetDocument”:缺少傳回類型;假定為傳回“int”的成員函數
解決方法:
C***View.h檔案頭添加
#include"C***Doc.h"
同時可以把C**View.cpp中上面去掉.