Frame、View、Document的關係,非常重要,它們的確不是孤立的,而是互相聯繫的。
如果是SDI程序,CMainFrame調用GetActiveView獲得CView,而CView可以調用GetDocument獲得CDocument。如果已知CDocument,可以使用GetFirstViewPosition獲得第一個相關的CView,然後使用GetNextView獲得下一個的CView。之所以這麼做,是因爲MFC允許一個CDocument擁有多個CView。
如果是MDI程序,稍微複雜一些。你不能直接調用GetActiveView,而需要先調用CMainFrame::MDIGetActive獲得當前的CMDIChildWnd,而這個CMDIChildWnd相當於SDI中的CMainFrame
1、框架窗口
框架窗口爲應用程序的用戶界面提供結構框架,它是應用程序的主窗口,負責管理其包容的窗口,一個應用程序的最頂層的框架窗口是應用程序啓動時創建的第一個窗口。
MFC提供三種類型的框架窗口:單文檔窗口,多文檔窗口(MDI),對話框。在AppWizard的第一個對話框中,就提供了選項,讓用戶選擇應用程序是基於單文檔、多文檔還是對話框的。MFC單文檔窗口一次只能打開一個文檔框架窗口,而MDI應用程序運行時,在應用程序的一個實例中打開多個文檔框架窗口,這些窗口稱作子窗口(Child Window)。這些文檔可以是同一類型的,也可以是不同類型的。如Visual Studio就可以打開資源文件窗口和源程序窗口等不同類型的窗口。此時,激活不同類型的MDI子窗口,菜單也將相應變化。
MFC提供了三個類CFrameWnd、CMDIFrameWnd、CMDIChildWnd和CDialog 分別用於支持單文檔窗口、多文檔窗口和對話框。
CFrameWnd
用於SDI框架窗口,形成單個文檔及其視的邊框。框架窗口既是應用程序的主框架窗口,也是當前文檔對應的視圖的邊框。
CMDIFrameWnd
用於MDI應用程序的主框架窗口。主框架窗口是所有MDI文檔窗口的容器,並與它們共享菜單條。MDI框架窗口是出現在桌面中的頂層窗口。
CMDIChildWnd
用於在MDI主框架窗口中顯示打開的各個文檔。每個文檔及其視都有一個MDI子框架窗口,子框架窗口包含在MDI主框架窗口中。子框架窗口看起來類似一般的框架邊框窗口,但它是包含在主框架窗口中,而不是位於桌面的,並且爲主窗口所裁剪。而且MDI子窗口沒有自己的菜單,它與主MDI框架窗口共享菜單。
CDialog
對話框是一種特殊類型的窗口,它的邊框一般不可以調整,而且內部包含一些控件窗口。有關對話框作爲主窗口的技術可以參見下一章。
要生成一個單文檔窗口,主窗口就必須從CFrameWnd派生;要生成一個多文檔窗口,主窗口就必須從CMDIFrameWnd派生,而且其中的子窗口必須從CMDIChildWnd派生出來;而基於對話框的窗口程序就要從CDialog派生出主窗口類。
子窗口
子窗口就是具有WS_CHILD風格的窗口,且一定有一個父窗口。所有的控件都是子窗口。子窗口可以沒有邊框。子窗口被完全限制在父窗口內部。
父窗口
父窗口就是擁有子窗口的窗口。
彈出式窗口
具有WS_POPUP風格,它可以沒有父窗口。這種窗口幾乎什麼都沒有,可看作一個矩形區域。
2、窗口的創建
窗口的創建分爲兩步:第一步是用new創建一個C++的窗口對象,但是此時只是初始化窗口的數據成員,並沒有真正創建窗口(這一點與一般的對象有所不同)。
2.1、第一步:創建一個C++對象,其中CMainFrame是從CFrameWnd派生的對象。
CMainFrame* pMyFrame=new CMainFrame();//用new操作符創建窗口對象
或
CMainFrame MyFrame;//定義一個窗口對象,自動調用其構造函數
2.2、第二步是創建窗口。CFrameWnd的Create成員函數把窗口給做出來,並將其HWND保存在C++對象的公共數據成員m_hWnd中。
//第二步:創建窗口
pMyFrame->Create(NULL,“My Frame Window”);
或
MyFrame.Create(NULL,“My Frame Window”);
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 );
Create函數第一個參數爲窗口註冊類名,它指定了窗口的圖標和類風格。這裏我們使用NULL做爲其值,表明使用缺省屬性。第二個參數爲窗口標題。其餘幾個參數指定了窗口的風格、大小、父窗口、菜單名等。
這個函數看起來比較複雜,對於CFrameWnd派生出來的窗口,我們可以使用LoadFrame從資源文件中創建窗口,它只需要一個參數。
pMyFrame->LoadFrame(IDR_FRAME);
LoadFrame使用該參數從資源中獲取許多默認值,包括主邊框窗口的標題、圖標、菜單、加速鍵等。但是,在使用LoadFrame時,必須確保標題字符串、圖標、菜單、加速鍵等資源使用同一個ID標識符(在本例中,我們使用IDR_FRAME)。
提示:在Hello程序的InitInstance中我們看不到創建窗口的過程。實際上,在
pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CHelloDoc),RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CHelloView));
AddDocTemplate(pDocTemplate);
程序片段中,我們看到,CSingleDocTemplate構造函數的第二個參數就是IDR_MAINFRAME。在構造函數內部,已經通過調用m_pMainWnd->LoadFrame(IDR_MAINFRAME),完成了應用程序主窗口的創建過程。
在InitInstance中,創建完窗口後,窗口調用ShowWindow成員函數來顯示窗口。ShowWindow帶一個參數,指示窗口以何種方式顯示(最大化、最小化或一般)。缺省方式爲SW_SHOW,但實際上我們經常希望應用程序啓動時窗口最大化,此時可以將該參數該爲SW_SHOWMAXMIZED,即調用
m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);
在MainFrm.cpp中,我們還看到CMainFrame類有一個OnCreate方法。OnCreate成員函數定義如清單3.3。當調用Create或CreateEx時,操作系統會向窗口發送一條WM_CREATE消息。這一函數就是用來響應WM_CREATE消息的。
清單3.3 OnCreate成員函數定義
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{if (CFrameWnd::OnCreate(lpCreateStruct) == -1)return -1;
if (!m_wndToolBar.Create(this) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar/n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))){
TRACE0("Failed to create status bar/n");
return -1; // fail to create
}
// TODO: Remove this if you don't want tool tips or a resizeable toolbar
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
在OnCreate函數中,首先調用CFrameWnd的缺省處理方法OnCreate完成窗口創建工作。後面是應用程序主窗口的特定工作,在上面程序中,創建了工具條和狀態欄(有關工具條和狀態欄編程參見下一章有關內容)。可以在此處加入一些初始化工作,如從INI文件中載入設置,顯示Splash Window(啓動畫面)等。
3 、註冊窗口
在傳統的Windows C程序中,送給一個窗口的所有消息是在它的窗口函數中處理的。把一個窗口同它的窗口函數聯繫起來的過程稱爲註冊窗口類。註冊窗口包括對窗口指定一個窗口函數(給出窗口函數的指針)以及設定窗口的光標、背景刷子等內容。一個註冊窗口類可以被多個窗口共享。註冊窗口通過調用API函數RegisterClass來完成。
在MFC下,框架提供了缺省的自動窗口註冊過程。框架仍然使用傳統的註冊類,而且提供了幾個標準的註冊類,它們在標準的應用程序初始化函數中註冊。調用AfxRegisterWndClass全局函數就可以註冊附加的窗口類,然後把已經註冊的類傳給CWnd的Create成員函數。用戶可以定製自己的註冊過程,以提供一些附加的特性。比如設置窗口的圖標、背景、光標等。下面是註冊窗口的例子。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
UINT ClassStyle=CS_VREDRAW|CS_HREDRAW;
cs.style=cs.style&(~FWS_ADDTOTITLE);
cs.lpszClass = AfxRegisterWndClass(ClassStyle,
AfxGetApp()->LoadStandardCursor(IDC_ARROW), (HBRUSH)(COLOR_WINDOW+1),//for brush
AfxGetApp()->LoadIcon(IDR_MAINFRAME));
return TRUE;
}
註冊窗口在CFrameWnd的PreCreateWnd方法中完成。從成員函數名字PreCreateWindow中就可以看出來,註冊窗口的工作必須在調用Create函數創建窗口之前完成。其中cs.style=cs.style&(~FWS_ADDTOTITLE)指定窗口標題風格,關閉自動添加文檔標題的功能。AfxRegisterWndClass指定窗口使用箭頭光標、背景刷子使用比窗口顏色標號大一的顏色、圖標使用IDR_MAINFRAME標識符指定的圖標(當然也可以使用其它圖標)。用上面的程序段替換Hello程序MainFrm.cpp中的PreCreateWindow成員函數定義,並重新編譯和運行程序。此時,窗口標題變成了Hello,原來令人討厭的“Untitled-”沒有了,因爲窗口風格中關閉自動添加當前文件名的風格。
4、 關閉和銷燬窗口
框架窗口不僅維護窗口的創建,還管理着窗口的關閉和銷燬過程。關閉窗口時,操作系統依次向被關閉的窗口發送WM_CLOSE和WM_DESTROY消息。WM_CLOSE消息的缺省處理函數OnClose將調用DestroyWindow,來銷燬窗口;最後,框架調用窗口的析構函數作清理工作並刪除C++窗口對象。
不要使用C++的delete操作符來銷燬框架窗口,而應當採用CWnd的DestroyWindow成員函數來銷燬。DestroyWindow首先刪除子窗口,再刪除窗口本身。若窗口以變量方式產生(即在堆棧上分配內存),該窗口對象會被自動清除。若對象是用new操作符創建的(也就是在堆上分配內存的),則需要用戶自己處理。有關DestroyWindow問題在第五章對話框技術中還要作進一步解釋。
OnClose()常用功能:保存窗口的一些狀態、工具條狀態,提示保存未保存的數據等等。
void CMainFrame::OnClose()
{
SaveBarState( "MyDockState" );//保存工具條狀態
CFrameWnd::OnClose();
}
5、 窗口激活
活動窗口必定是一個沒有父窗口的頂層窗口,包括框架窗口和對話框。當頂層窗口被激活時,Windows向窗口發送WM_ACTIVATE消息,對此消息的缺省處理是將活動窗口設爲有輸入焦點。
輸入焦點用於表示哪個窗口有資格接收鍵盤輸入消息。帶有輸入焦點的窗口或是一個活動窗口,或者是該活動窗口的子窗口。當一個頂層窗口獲得輸入焦點時,Windows向該窗口發送WM_SETFOCUS消息,此窗口可將輸入焦點重定位到它的子窗口上。子窗口不會自動獲得輸入焦點。失去輸入焦點的窗口會收到WM_KILLFOCUS消息。當子窗口擁有輸入焦點時,父窗口就不會處理鍵盤輸入了。