在 Visual C++ 中,CEdit 和 CRichEditCtrl 是兩個最常用的編輯控件,它們已能滿足大多數開發需求。然而,CEdit 支援滑鼠右鍵訊息,方便使用者進行剪貼簿操作,而 CRichEditCtrl 雖支援豐富文字格式(如 Word 般的字型、顏色設定),卻缺乏內建的右鍵選單支援。本文將示範如何透過繼承 CRichEditCtrl,為其加入完整的右鍵快捷選單,包含復原、剪下、複製、貼上、清除、全選與字型設定等功能。
首先從 CRichEditCtrl 衍生一個新類別 CMyRichEdit,所有擴充功能將在這個類別中實作。
在類別精靈中為 WM_RBUTTONDOWN 新增處理函式 OnRButtonDown,並在其中建立彈出式選單:
// 設定焦點
SetFocus();
// 建立彈出式選單
CMenu popmenu;
popmenu.CreatePopupMenu();
// 新增選單項目
popmenu.AppendMenu(MF_STRING, ID_RICH_UNDO, _T("&Undo"));
popmenu.AppendMenu(MF_SEPARATOR);
popmenu.AppendMenu(MF_STRING, ID_RICH_CUT, _T("Cu&t"));
popmenu.AppendMenu(MF_STRING, ID_RICH_COPY, _T("C&opy"));
popmenu.AppendMenu(MF_STRING, ID_RICH_PASTE, _T("&Paste"));
popmenu.AppendMenu(MF_STRING, ID_RICH_CLEAR, _T("C&lear"));
popmenu.AppendMenu(MF_SEPARATOR);
popmenu.AppendMenu(MF_STRING, ID_RICH_SELECTALL, _T("Select &All"));
popmenu.AppendMenu(MF_SEPARATOR);
popmenu.AppendMenu(MF_STRING, ID_RICH_SETFONT, _T("Select &Font"));
// 依狀態啟用/停用選單項目
UINT nUndo = CanUndo() ? 0 : MF_GRAYED;
popmenu.EnableMenuItem(ID_RICH_UNDO, MF_BYCOMMAND | nUndo);
UINT nSel = (GetSelectionType() != SEL_EMPTY) ? 0 : MF_GRAYED;
popmenu.EnableMenuItem(ID_RICH_CUT, MF_BYCOMMAND | nSel);
popmenu.EnableMenuItem(ID_RICH_COPY, MF_BYCOMMAND | nSel);
popmenu.EnableMenuItem(ID_RICH_CLEAR, MF_BYCOMMAND | nSel);
UINT nPaste = CanPaste() ? 0 : MF_GRAYED;
popmenu.EnableMenuItem(ID_RICH_PASTE, MF_BYCOMMAND | nPaste);
// 顯示選單
CPoint pt;
GetCursorPos(&pt);
popmenu.TrackPopupMenu(TPM_RIGHTBUTTON | TPM_LEFTALIGN, pt.x, pt.y, this);
popmenu.DestroyMenu();
在標頭檔 (.h) 中定義選單項目對應的命令 ID,數值可自訂,或透過「View → Resource Symbols」由 VC++ 自動管理:
#define ID_RICH_UNDO 101
#define ID_RICH_CUT 102
#define ID_RICH_COPY 103
#define ID_RICH_PASTE 104
#define ID_RICH_CLEAR 105
#define ID_RICH_SELECTALL 106
#define ID_RICH_SETFONT 107
在標頭檔宣告處理函式:
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnCopy() { Copy(); }
afx_msg void OnCut() { Cut(); }
afx_msg void OnPaste() { Paste(); }
afx_msg void OnSelectall() { SetSel(0, -1); }
afx_msg void OnUndo() { Undo(); }
afx_msg void OnClear() { Clear(); }
afx_msg void OnSelectfont(); // 變更字型
在實作檔 (.cpp) 的訊息對應巨集中加入:
ON_COMMAND(ID_RICH_COPY, OnCopy)
ON_COMMAND(ID_RICH_CUT, OnCut)
ON_COMMAND(ID_RICH_PASTE, OnPaste)
ON_COMMAND(ID_RICH_SELECTALL, OnSelectall)
ON_COMMAND(ID_RICH_UNDO, OnUndo)
ON_COMMAND(ID_RICH_CLEAR, OnClear)
ON_COMMAND(ID_RICH_SETFONT, OnSelectfont)
此函式會依目前選取狀態(有選取文字或無選取)決定套用對象,並呼叫 CFontDialog 讓使用者挑選字型:
void CMyRichEdit::OnSelectfont()
{
CHARFORMAT cf = {0};
LOGFONT lf = {0};
cf.cbSize = sizeof(CHARFORMAT);
// 判斷是否有選取文字
BOOL bSelect = (GetSelectionType() != SEL_EMPTY);
if (bSelect)
GetSelectionCharFormat(cf);
else
GetDefaultCharFormat(cf);
// 將 CHARFORMAT 轉為 LOGFONT 供 CFontDialog 使用
lf.lfCharSet = cf.bCharSet;
lf.lfHeight = cf.yHeight / 15; // twips to logical units
lf.lfPitchAndFamily = cf.bPitchAndFamily;
lf.lfItalic = (cf.dwEffects & CFE_ITALIC) ? TRUE : FALSE;
lf.lfWeight = (cf.dwEffects & CFE_BOLD) ? FW_BOLD : FW_NORMAL;
lf.lfUnderline = (cf.dwEffects & CFE_UNDERLINE) ? TRUE : FALSE;
lf.lfStrikeOut = (cf.dwEffects & CFE_STRIKEOUT) ? TRUE : FALSE;
_tcscpy_s(lf.lfFaceName, LF_FACESIZE, cf.szFaceName);
CFontDialog dlg(&lf);
dlg.m_cf.rgbColors = cf.crTextColor;
if (dlg.DoModal() == IDOK)
{
dlg.GetCharFormat(cf);
if (bSelect)
SetSelectionCharFormat(cf); // 套用至選取區塊
else
SetWordCharFormat(cf); // 套用至後續輸入
}
}
在專案中包含 CMyRichEdit.h 與 CMyRichEdit.cpp,將原本宣告為 CRichEditCtrl 的控制項變數改為 CMyRichEdit 即可。別忘了在 CWinApp::InitInstance() 中呼叫 AfxInitRichEdit() (或 AfxInitRichEdit2()) 以初始化 Rich Edit 程式庫。
圖示:程式執行效果,右鍵選單包含編輯與字型功能。