首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC.NET

Visual C++.NET 程式設計講座第三講:文件資料讀取與顯示技巧

     編輯:VC.NET

第 2 講:文件資料的讀取與顯示

本講介紹 MFC 文件/視圖架構中各類別的關聯機制、文件讀取流程與序列化操作,並詳細比較三種文件資料顯示方法:CEditView 機制、編輯控件 (CEdit)、以及直接使用 CDC 繪圖類別輸出文字,分析各自優缺點與適用情境。

程式中各使用者類別的關聯

使用 MFC 應用程式精靈建立單一檔案專案 TextViewer 後,精靈自動產生下列核心類別:

  • CTextViewerApp — 應用程式類別,程式進入點
  • CMainFrame — 主框架視窗類別,管理標題列、選單、工具列、狀態列
  • CTextViewerDoc — 文件類別,負責資料讀取/儲存
  • CTextViewerView — 視圖類別,負責資料顯示與使用者輸入回應
  • CAboutDlg — 關於對話框類別

類別宣告置於 .h 標頭檔,實作碼置於 .cpp 檔。三大核心類別 (框架、文件、視圖) 之間的關聯是在 CTextViewerApp::InitInstance 中由 CSingleDocTemplate 建立:

CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
    IDR_MAINFRAME,
    RUNTIME_CLASS(CTextViewerDoc),
    RUNTIME_CLASS(CMainFrame),
    RUNTIME_CLASS(CTextViewerView));
AddDocTemplate(pDocTemplate);

InitInstance 代碼片段
圖 1 InitInstance 中的文件模板建立代碼

關鍵機制:

  • CSingleDocTemplate 協調文件、框架視窗、視圖三者關係。
  • RUNTIME_CLASS 宏取得 CRuntimeClass 結構指標,支援執行期類型資訊 (RTTI)。
  • AddDocTemplate 將模板指標加入內部清單。

文件的讀取流程

執行程式,選擇 [檔案] → [開啟舊檔],會自動彈出通用「開啟舊檔」對話框。此功能源於精靈自動將 ID_FILE_OPEN 對應至 CWinApp::OnFileOpen,透過訊息對應實現:


BEGIN_MESSAGE_MAP(CTextViewerApp, CWinApp)
    ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
END_MESSAGE_MAP()

應用程式類別訊息對應
圖 2 應用程式類別的訊息對應

訊息對應機制:Windows 所有動作 (系統或使用者) 產生事件/訊息。ON_COMMAND 專門對應選單等命令訊息,參數為命令 ID 與處理函數。

使用者在對話框指定檔案後,流程如下:

  1. 呼叫文件物件的 CDocument::OnOpenDocument 虛函數開啟檔案。
  2. 呼叫 DeleteContents 清除文件物件現有內容。
  3. 建立 CArchive (歸檔類別) 物件進行資料讀取。
  4. 自動呼叫 Serialize 函數執行序列化讀取。
  5. 呼叫視圖物件的 CView::OnInitialUpdate 更新顯示。

關鍵類別說明:

  • CArchive:提供 << (寫入) 與 >> (讀取) 運算子,支援 CObject、CString 等類型,另提供 ReadString/WriteString 逐行讀寫。
  • Serialize:雙向函數,依 CArchive 內部旗標決定讀取或儲存。

Serialize 函數代碼
圖 3 Serialize 函數代碼範例

文件資料的讀取操作實作

我們在文件類別的 Serialize 中加入讀取程式碼,將檔案文字內容逐行讀入 CStringArray 集合類別物件。

  1. 於 CTextViewerDoc 加入成員變數:CStringArray m_strContent。
    CStringArray 封裝 CString 陣列操作 (Add、RemoveAll、GetAt 等),類似集合類別尚有 CByteArray、CUIntArray、CWordArray、CDWordArray。

加入成員變數
圖 4 加入 CStringArray 成員變數

  1. 修改 CTextViewerDoc::Serialize 加入讀取代碼:

if (ar.IsStoring())
{
    // 儲存邏輯 (略)
}
else
{
    m_strContent.RemoveAll();
    CString strLine;
    while (ar.ReadString(strLine))
    {
        m_strContent.Add(strLine);
    }
}

讀取代碼
圖 5 Serialize 中加入讀取檔案文字內容代碼

ReadString 成功讀出一行回傳 TRUE,檔案結尾回傳 FALSE,配合 while 迴圈讀完整個檔案。

  1. 覆寫 DeleteContents 確保開啟新檔案時清除舊資料:

加入 DeleteContents 覆寫
圖 6 加入 DeleteContents 函數覆寫


void CTextViewerDoc::DeleteContents()
{
    m_strContent.RemoveAll();
    CDocument::DeleteContents();
}

DeleteContents 代碼
圖 7 DeleteContents 中加入清除代碼

備註:也可將 m_strContent.RemoveAll() 直接放在 Serialize 的 while 迴圈前,省略覆寫 DeleteContents 步驟。

文件資料顯示的三種方法與技巧

方法一:使用 CEditView 機制

CEditView 內建簡易文字編輯功能 (列印、尋找取代、剪貼簿操作) 且支援序列化。

  1. 修改 InitInstance 中文件模板,將視圖類別從 CTextViewerView 改為 CEditView:

pDocTemplate = new CSingleDocTemplate(
    IDR_MAINFRAME,
    RUNTIME_CLASS(CTextViewerDoc),
    RUNTIME_CLASS(CMainFrame),
    RUNTIME_CLASS(CEditView)); // 變更此處

變更視圖類別
圖 8 文件模板中變更關聯的視圖類別

  1. 於 CTextViewerDoc::Serialize 中呼叫 CEditView 序列化:

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

序列化代碼
圖 9 加入 CEditView 序列化代碼

評析:簡單有效,具編輯功能。缺點:原視圖類別 CTextViewerView 失去作用,難以進行深層視圖控制。VC++.NET 中可能出現亂碼 (已知 Bug)。

方法二:使用編輯控件 (CEdit)

在視圖客戶區建立與視圖同大小的編輯控件,將文件文字內容轉送至控件。

  1. 於 CTextViewerView 加入成員指標 CEdit* m_ctrlEdit。
  2. 覆寫 OnInitialUpdate 建立控件:

void CTextViewerView::OnInitialUpdate()
{
    CView::OnInitialUpdate();
    if (m_ctrlEdit) delete m_ctrlEdit;
    m_ctrlEdit = new CEdit;
    m_ctrlEdit->Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | 
                       ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL,
                       CRect(0,0,0,0), this, 1);
    // 將文件內容填入編輯控件
    CTextViewerDoc* pDoc = GetDocument();
    CString str;
    for (int i = 0; i < pDoc->m_strContent.GetSize(); i++)
    {
        str += pDoc->m_strContent.GetAt(i) + _T("\r\n");
    }
    m_ctrlEdit->SetWindowText(str);
}

OnInitialUpdate 代碼
圖 10 OnInitialUpdate 中建立編輯控件代碼

  1. 解構函數釋放記憶體:if (m_ctrlEdit) delete m_ctrlEdit;
  2. 對應 WM_SIZE 訊息,視窗大小改變時調整控件尺寸:

void CTextViewerView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);
    if (m_ctrlEdit)
        m_ctrlEdit->MoveWindow(0, 0, cx, cy);
}

WM_SIZE 對應
圖 11 加入 WM_SIZE 訊息對應

OnSize 代碼
圖 12 OnSize 中調整控件大小代碼

編輯控件顯示結果
圖 13 使用編輯控件顯示文件資料結果

評析:簡單且具編輯功能。缺點:格式單調 (行距、字距無法調整),視圖繪圖功能無法發揮。

方法三:直接控制文字輸出 (CDC 繪圖類別)

CDC 為裝置環境類別,提供四個文字輸出函數:

  • TextOut — 簡單輸出
  • ExtTextOut — 支援字符間距、背景色、矩形裁剪
  • TabbedTextOut — 支援 Tab 定位,適合對齊顯示
  • DrawText — 矩形區域多行輸出,效率佳

本例使用 TabbedTextOut,原型:


CSize TabbedTextOut(int x, int y, const CString& str,
    int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin);
  1. 重新建立單一檔案專案 Viewer,精靈 [產生的類別] 頁面將 CViewerView 基底類別改為 CScrollView (支援自動捲動/縮放)。
  2. 於 CViewerDoc 加入 CStringArray m_strContent 及讀取代碼 (同方法二)。
  3. 於 CViewerView 加入成員函數 void DispContent(CDC* pDC)。

加入 DispContent 函數
圖 14 加入 DispContent 成員函數

  1. DispContent 實作代碼:

void CViewerView::DispContent(CDC* pDC)
{
    CViewerDoc* pDoc = GetDocument();
    int y = 0;
    int nTabStops[] = { 200, 400, 600 }; // Tab 停靠位置
    for (int i = 0; i < pDoc->m_strContent.GetSize(); i++)
    {
        pDC->TabbedTextOut(0, y, pDoc->m_strContent.GetAt(i), 
                           3, nTabStops, 0);
        y += 20; // 行距
    }
    // 設定捲動範圍
    SetScrollSizes(MM_TEXT, CSize(800, y));
}

DispContent 代碼
圖 15 DispContent 函數代碼

SetScrollSizes 設定座標映射模式 (MM_TEXT:像素單位,X 向右、Y 向下遞增) 與邏輯捲動視窗大小。虛擬視窗超過顯示區域時自動出現捲軸。

  1. 於 OnDraw 呼叫 DispContent:

void CViewerView::OnDraw(CDC* pDC)
{
    DispContent(pDC);
}

OnDraw 呼叫 DispContent
圖 16 OnDraw 中呼叫 DispContent

文字繪製結果
圖 17 文字繪製結果 (含 Tab 對齊)

評析:可精確控制行距、字距、Tab 對齊,善用 CDC 繪圖功能,列印/預覽自動生效。缺點:顯示速度較慢,程式碼較複雜。

結語

本講透過 MFC 文件讀取流程探討序列化操作,並比較三種顯示方法:CEditView 最簡單但擴充性低;編輯控件平衡簡易與功能;CDC 直接繪圖最靈活強大但效能較低。下一講將透過選單與工具列改變文字字型與顏色,探討三者與狀態列的互動關係。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved