MSFlexGrid 是經典的唯讀表格控件,若需支援儲存格編輯,常見做法是「視覺假象法」:在使用者點擊儲存格時,精準疊加一個 CEdit 或 CComboBox 控件於儲存格上方,編輯完成後將內容寫回並隱藏控件。本文完整解析此技術的座標換算、控件切換、焦點管理等關鍵細節。
class CChartInfoEditDlg : public CDialog
{
// 隱藏的編輯控件,初始不可見
CEdit m_EditCtrl; // 文字編輯
CComboBox m_ComboCtrl; // 下拉選單編輯
CString m_strEditText; // 與 CEdit 關聯的 DDX 變數
MSFlexGrid m_Grid; // 表格控件
// ... 其他變數
};
MSFlexGrid 內部座標單位為 Twips (1/1440 英吋),必須轉換為螢幕像素才能正確定位疊加控件。
void CChartInfoEditDlg::OnClickGrid()
{
long lRow = m_Grid.GetRowSel();
long lCol = m_Grid.GetColSel();
// 1. 邊界檢查:忽略標題列 (Row 0) 與超出資料範圍的點擊
if (lRow == 0 || lRow > m_MaxDataRow) return;
// 2. 取得 Grid 視窗矩形 (螢幕座標) -> 轉為用戶區座標
CRect rcGrid;
m_Grid.GetWindowRect(&rcGrid);
ScreenToClient(&rcGrid);
// 3. 計算 Twips -> Pixel 轉換比例
CDC* pDC = GetDC();
int nTwipsPerPixelX = 1440 / pDC->GetDeviceCaps(LOGPIXELSX);
int nTwipsPerPixelY = 1440 / pDC->GetDeviceCaps(LOGPIXELSY);
ReleaseDC(pDC);
// 4. 計算目標儲存格左上角與寬高 (像素)
long x = m_Grid.GetColPos(lCol) / nTwipsPerPixelX;
long y = m_Grid.GetRowPos(lRow) / nTwipsPerPixelY;
long w = m_Grid.GetColWidth(lCol) / nTwipsPerPixelX + 1; // +1 修正邊界
long h = m_Grid.GetRowHeight(lRow) / nTwipsPerPixelY + 1;
// 5. 形成相對對話框的矩形區域
CRect rcCell(x, y, x + w, y + h);
rcCell.OffsetRect(rcGrid.left + 1, rcGrid.top + 1); // 微調對齊
// 6. 取得儲存格現有文字
CString strCellText = m_Grid.GetTextMatrix(lRow, lCol);
// 7. 依欄位屬性決定使用 CEdit 或 CComboBox (此處略過判斷邏輯)
bool useCombo = ShouldUseComboBox(lCol); // 自訂判斷函式
if (useCombo)
{
m_ComboCtrl.ResetContent();
FillComboBoxItems(lCol, m_ComboCtrl); // 填入選項
m_ComboCtrl.MoveWindow(&rcCell);
m_ComboCtrl.SelectString(-1, strCellText);
m_ComboCtrl.ShowWindow(SW_SHOW);
m_ComboCtrl.SetFocus();
m_EditCtrl.ShowWindow(SW_HIDE);
}
else
{
m_EditCtrl.MoveWindow(&rcCell);
m_EditCtrl.SetWindowText(strCellText);
m_EditCtrl.SetSel(0, -1); // 全選
m_EditCtrl.ShowWindow(SW_SHOW);
m_EditCtrl.SetFocus();
m_ComboCtrl.ShowWindow(SW_HIDE);
}
}
透過 EN_KILLFOCUS (CEdit) 與 CBN_KILLFOCUS (CComboBox) 訊息處理:
// CEdit 失去焦點
void CChartInfoEditDlg::OnKillFocusEdit()
{
UpdateData(TRUE); // DDX 取得 m_strEditText
m_Grid.SetTextMatrix(m_Grid.GetRowSel(), m_Grid.GetColSel(), m_strEditText);
m_EditCtrl.ShowWindow(SW_HIDE);
UpdateData(FALSE);
}
// CComboBox 失去焦點
void CChartInfoEditDlg::OnKillFocusCombo()
{
CString str;
m_ComboCtrl.GetWindowText(str);
m_Grid.SetTextMatrix(m_Grid.GetRowSel(), m_Grid.GetColSel(), str);
m_ComboCtrl.ShowWindow(SW_HIDE);
}
CEdit/CComboBox/CDateTimeCtrl 等,依欄位型別動態切換。OnKillFocus 中加入格式檢查 (數字、日期、長度),失敗時保持焦點並提示。WM_KEYDOWN (Enter/Tab/Escape) 實現類 Excel 的儲存格跳轉體驗。| 問題 | 原因 | 解決方案 |
|---|---|---|
| 編輯控件位置偏移 | Twips/Pixel 換算遺漏、未考慮邊框/捲軸 | 嚴格依照 GetColPos/GetRowPos 換算,並實測微調 +1 像素 |
| 焦點閃爍或無法輸入 | ShowWindow/Swap 控件順序錯誤、未呼叫 SetFocus | 先 MoveWindow 再 ShowWindow 再 SetFocus,確保 Z-Order 正確 |
| ComboBox 選項不顯示 | 未呼叫 ResetContent、或 Items 為空 | 每次 OnClick 重新填充選項,確保內容最新 |
| 編輯後資料未寫回 | KillFocus 事件未正確映射、或 Grid 索引錯誤 | 使用 GetRowSel()/GetColSel() 取得當前編輯儲存格 |
「視覺假象法」雖非原生編輯支援,但實作彈性高、相容性好,是 MFC 時代延伸 MSFlexGrid 功能的經典方案。掌握座標換算、焦點管理、控件切換三大核心,即可輕鬆打造專業級可編輯表格。若專案允許,現代開發建議改用 CGridCtrl (CodeProject)、BCGControlBar 或 Qt/QML 等原生支援編輯的表格元件,開發效率更高。