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

VC++ ListBox 拖放重排實戰:繼承 CListBox 實現項目拖拽排序

     編輯:VC 教學

前言

標準 `CListBox` 不支援拖放重排。本文通過繼承 `CListBox` → `CMyListBox`,處理 `WM_LBUTTONDOWN/UP/MOUSEMOVE` 三大訊息,實現**直觀的項目拖拽排序**,並完整保留項目資料 (ItemData)。

類別設計


class CMyListBox : public CListBox {
    BOOL m_bLBtnDown;     // 左鍵按下標誌
    BOOL m_bMouseMove;    // 是否發生移動
    int  m_nOldPos;       // 原始索引
    int  m_nNewPos;       // 目標索引
    CString m_strDrag;    // 拖曳項目文字
    DWORD m_dwItemData;   // 拖曳項目關聯資料

protected:
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    DECLARE_MESSAGE_MAP()
};

訊息處理核心邏輯

1. OnLButtonDown:記錄起始狀態


void CMyListBox::OnLButtonDown(UINT nFlags, CPoint point) {
    CListBox::OnLButtonDown(nFlags, point);
    if (GetCurSel() != LB_ERR) { // 必須有選中項
        m_bLBtnDown = TRUE;
    }
}

2. OnMouseMove:啟動拖曳、記錄資料、變更游標


void CMyListBox::OnMouseMove(UINT nFlags, CPoint point) {
    CListBox::OnMouseMove(nFlags, point);
    if (m_bLBtnDown) {
        m_bMouseMove = TRUE;
        m_nOldPos = GetCurSel();
        GetText(m_nOldPos, m_strDrag);
        try {
            m_dwItemData = GetItemData(m_nOldPos);
        } catch (...) { AfxMessageBox(_T("GetItemData 失敗")); }

        m_bLBtnDown = FALSE; // 只記錄一次
        SetCursor(AfxGetApp()->LoadCursor(IDC_CURSOR1)); // 自定義拖曳游標
    }
}

3. OnLButtonUp:完成重排


void CMyListBox::OnLButtonUp(UINT nFlags, CPoint point) {
    CListBox::OnLButtonUp(nFlags, point);
    m_bLBtnDown = FALSE;

    if (m_bMouseMove) {
        m_bMouseMove = FALSE;

        // 確認滑鼠仍在控制項內
        POINT pt; GetCursorPos(&pt);
        CRect rc; GetWindowRect(&rc);
        if (!rc.PtInRect(pt)) return;

        m_nNewPos = GetCurSel(); // 目標位置 (滑鼠釋放處的選中項)

        if (m_nNewPos < m_nOldPos) { // 向上移動
            InsertString(m_nNewPos, m_strDrag);
            DeleteString(m_nOldPos + 1); // 插入後原索引後移 1
            SetCurSel(m_nNewPos);
            SetItemData(m_nNewPos, m_dwItemData);
        } else if (m_nNewPos > m_nOldPos) { // 向下移動
            InsertString(m_nNewPos + 1, m_strDrag);
            DeleteString(m_nOldPos); // 原索引未變
            SetCurSel(m_nNewPos);
            SetItemData(m_nNewPos, m_dwItemData);
        }
        // 相等則無操作
    }
}

使用方法

  1. 對話框編輯器放入 ListBox,ID 為 `IDC_MYLIST`
  2. ClassWizard 成員變數:`CMyListBox m_wndList` (Control 類型)
  3. 初始化填入資料:`m_wndList.AddString(_T("Item 1")); m_wndList.SetItemData(0, 1001);`
  4. 運行:滑鼠左鍵拖曳任意項目,釋放即重排,**ItemData 隨文字自動遷移**

避坑指南

問題原因對策
拖曳後順序錯亂InsertString 後索引偏移計算錯誤向上移動:`DeleteString(old+1)`;向下移動:`DeleteString(old)`
ItemData 丟失未在新位置呼叫 `SetItemData`重排後務必 `SetItemData(newPos, savedData)`
游標不變/閃爍未設定拖曳游標 / `SetCursor` 呼叫時機錯`OnMouseMove` 中 `SetCursor`,資源包含自定義 `.cur`
釋放滑鼠時不在控制項內仍重排未檢查 `PtInRect``GetCursorPos` + `GetWindowRect` + `PtInRect` 守衛
多選模式下行為異常邏輯假設單選若需多選拖曳,需改用 `CListCtrl` (報表視圖) 實現

進階:支援鍵盤輔助與視覺反饋

  • 鍵盤:`Ctrl+Up/Down` 移動選中項 (覆寫 `OnKeyDown`)
  • 視覺:`OnMouseMove` 中 `DrawInsert` (自繪插入線) 或 `RedrawWindow` 高亮目標項
  • 資料模型分離:將字串/ItemData 封裝為 `struct Item { CString text; DWORD data; }`,內部用 `std::vector` 管理,`CMyListBox` 僅負責視圖同步 → **MVC 架構更清晰**

結語

**繼承 + 三個訊息處理器 + 索引偏移小心機** = 完整拖放重排。這段代碼已在多個 MFC 專案服役十年,穩定、輕量、零依賴。下次 UI 需求「用戶想調整列表順序」,直接丟上 `CMyListBox.h/.cpp`,五分鐘集成,用戶體驗瞬間專業化。

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