架構總覽
- **單文檔 MFC**,View 繼承 `CFormView`
- **DAO 連線 Access**,`CShengSet` 繼承 `CDaoRecordset`
- **分割視窗**:左側 `CPppView` (TreeCtrl 省份清單),右側 `CShengView` (記錄明細)
- **即時同步**:`SetTimer` 每 10ms 掃描 Tree 選取項,自動 `Requery` 右側記錄集
關鍵實作步驟
1. 標頭檔引入 DAO 支援
// Stdafx.h
#include <afxdao.h> // 關鍵:DAO 類別定義
2. Document 與 View 持有 Recordset 指標
// PppDoc.h
#include "ShengSet.h"
public: CShengSet m_ShengSet;
// PppView.h
#include "ShengSet.h"
public: CShengSet* m_ShengSet;
3. OnInitialUpdate 建立 Tree 根節點與省份節點
void CPppView::OnInitialUpdate() {
CFormView::OnInitialUpdate();
CPppDoc* pDoc = (CPppDoc*)GetDocument();
m_ShengSet = &pDoc->m_ShengSet;
TV_INSERTSTRUCT tv; tv.hParent = NULL;
tv.item.mask = TVIF_TEXT; tv.item.pszText = _T("全國");
HTREEITEM hDad = m_treectrl.InsertItem(&tv); // 根節點
if (m_ShengSet->IsOpen()) m_ShengSet->Close();
m_ShengSet->Open(); m_ShengSet->MoveFirst();
CString c1; char buff[80];
while (!m_ShengSet->IsEOF()) {
c1 = m_ShengSet->m_column1; // 省份名稱欄位
wsprintf(buff, _T("%s"), c1);
tv.item.pszText = buff; tv.hParent = hDad;
m_treectrl.InsertItem(&tv);
m_ShengSet->MoveNext();
}
}
4. MainFrame 分割視窗建立雙 View
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext) {
if (!m_wndSpMain.CreateStatic(this, 1, 2)) return FALSE;
if (!m_wndSpMain.CreateView(0, 0, RUNTIME_CLASS(CPppView), CSize(180,200), pContext)) return FALSE;
if (!m_wndSpMain.CreateView(0, 1, RUNTIME_CLASS(CShengView), CSize(100,100), pContext)) return FALSE;
return TRUE;
}
5. Tree 選取變更通知 Document
void CIdView::OnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult) {
NM_TREEVIEW* pNM = (NM_TREEVIEW*)pNMHDR;
CTreeCtrl* pTree = (CTreeCtrl*)GetDlgItem(IDC_TREE1);
HTREEITEM hSel = pNM->itemNew.hItem;
CPppDoc* pDoc = (CPppDoc*)GetDocument();
if (hSel) {
HTREEITEM hParent = pTree->GetParentItem(hSel);
pDoc->Name = pTree->GetItemText(hSel); // 寫入 Document 共享變數
}
*pResult = 0;
}
6. 右側 View 定時器輪詢 Document.Name 並 Requery
void CShengView::OnInitialUpdate() {
CRecordView::OnInitialUpdate();
SetTimer(1, 10, NULL); // 10ms 間隔
}
void CShengView::OnTimer(UINT nIDEvent) {
CPppDoc* pDoc = (CPppDoc*)GetDocument();
if (m_pSet->IsOpen()) {
char filter[60];
wsprintf(filter, _T("[省].省名稱='%s'"), pDoc->Name);
m_pSet->m_strFilter = filter;
m_pSet->Requery();
UpdateData(FALSE); // DDX 更新控制項
}
}
優缺點分析
| 優點 | 缺點 |
| 架構清晰:Tree 做導覽,Form 做明細 | 10ms Timer 輪詢浪費 CPU,建議改用 `TVN_SELCHANGED` 直接通知 |
| DAO 簡單易用,適合 Access | DAO 不支援新版 Access,長期維護風險 |
| Document 做資料中介,解耦 View | 單表設計,無主從關聯 (如縣市/鄉鎮) |
| 分割視窗標準 MFC 寫法 | 硬編碼欄位名 `m_column1`,缺乏泛用性 |
改進建議
- 移除 Timer,在 `OnSelchangedTree1` 中直接呼叫右側 View 的 `RefreshData(pDoc->Name)`
- 改用 ADO + `OpenSchema` 動態取得欄位,支援任意資料表
- 擴充 Tree 為多層 (省->縣->鄉),對應多層 `m_strFilter`
- 右側改用 `CListCtrl` + 動態欄位,而非固定 DDX 控制項