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

打造類 VB 分類屬性表控制項:CPropertyList 完整實作指南

     編輯:VC++

前言

Visual Basic 的屬性視窗以「分類檢視」著稱:左側分類圖示可展開/摺疊,右側屬性名不可編輯,值欄位依型別呈現編輯框、下拉框、按鈕等。本文介紹一個從 CListBox 衍生的 CPropertyList 控制項,單一列表列模擬三欄佈局,完美複刻 VB 風格。

核心架構設計

  • 基類: CListBox (Owner-draw variable height 模式)
  • 列佈局: 第 0 欄:展開/摺疊圖示 (16×16);第 1 欄:屬性名稱 (唯讀);第 2 欄:動態內嵌編輯控制項 (CEdit/CComboBox/CButton)。
  • 資料模型: CPropertyItem 封裝分類/屬性節點,含型別枚舉 PIT_EDIT、PIT_COMBO、PIT_BUTTON 等。

專案整合步驟

  1. 複製 PropertyList.h/.cpp、Expand.bmp/UnExpand.bmp 至專案目錄。
  2. 資源檢視 → Import 兩個 BMP,設定 ID 為 IDB_PROPERTY_EXPAND、IDB_PROPERTY_UNEXPAND。
  3. 對話方塊編輯器放入 ListBox,屬性設定:Owner Draw Variable、Has Strings (關閉)、Border (可選)。
  4. 類別向導新增控制項變數 m_cList,手動修改標頭檔:CPropertyList m_cList;
  5. 對話方塊類別標頭檔加入:
#include "PropertyList.h"

CPropertyList* m_pList;          // 控制項指標
CPropertyItem* m_pCategory;      // 分類節點
CPropertyItem* m_pName;          // 屬性節點

初始化與建立屬性樹

BOOL CMyDialog::OnInitDialog()
{
    CDialog::OnInitDialog();

    m_pList = &m_cList;

    // 建立分類節點:名稱、初始值、型別、選項字串、可見、可展開、圖示索引
    m_pCategory = new CPropertyItem(_T("公用屬性"), _T(""), PIT_CATEGORY, _T(""), TRUE, TRUE, 0);
    m_pName     = new CPropertyItem(_T("名稱"),      _T(""), PIT_EDIT,    _T(""), FALSE, FALSE, 0);

    // 將分類加入控制項
    m_pList->AddItem(m_pCategory);
    // 將屬性加入分類 (父節點指標)
    m_pList->AddItem(m_pName, m_pCategory);

    return TRUE;
}

void CMyDialog::OnDestroy()
{
    delete m_pCategory;
    delete m_pName;
    CDialog::OnDestroy();
}

執行期存取屬性值

// 寫入
m_pName->m_curValue = pGraph->GetNodeName();
m_pList->Invalidate();  // 刷新顯示

// 讀取
pGraph->SetNodeName(m_pName->m_curValue);

關鍵實作細節 (PropertyList.cpp 節錄)

1. 繪製分類列 (含展開圖示)

void CPropertyList::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    CRect rc = lpDrawItemStruct->rcItem;
    CPropertyItem* pItem = (CPropertyItem*)lpDrawItemStruct->itemData;

    // 背景
    pDC->FillSolidRect(rc, GetSysColor(COLOR_WINDOW));

    // 第 0 欄:展開/摺疊圖示 (僅分類節點)
    if (pItem->m_nType == PIT_CATEGORY)
    {
        CBitmap* pBmp = pItem->m_bExpanded ? &m_bmpUnExpand : &m_bmpExpand;
        CDC memDC; memDC.CreateCompatibleDC(pDC);
        CBitmap* pOld = memDC.SelectObject(pBmp);
        pDC->BitBlt(rc.left + 2, rc.top + 2, 16, 16, &memDC, 0, 0, SRCCOPY);
        memDC.SelectObject(pOld);
    }

    // 第 1 欄:屬性名稱
    CRect rcName(rc.left + 22, rc.top, rc.left + 150, rc.bottom);
    pDC->DrawText(pItem->m_strName, rcName, DT_LEFT | DT_VCENTER | DT_SINGLELINE);

    // 第 2 欄:由內嵌控制項自行繪製 (CreateInplaceControl)
}

2. 滑鼠點擊展開/摺疊邏輯

void CPropertyList::OnLButtonDown(UINT nFlags, CPoint point)
{
    int nIndex = GetTopIndex();
    for (int i = 0; i < GetCount(); i++, nIndex++)
    {
        CRect rc; GetItemRect(nIndex, &rc);
        if (rc.PtInRect(point))
        {
            CPropertyItem* pItem = (CPropertyItem*)GetItemData(nIndex);
            if (pItem->m_nType == PIT_CATEGORY && point.x < 22)
            {
                pItem->m_bExpanded = !pItem->m_bExpanded;
                RefreshVisibleItems();  // 重建可見列表陣列
                Invalidate();
                break;
            }
            // 其他列點擊 → 啟動內嵌編輯控制項
            EditItem(nIndex);
            break;
        }
    }
    CListBox::OnLButtonDown(nFlags, point);
}

3. 動態建立內嵌編輯控制項

void CPropertyList::EditItem(int nIndex)
{
    CPropertyItem* pItem = (CPropertyItem*)GetItemData(nIndex);
    CRect rc; GetItemRect(nIndex, &rc);
    rc.left += 152; rc.right -= 2;  // 第 2 欄區域

    switch (pItem->m_nType)
    {
    case PIT_EDIT:
        m_pEditCtrl = new CEdit;
        m_pEditCtrl->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, rc, this, IDC_INPLACE_EDIT);
        m_pEditCtrl->SetWindowText(pItem->m_curValue);
        m_pEditCtrl->SetFocus();
        break;
    case PIT_COMBO:
        m_pComboCtrl = new CComboBox;
        m_pComboCtrl->Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, rc, this, IDC_INPLACE_COMBO);
        // 填入選項 pItem->m_strOptions (分號分隔)
        ParseOptions(pItem->m_strOptions, m_pComboCtrl);
        m_pComboCtrl->SetCurSel(pItem->m_nCurSel);
        m_pComboCtrl->SetFocus();
        break;
    case PIT_BUTTON:
        m_pBtnCtrl = new CButton;
        m_pBtnCtrl->Create(pItem->m_curValue, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, rc, this, IDC_INPLACE_BUTTON);
        m_pBtnCtrl->SetFocus();
        break;
    }
    m_nEditingIndex = nIndex;
}

擴充建議

  • 支援巢狀分類: CPropertyItem 加入 CPropertyItem* m_pParent,RefreshVisibleItems 遞迴展開。
  • 資料驗證: 在 EN_KILLFOCUS/CBN_SELENDOK 通知中回寫 m_curValue 並觸發驗證回呼。
  • 自訂繪製: 覆寫 DrawItem 實作主題感知 (Dark Mode) 配色。
  • 鍵盤導航: 處理 WM_KEYDOWN (Up/Down/Enter/Escape) 實現無滑鼠操作。

結語

CPropertyList 以 CListBox Owner-draw 機制為基礎,巧妙模擬三欄分類屬性表,代碼量精簡且易於擴充。掌握「資料模型分離、動態內嵌控制項、可見列表重建」三大核心技巧,即可快速整合至任何 MFC 專案,為使用者提供熟悉的 VB 風格屬性編輯體驗。

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