程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC.net >> Visual C++.NET編程講座之三

Visual C++.NET編程講座之三

編輯:VC.net
第二講 文檔數據的讀取和顯示

  摘要

  本講先來介紹程序各個類的關聯機制,然後討論文檔的讀過程及讀操作,最後討論文檔數據的顯示方法和技巧。

  程序中各用戶類的關聯

  在上一講中,我們使用MFC應用程序向導創建一個單文檔項目TextViewer。現在,我們打開該項目。可以看出,向導為TextViewer項目創建了以下幾個類:

  應用程序類CTextViewerApp,應用程序必須的運行入口,在上一講已經討論過。

  主框架窗口類CMainFrame,用來負責窗口的標題欄、菜單、工具欄及狀態欄等界面元素的操作。

  文檔類CTextViewerDoc,用來負責文檔數據的讀取和保存

  視圖類CTextViewerView類,用來顯示文檔顯示,並可響應各種類型的輸入(例如鍵盤輸入)以及實現打印和打印預覽等。

  還有一個對話框類CAboutDlg,用來顯示該應用程序的版本信息,是一個"關於"對話框。

  需要說明的是,Visual C++ .NET將各個類的聲明保存在頭文件中,即以.h為擴展名,而將類的實現代碼保存在以.cpp為擴展名的實現文件中。

  那麼,在MFC中上述的主框架窗口類、文檔類和視圖類的關系是怎樣呢?我們來看一下。

  將解決方案管理器切換到"類視圖",展開CTextViewerApp類的所有節點,雙擊"InitInstance( void )",打開該函數代碼。由於Visual C++ .NET的代碼注釋是中文的,因此我們這裡僅給出如圖1所示的代碼。


圖1 InitInstance函數中的部分代碼

  代碼中,CSingleDocTemplate是從CDocTemplate派生的單文檔類,它協調了文檔窗口、文檔和視圖的關系,並把三者聯系起來。該類的構造函數需要指定四個參數,分別為表示菜單和加速鍵等的資源ID號以及三個由宏RUNTIME_CLASS指定的CRuntimeClass結構對象指針,它們分別是程序的文檔類、框架窗口類和視圖類的結構指針。

  CRuntimeClass結構反映一個運行時類的信息,通常用宏RUNTIME_CLASS來獲取一個類的CRuntimeClass結構指針。Visual C++借助CRuntimeClass類結構能在應用程序運行過程中獲得該類對象及其基類的相關信息,從而可以實現運行時類型檢查(Run Time Type Inspection,RTTI)。

  AddDocTemplate用來將指定的單文檔模板或多文檔模板指針添加到程序所包含內部的文檔模板指針列表中。

  文檔的讀過程

  在向導創建的應用程序中,程序的默認菜單有"文件"、"編輯"、"視圖"和"幫助"。當運行程序後,打開"文件"菜單中的"打開"命令時,應用程序會自動打開相應的"打開"文件通用對話框。之所以有這功能,是因為向導創建的應用程序框架中,自動將"打開"菜單命令與CWinApp的OnFileOpen成員函數相關聯。這種關聯是通過"消息映射"來實現的,在CTextViewerApp類的實現文件TextViewer.cpp前面有這樣的代碼,如圖2所示。


圖2 應用程序類的消息映射

  "消息映射"是MFC中的一個亮點。在Windows操作環境中,無論是系統產生的動作或是用戶運行應用程序產生的動作,都稱為事件(Events)產生的消息(Message)。例如,用戶選擇菜單時所產生的消息稱為"命令"消息,而鼠標改變窗口狀態時所產生的消息是"窗口"消息。只要是消息,都可以通過MFC的"消息機制"來映射。映射的目的是將消息和某個函數相關聯,這樣一旦該消息產生就會執行相關聯的函數。

  圖2代碼中,BEGIN_MESSAGE_MAP和END_MESSAGE_MAP是MFC開始和結束消息映射宏,ON_COMMAND是專門用來映射像菜單的一些命令消息宏,它有兩個參數,第一個參數用來指定命令標識,MFC中每個菜單項都有一個標識值,"打開"菜單的標識ID為ID_FILE_OPEN,第二個參數是用來指定關聯的函數。(消息映射以後還會講到)

  當用戶在通用"打開"文件對話框中指定一個文件後,應用程序將調用文檔對象的 CDocument::OnOpenDocument虛成員函數。該函數將打開文件,並調用DeleteContents清除文檔對象的內容,然後創建一個CArchive(歸檔類)對象用於數據的讀取,接著又自動調用Serialize函數。之後便調用視圖對象的CView::OnInitialUpdate虛成員函數。

  在這個過程中,我們可能有很多地方不理解。但我們仔細想一想就會明白許多。假如視圖中已有文檔數據顯示,為了能快速顯示和修改這些數據,顯然這些數據要存儲在專門的內存空間中,CArchive類對象就起到了這個作用。當打開另一個文檔時,以前在內存中存儲的數據要清除,這就是DeleteContents作用,而且還要使視圖能及時更新顯示,所以要調用OnInitialUpdate函數。

  上述的Serialize函數是一個很特別的函數,它既可以從中讀取文檔數據,也可以保存文檔數據,稱為"序列化"函數。它被添加用戶的文檔類中,用來根據CArchive內部的一個標志來決定文檔數據的流向(讀或寫),如圖3所示。


圖3 Serialize函數代碼

  文檔數據的讀操作

  對於上述過程,我們所做的僅僅是在文檔類的Serialize函數中添加文檔數據讀取(加載)和存儲的代碼。需要說明的是,Serialize函數的參數ar是一個CArchive類的引用對象。CArchive類提供了"<<"和">>"運算符,分別可以向文檔對象寫入數據或從文檔對象中讀取數據。它們的含義與C++中的"<<"和">>"運算符相同,只不過CArchive支持更多的數據類型,如:CObject、CString等。除此之外CArchive類還提供ReadString和WriteString成員函數來讀寫文檔中的一行文本。下面的過程用來將文檔的文本內容讀出並保存到一個字符串集合類對象中。

  (1) 將解決方案管理器窗口切換到"類視圖",展開所有的類,右擊類名"CTextViewerDoc",從彈出的快捷菜單中選擇"添加"->"添加變量",彈出"添加成員變量向導"對話框,在"變量類型"框中輸入CStringArray,在"變量名"框中輸入m_strContent,如圖4所示。單擊"完成"按鈕。

  CStringArray是"字符串集合類",它封裝了CString數組對象的全部操作。類似的還有對BYTE、UINT、WORD和DWORD等類型的數組操作的集合類CByteArray、CUIntArray、CWordArray和CDWordArray。這些集合類都有相似的操作,如Add(添加)、RemoveAll(刪除全部元素)、GetAt(獲取指定數組下標的元素)等。


圖4 添加成員變量

  (2) 在CTextViewerDoc::Serialize函數中添加讀取文檔文本內容代碼,如圖5所示。


圖5 添加的讀取文檔文本內容代碼

  代碼中,ReadString是讀取打開的文檔的一行文本,當成功讀出時,函數返回TRUE,當文本達到文檔結尾時,函數返回FALSE。這樣,通過while循環可以將文檔的文本內容全部讀取並保存到m_strContent中。

  (3) 由於另一個文檔打開時,需要將m_strContent中的內容清除,所以我們需要跟蹤DeleteContents函數。在CTextViewerDoc類的屬性窗口,單擊"重寫"按鈕,在列表框中找到DeleteContents函數項,單擊右邊的空格後再單擊右側的下拉按鈕,出現一個下拉列表,如圖6所示。


圖6 添加DeleteContents函數的重寫

  (4) 單擊"<添加>DeleteContents",該函數的重寫就添加好了。這樣,框架在自動執行該函數時就會將自己添加在這個函數中的代碼也會被執行。

  (5) 在DeleteContents中添加如圖7所示的加框代碼。


圖7 在DeleteContents函數中添加的代碼

  這就是文檔文本內容的整個讀取過程。需要說明的是,也可以將"m_strContent.RemoveAll();"語句直接添加在圖5中的while循環語句之前,從而可以省略(5)和(6)的步驟。

  文檔數據的顯示方法和技巧

  用戶的視圖類是負責顯示文檔數據的,目前常用的顯示方法有三個:一是使用CEditView機制來顯示,二是在視圖的客戶區中使用編輯控件,三是直接調用CDC類的文本輸出函數繪制所有的文本內容。下面就來介紹。

  1. 使用CEditView機制

  在MFC文檔應用程序中,其內部有一個視圖指針列表變量m_viewList,由於CEditView支持文檔的序列化,因此我們可以使用下列語句來進行:

((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);

  具體過程如下:

  (1) 在InitInstance函數中,改寫框架窗口類、文檔類和視圖類的關聯,如圖8所示的加框部分。


圖8 在文檔模板中改變關聯的視圖類

  CEditView視圖類提供了簡單的文本編輯功能,如打印、查找並替換、剪貼板的剪切、復制和粘貼等。

  (2) 在CTextViewerDoc::Serialize函數中添加序列化代碼,如圖9所示的加框部分。


圖9 添加的序列化代碼

  (3) 運行程序,打開一個文檔,看看是不是可以顯示出文檔的內容?(顯示的內容可能會出現亂碼,這是Visual C++ .NET中的一個BUG)

  評述:這種方法簡單有效,並且能夠實現文本的編輯功能,缺點是程序中的CTextViewerView類變得沒有用了,並且很難進行更深層次的視圖控制。

  2. 使用編輯控件

  "編輯控件"是一個可以讓用戶從鍵盤輸入和編輯文本的控件,通過它可以輸入各種文本、數字或者口令,也可使用它來編輯和修改簡單的文本內容。MFC類CEdit封裝了編輯控件的全部操作。

  使用編輯控件實現文檔數據的顯示的思路是,先在視圖中創建一個與視圖客戶區大小相同的編輯控件,然後把文檔的文本內容轉送到編輯控件中。這裡的視圖客戶區是指除了窗口標題欄、菜單欄、工具欄、狀態欄以及邊框之外的部分。簡單地說,就是默認的背景色為白色的區域。

  實現的步驟如下:

  (1) 為CTextViewerView類添加成員變量CEdit* m_ctrlEdit。這是一個指針變量,用"添加成員變量向導"添加時,要在"變量類型"框加輸入"CEdit*"(雙引號不輸入,注意其中的星號),而在"變量名"中輸入m_ctrlEdit。

  (2) 為CTextViewerView類添加OnInitialUpdate函數的重寫,並添加如圖10所示的代碼(加框部分)。


圖10 在OnInitialUpdate中添加的代碼

  new和delete分別用來為類對象分配和釋放內存空間。為了避免m_ctrlEdit內存空間重復分配,我們在new操作前,要先將m_ctrlEdit內存空間釋放。

  當框架將文檔與視圖關聯,且視圖將要顯示時調用OnInitialUpdate函數,因此我們將視圖的一些初始化代碼添加到這裡。

  (3) 在CTextViewerView析構函數中添加m_ctrlEdit內存空間釋放的語句: if ( m_ctrlEdit ) delete m_ctrlEdit;

  (4) 由於視圖大小改變後,編輯控件的大小也應隨之改變,因此我們需要跟蹤窗口的WM_SIZE消息,只要窗口大小發生改變後,都會發送這個消息。單擊CTextViewerView類屬性窗口中的"消息"按鈕,添加WM_SIZE消息映射。如圖11所示。


圖11 添加WM_SIZE的消息映射

  (5) 在消息映射函數CTextViewerView::OnSize中添加如圖12所示的代碼。


圖12 在OnSize中添加的代碼

  (6) 運行程序,打開當前文件夾下的ReadMe.txt文件,結果如圖13所示。


圖13 使用編輯控件的文檔數據顯示結果

  評述:這種方法雖然也比較簡單,且具有文本的編輯功能,但文本顯示的格式還很單調,例如它的行間距和字間距無法調整,更主要的是視圖的繪制功能無法起作用。

  3. 直接控制文本的輸出

  圖形和文本的繪制需要用到MFC的CDC類,它是一個設備環境類。所謂設備環境,就好比我們寫字用的紙那樣,顯示時指的是屏幕,打印時指的是打印機。實際上,MFC的CDC類還為一些特殊的設備環境提供相應的派生類。例如,CClientDC是一個窗口客戶區的設備環境類。

  CDC為我們提供了四個輸出文本的函數:TextOut、ExtTextOut、TabbedTextOut和DrawText,分別用於不同的場合。如果想要繪制的文本需要支持Tab符,那麼采用TabbedTextOut函數,可以使繪制出來的文本效果更佳;如果要在一個矩形區域內繪制多行文本,那麼采用DrawText函數,會更富於效率;如果文本和圖形結合緊密,字符間隔不等,並要求有背景顏色或矩形裁剪特性,那麼ExtTextOut函數就將是最好的選擇。如果沒有什麼特殊要求,那使用TextOut函數就顯得簡練了。在本例中,我們使用TabbedTextOut函數來繪制文本,它的函數原型如下:

CSize TabbedTextOut( int x, int y, const CString& str,
int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin );

  該函數用當前字體在指定位置 (x,y) 處顯示一個由str指定的文本,且根據指定的制表停止位設置相應字符位置。函數成功時返回文本的大小。參數中,nTabPositions表示lpnTabStopPositions數組的大小,lpnTabStopPositions表示多個遞增的停止位(邏輯坐標)的數組,nTabOrigin表示制表停止位x方向的起始點(邏輯坐標)。

  具體步驟如下:

  (1) 重新創建一個新的單文檔應用程序項目Viewer,在向導的"生成的類"頁面中將CViewerView的基類CView改成CScrollView。CScrollView類是一個用來提供自動滾動或縮放功能的視圖結構。

  (2) 按文檔數據的讀操作,在CViewerDoc類中,添加保存文檔數據的CStringArray類對象m_strContent,並添加相應的操作代碼。

  (3) 文本內容的輸出代碼一般是添加在視圖類的OnDraw函數中,但為了調用的方便,我們這裡在視圖類中添加一個成員函數DispContent。

  (4) 將解決方案管理器窗口切換到"類視圖",展開所有的類,右擊類名"CViewerView",從彈出的快捷菜單中選擇"添加"->"添加函數",彈出"添加成員函數向導"對話框,在"返回類型"框中輸入void,在"函數名"框中輸入DispContent,在"參數類型"框中輸入CDC*,在"參數名"框中輸入pDC,然後單擊"添加"按鈕,結果如圖14所示。單擊"完成"按鈕。


圖14 添加成員函數DispContent

  (5) 在DispContent函數中添加如圖15所示的代碼。


圖15 DispContent函數代碼

  SetScrollSizes()是CScrollView類的成員函數,用來設置相應的坐標映射模式和邏輯滾動窗口的大小。所謂"邏輯滾動窗口",是在指定的坐標映射模式下的一個"虛擬窗口"。當虛擬窗口超過視圖客戶區(可稱為"顯示窗口")的大小時,視圖客戶區中就會自動出現滾動條,供用戶滾動浏覽。若虛擬窗口比顯示窗口小,則視圖客戶區中不會出現滾動條。MM_TEXT是"文本"坐標映射模式。在該模式下,x坐標從左向右遞增,y坐標自上而下遞增,坐標以像素為單位,這也是MFC的默認坐標模式。

  (6) 在CViewerView::OnDraw函數添加DispContent調用代碼,如圖16所示的加框部分。OnDraw()是視圖類中非常有用的一個函數,當應用程序中的窗口狀態或大小發生改變時,系統均會調用此函數重新繪制視圖窗口的客戶區。因此,我們應該將一些圖形繪制添加到此函數中。


圖16 DispContent的調用

  (7) 運行程序,打開當前文件夾下的ReadMe.txt文件,結果如圖17所示。


圖17 文本繪制的結果

  評述:這種方法雖然較復雜一點,但是卻能控制每行文本的行距,並可使用CDC類的其他文本和文字處理函數,使得文本表現更具豐富力。另外,由於繪制的代碼過程是添加在OnDraw中,因此該方法能使默認的打印和打印預覽功能有效。缺點是,顯示的速度表現欠佳。

  結束語

  本講中,通過MFC的文檔讀過程討論了文檔的讀操作及不同的文檔數據顯示方法和技巧,在下一講中,我們將通過菜單和工具欄來改變文本顯示的字體和顏色,並討論它們與狀態欄這三者之間的相互關系。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved