標準 `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()
};
void CMyListBox::OnLButtonDown(UINT nFlags, CPoint point) {
CListBox::OnLButtonDown(nFlags, point);
if (GetCurSel() != LB_ERR) { // 必須有選中項
m_bLBtnDown = TRUE;
}
}
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)); // 自定義拖曳游標
}
}
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);
}
// 相等則無操作
}
}
| 問題 | 原因 | 對策 |
|---|---|---|
| 拖曳後順序錯亂 | InsertString 後索引偏移計算錯誤 | 向上移動:`DeleteString(old+1)`;向下移動:`DeleteString(old)` |
| ItemData 丟失 | 未在新位置呼叫 `SetItemData` | 重排後務必 `SetItemData(newPos, savedData)` |
| 游標不變/閃爍 | 未設定拖曳游標 / `SetCursor` 呼叫時機錯 | `OnMouseMove` 中 `SetCursor`,資源包含自定義 `.cur` |
| 釋放滑鼠時不在控制項內仍重排 | 未檢查 `PtInRect` | `GetCursorPos` + `GetWindowRect` + `PtInRect` 守衛 |
| 多選模式下行為異常 | 邏輯假設單選 | 若需多選拖曳,需改用 `CListCtrl` (報表視圖) 實現 |
**繼承 + 三個訊息處理器 + 索引偏移小心機** = 完整拖放重排。這段代碼已在多個 MFC 專案服役十年,穩定、輕量、零依賴。下次 UI 需求「用戶想調整列表順序」,直接丟上 `CMyListBox.h/.cpp`,五分鐘集成,用戶體驗瞬間專業化。