多文檔 主框架窗口、客戶窗口、子窗口和視圖的關係,有圖顯示

多文檔程序及窗口的區域重繪

       MDI 應用程序是另一類重要的文檔視結構程序。它的特點是:用戶一次可以打開多個文檔,每個文檔對應不同的窗口;主窗口的菜單會自動隨着當前活動的子窗口的變化而變化;可以對子窗口進行層疊、平鋪等各種操作;子窗口可以在MDI 主窗口區域內定位、改變大小、最大化和最小化,當最大化子窗口時,它將佔滿MDI 主窗口的全部客戶區。MDI 不僅可以在同一時間內同時打開多個文檔,還可以爲同一文檔打開多個視圖。在Windows 菜單下選擇New ,就爲當前活動文檔打開一個新的子窗口。

從程序員角度看,每個MDI 應用程序必須有一個CMDIFrameWnd 或其派生類的實例,這個窗口稱作MDI 框架窗口。CMDIFrameWndCFrameWnd 的派生類,它除了擁有CFrameWnd 框架窗口類的全部特性外,還具有以下與MDI 相關的特性:

SDI 不同,主框架窗口並不直接與一個文檔和視圖相關聯。MDI 框架窗口擁有MDICLIENTMDI 客戶窗口),在顯示或隱藏控制條(包括工具條、狀態欄、對話條)時,重新定位該子窗口。

MDI 客戶窗口是MDI 子窗口的直接父窗口,它負責管理主框架窗口的客戶區以及創建子窗口。每個MDI 主框架窗口都有且只有一個MDI 客戶窗口。

MDI 主框架窗口、客戶窗口和子窗口的關係如下圖所示:

 

MDI 子窗口是CMDIChildWnd 或其派生類的實例,CMDIChildWndCFrameWnd 的派生類,用於容納視圖和文檔,它相當於SDI 下的主框架窗口。每打開一個文檔,框架就自動爲文檔創建一個MDI 子窗口。一個MDI 應用程序負責動態的創建和刪除MDI 子窗口。在任何時刻,最多隻有一個子窗口是活動的( 窗口標題欄顏色呈高亮顯示)MDI 框架窗口始終與當前活動子窗口相關聯,命令消息在傳給MDI 框架窗口之前首先分派給當前活動子窗口。

在沒有任何活動的MDI 子窗口時,MDI 框架窗口可以擁有自己的缺省菜單。當有活動子窗口時,MDI框架窗口的菜單條會自動被子窗口的菜單所替代。框架會自動監視當前活動的子窗口類型,並相應的改變主窗口的菜單。比如,在Visual Studio中,當選擇對話框資源編輯窗口或源程序窗口時,菜單會有所不同。但是,對於程序員來說,只需要在InitInstance 中註冊文檔時指定每一類子窗口(嚴格的講是文檔)所使用的菜單,而不必顯式的通過調用函數去改變主框架窗口的菜單,因爲框架會自動完成這一任務。

MDI 框架窗口爲層疊、平鋪、排列子窗口和新建子窗口等一些標準窗口操作提供了缺省的菜單響應。在響應新建子窗口命令時,框架調用CDocTemplate::CreateNewFrame() 爲當前活動文檔創建一個子窗口。CreateNewFrame() 不僅創建子窗口,還創建與文檔相對應的視圖。

下面,我們結合一個繪圖程序例子,介紹多文檔界面技術。在此之前,我們首先要介紹一下如何在Windows 中繪圖以及Windows 的圖形設備接口(GDI )。

 

 

 

窗口區域重繪

Windows通知窗口要重繪用戶區時,並非整個用戶區都需要重繪,需要重繪的區域稱爲無效矩形區。用戶區中出現一個無效矩形提示Windows在應用程序隊列中放置WM_PAINT消息。由於WM_PAINT消息優先級最低,可調用UpdateWindows直接立即向窗口發送WM_PAINT消息,從而立即重繪。無效矩形區限制程序只能在該區域中繪圖,越界的繪圖將被裁剪掉。下面三個函數與無效矩形有關:

l InvalidateRect 產生一個無效矩形,並生成WM_PAINT消息

l ValidateRect 使無效矩形區有效

l GetUpdateRect 獲得無效矩形座標(邏輯)

Windows爲每個窗口保留一個PAINTSTRUCT結構,其中包含無效矩形區域的座標值。

要想在自己的程序高效繪圖、只繪製無效矩形,首先需要重載視圖的OnUpdate成員函數。

virtual void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint );

當調用文檔的UpdateAllViews時,框架會自動調用OnUpdate函數,也可在視圖類中直接調用該函數。OnUpdate函數一般是這樣處理的:訪問文檔,讀取文檔的數據,然後對視圖的數據成員或控制進行更新,以反映文檔的改動。可以用OnUpdate函數使視圖的某部分無效,以便觸發視的OnDraw,利用文檔數據重繪窗口。缺省的OnUpdate使窗口整個客戶區都無效,在重新設計時,要利用提示信息lHintpHint定義一個較小的無效矩形。修改後的OnUpdate成員函數如下

void CDrawView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)

{

if (pHint != NULL)

{

if (pHint->IsKindOf(RUNTIME_CLASS(CStroke)))

{

CStroke* pStroke = (CStroke*)pHint;

CClientDC dc(this);

OnPrepareDC(&dc);

CRect rectInvalid = pStroke->GetBoundingRect();

dc.LPtoDP(&rectInvalid);

InvalidateRect(&rectInvalid);

return;

}

}

Invalidate(TRUE);

return;

}

這裏,傳給pHint指針的內容是指向需要繪製的筆畫對象的指針。採用強制類型轉換將它轉換爲筆劃指針,然後取得包圍該筆劃的最小矩形。OnPrepareDC用於調整視圖座標原點。由於InvalidateRect需要設備座標,因此調用LPToDP(&rectInvalid)將邏輯座標轉換爲設備座標。最後,調用InvalidateRect是窗口部分區域無效,也就是視圖在收到WM_PAINT消息後需要重繪這一區域。

InvalidateRect函數原型爲:

void InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE );

第一個參數是指向要重繪的矩形的指針,第二個參數告訴視圖是否要刪除區域內的背景。

這樣,當需要重畫某一筆劃時,只需要重畫包圍筆劃的最小矩形部分就可以了,其他部分就不再重繪。這也是爲什麼在筆劃對象中提供最小矩形信息的原因。

如果pHint爲空,則表明是一般的重繪,此時需要重繪整個客戶區。

現在,在OnDraw中,根據無效矩形繪製圖形,而不是重繪全部筆劃,見下

void CDrawView::OnDraw(CDC* pDC)

{

CDrawDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

 

CRect rectClip;

CRect rectStroke;

pDC->GetClipBox(&rectClip);

pDC->LPtoDP(&rectClip);

rectClip.InflateRect(1, 1); // avoid rounding to nothing

CTypedPtrList<CObList,CStroke*>& strokeList = pDoc->m_strokeList;

POSITION pos = strokeList.GetHeadPosition();

while (pos != NULL)

{

CStroke* pStroke = strokeList.GetNext(pos);

rectStroke = pStroke->GetBoundingRect();

pDC->LPtoDP(&rectStroke);

rectStroke.InflateRect(1, 1); // avoid rounding to nothing

if (!rectStroke.IntersectRect(&rectStroke, &rectClip))

continue;

pStroke->DrawStroke(pDC);

}

}

OnDraw首先調用GetClipBox取得當前被剪裁區域(無效矩形區域),它把矩形複製導GetClipBox的參數rectClip中。然後將rectClip的座標由邏輯座標轉換爲設備座標。爲了防止該矩形太小而無法包圍其他內容,上下各放大一個單位。然後OnDraw遍歷筆劃鏈表中的所有筆劃,獲取它們的最小矩形,用IntersectRect看它是否與無效矩形相交。如果相交,說明筆劃的部分或全部落在無效矩形中,此時調用筆劃的DrawStroke方法畫出該筆劃。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章