|
I came up with the following solution. Override the virtual function GetContextMenu() within your CRichEditView derived class and add the following code.
<br />
CRichEditCtrl& Edit = GetRichEditCtrl();<br />
<br />
CString strMenu[] =<br />
{<br />
"&Undo", "Cu&t", "&Copy", "&Paste", "&Delete", "Select &All"<br />
};<br />
int nCmd[] = {WM_UNDO, WM_CUT, WM_COPY, WM_PASTE, WM_CLEAR, EM_SETSEL};<br />
CMenu menu;<br />
HMODULE hUser32 = LoadLibrary("USER32");<br />
if (hUser32)<br />
{<br />
if (menu.Attach(LoadMenu(hUser32, MAKEINTRESOURCE(1))))<br />
{<br />
for (int i = _countof(nCmd); --i >= 0;)<br />
VERIFY(menu.GetMenuString(nCmd[i], strMenu[i], MF_BYCOMMAND) != 0);<br />
}<br />
menu.Detach();<br />
FreeLibrary(hUser32);<br />
}<br />
<br />
menu.CreatePopupMenu();<br />
menu.AppendMenu(MF_STRING, ID_EDIT_UNDO, strMenu[0]);<br />
menu.AppendMenu(MF_SEPARATOR);<br />
menu.AppendMenu(MF_STRING, ID_EDIT_CUT, strMenu[1]);<br />
menu.AppendMenu(MF_STRING, ID_EDIT_COPY, strMenu[2]);<br />
menu.AppendMenu(MF_STRING, ID_EDIT_PASTE, strMenu[3]);<br />
menu.AppendMenu(MF_STRING, ID_EDIT_CLEAR, strMenu[4]);<br />
menu.AppendMenu(MF_SEPARATOR);<br />
menu.AppendMenu(MF_STRING, ID_EDIT_SELECT_ALL, strMenu[5]);<br />
<br />
CallPopupMenuUpdateHandlers(&menu, this);<br />
<br />
HMENU hMenu = menu.Detach();<br />
return hMenu;<br />
As you might have noticed, MFC does not automatically call the required update handlers for the menu items. Although MFC tries to do a lot of work to make programming as easy as possible, I've encountered several situations where MFC did not the perform the automatic magic for the update handlers. In these situations I manually added the function CallPopupMenuUpdateHandlers(). The function was copied from a part of the CFrameWnd class, because the functionality is not being exposed in another way.
The implementation for CallPopupMenuUpdateHandlers() looks like:
<br />
void CallPopupMenuUpdateHandlers(CMenu* pMenu, CCmdTarget* pTarget)<br />
{<br />
<br />
CCmdUI state;<br />
state.m_pMenu = pMenu;<br />
ASSERT(state.m_pOther == NULL);<br />
ASSERT(state.m_pParentMenu == NULL);<br />
<br />
state.m_nIndexMax = pMenu->GetMenuItemCount();<br />
for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;<br />
state.m_nIndex++)<br />
{<br />
state.m_nID = pMenu->GetMenuItemID(state.m_nIndex);<br />
if (state.m_nID == 0)<br />
continue;
<br />
ASSERT(state.m_pOther == NULL);<br />
ASSERT(state.m_pMenu != NULL);<br />
if (state.m_nID == (UINT)-1)<br />
{<br />
state.m_pSubMenu = pMenu->GetSubMenu(state.m_nIndex);<br />
if (state.m_pSubMenu == NULL ||<br />
(state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||<br />
state.m_nID == (UINT)-1)<br />
{<br />
continue;
}<br />
state.DoUpdate(pTarget, FALSE);
}<br />
else<br />
{<br />
state.m_pSubMenu = NULL;<br />
state.DoUpdate(pTarget, true);<br />
}<br />
<br />
UINT nCount = pMenu->GetMenuItemCount();<br />
if (nCount < state.m_nIndexMax)<br />
{<br />
state.m_nIndex -= (state.m_nIndexMax - nCount);<br />
while (state.m_nIndex < nCount &&<br />
pMenu->GetMenuItemID(state.m_nIndex) == state.m_nID)<br />
{<br />
state.m_nIndex++;<br />
}<br />
}<br />
state.m_nIndexMax = nCount;<br />
}<br />
}<br />
Hope this code is helpfull to some of you!
Marc
|
|
|
|
|
sorry, the code snippets are not completely in correct style.
I don't know why? but anyway... it might be of help for anyone.
Marc
|
|
|
|
|
Is it possible to insert a control, like an edit box, in a popup menu item?
|
|
|
|
|
Thanks again
|
|
|
|
|
You can use User32.dll for have an international Context Menu
<br />
void RichEdit_ContextMenu(HWND hwndRichEdit){<br />
<br />
BOOL bCandUndo=SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0);<br />
BOOL bCandRedo=SendMessage(hwndRichEdit, EM_CANREDO, 0, 0);<br />
<br />
DWORD dwStyle = GetWindowLong(hwndRichEdit, GWL_STYLE);<br />
DWORD ichSelectionStart;<br />
DWORD ichSelectionEnd;<br />
SendMessage(hwndRichEdit, EM_GETSEL, OUT (WPARAM)&&RichSelectionStart, OUT (LPARAM)&&RichSelectionEnd);<br />
int cchSelection = ichSelectionEnd - ichSelectionStart;<br />
<br />
BOOL bCanCut=(cchSelection && !(dwStyle&ES_READONLY));<br />
BOOL bCanCopy=(cchSelection);<br />
BOOL bCanPaste=SendMessage(hwndRichEdit, EM_CANPASTE, 0, 0);<br />
<br />
HMODULE hUser32=LoadLibrary("USER32");<br />
<br />
char Strings[6][100]={"&Undo","Cu&t","&Copy","&Paste","&Delete","Select &All"};<br />
int Ids[6]={WM_UNDO,WM_CUT,WM_COPY,WM_PASTE,WM_CLEAR,EM_SETSEL};<br />
<br />
if (hUser32){<br />
HMENU hMenu=LoadMenu(hUser32,(LPCSTR)1);<br />
for (int i=5;i>=0;--i)<br />
GetMenuItemString(hMenu,Ids[i],false,Strings[i],100);<br />
FreeLibrary(hUser32);<br />
}<br />
<br />
HMENU hMenu = CreatePopupMenu();<br />
AppendMenuEx(hMenu,MF_STRING|(bCandUndo?0:MF_DISABLED),Ids[0],Strings[0]);<br />
AppendMenu(hMenu,MF_SEPARATOR,0,0);<br />
AppendMenuEx(hMenu,MF_STRING|(bCanCut?0:MF_DISABLED),Ids[1],Strings[1]);<br />
AppendMenuEx(hMenu,MF_STRING|(bCanCopy?0:MF_DISABLED),Ids[2],Strings[2]);<br />
AppendMenuEx(hMenu,MF_STRING|(bCanPaste?0:MF_DISABLED),Ids[3],Strings[3]);<br />
AppendMenu(hMenu,MF_STRING,Ids[4],Strings[4]);<br />
AppendMenu(hMenu,MF_SEPARATOR,0,0);<br />
AppendMenu(hMenu,MF_STRING,Ids[5],Strings[5]);<br />
<br />
POINT pt;<br />
GetCursorPos(OUT &pt);<br />
<br />
int nCmdId = TrackPopupMenu(hMenu,TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,pt.x, pt.y,0, hwndRichEdit, NULL);<br />
<br />
DestroyMenu(hMenu);<br />
<br />
switch (nCmdId) {<br />
case EM_UNDO:<br />
case WM_CUT:<br />
case WM_COPY:<br />
case WM_PASTE:<br />
case WM_CLEAR:<br />
case EM_SETSEL:<br />
SendMessage(hwndRichEdit, nCmdId, 0, -1);<br />
break;<br />
}<br />
}<br />
|
|
|
|
|
I added this code to my CRichEditCtrl and gave the menu items the same IDs as my tool bar buttons but they don't work. Also my Update Command UIs don't disable the menu when for example cut and copy are not valid menu items.
|
|
|
|
|
Thanks for this article - saved me a lot of research too.
I made one addition to it - you can detect whether a control is rich edit by finding out what its class is - and then adding that into the message loop automatically adds the right click menu to any of your rich edit fields throughout the program.
I'm working in C. Maybe this will be useful for others who use C rather than C++:
HMENU MakeRichEditRightClickMenu(HWND hwndRichEdit)
{
#define IDC_RIGHT_CLICK_CUT 1
#define IDC_RIGHT_CLICK_COPY 2
#define IDC_RIGHT_CLICK_PASTE 3
HMENU hRichEditRightClickMenu=NULL;
CHARRANGE charr;
char highlight=0;
if(!hwndRichEdit)
return NULL;
hRichEditRightClickMenu=CreatePopupMenu();
SendMessage(hwndRichEdit,EM_EXGETSEL,0,(LPARAM)&charr);
highlight=charr.cpMax>charr.cpMin?1:0;
AppendMenu(hRichEditRightClickMenu,MF_STRING,IDC_RIGHT_CLICK_CUT,"Cut");
AppendMenu(hRichEditRightClickMenu,MF_STRING,IDC_RIGHT_CLICK_COPY,"Copy");
AppendMenu(hRichEditRightClickMenu,MF_STRING,IDC_RIGHT_CLICK_PASTE,"Paste");
if(!highlight)
{
EnableMenuItem(hRichEditRightClickMenu,IDC_RIGHT_CLICK_CUT,MF_GRAYED);
EnableMenuItem(hRichEditRightClickMenu,IDC_RIGHT_CLICK_COPY,MF_GRAYED);
}
if(!IsClipboardFormatAvailable(CF_TEXT))
EnableMenuItem(hRichEditRightClickMenu,IDC_RIGHT_CLICK_PASTE,MF_GRAYED);
return hRichEditRightClickMenu;
}
while(GetMessage(&msg,NULL,0,0))
{
if(msg.message==WM_RBUTTONUP)
if(msg.hwnd==GetFocus())
{
char szClass[64];
GetClassName(msg.hwnd, szClass, sizeof(szClass));
if(strcmpi(szClass,"RichEdit20A")==0)
{
POINT point;
HMENU hMenu=MakeRichEditRightClickMenu(msg.hwnd);
floating_popup_init=1;
GetCursorPos(&point);
if(hMenu)
{
HWND hwndRichEdit=msg.hwnd;
WORD idc=TrackPopupMenu
(hMenu,
TPM_LEFTBUTTON|TPM_RIGHTBUTTON|TPM_LEFTALIGN|TPM_RETURNCMD ,
point.x, point.y, 0, hwndMain, NULL
);
switch(idc)
{
case IDC_RIGHT_CLICK_CUT:
SendMessage(hwndRichEdit,WM_CUT,0,0);
break;
case IDC_RIGHT_CLICK_COPY:
SendMessage(hwndRichEdit,WM_COPY,0,0);
break;
case IDC_RIGHT_CLICK_PASTE:
SendMessage(hwndRichEdit,WM_PASTE,0,0);
break;
}
DestroyMenu(hMenu);
}
}
}
if(TranslateAccelerator(hwndMain,hAccel,&msg))
continue;
if(IsDialogMessage(hDlgHelp,&msg))
continue;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
For those using C++ then the new addition is (at least, in the C version o fthe code):
char szClass[64];
GetClassName(msg.hwnd, szClass, sizeof(szClass));
if(strcmpi(szClass,"RichEdit20A")==0)
I expcet it's easy to modify that for C++///
Thanks again for a great article
Robert
|
|
|
|
|
Does anyone know how to call CreateWindow to make a component that behaves like "CRichEditCtrl" without mfc in win32?
|
|
|
|
|
CRect rect(0,0,100,100);
CRichEditCtrl richedit;
CWnd * pParent = parent window of richedit control
CWnd* pWnd = &richedit;
pWnd->Create(RICHEDIT_CLASS, NULL, ES_MULTILINE | ES_WANTRETURN, rect, pParent, ID_RICHEDIT);
|
|
|
|
|
I found a problem with your code for the TrackPopupMenu(....)
You used TPM_NONOTIFY and TPM_RETURNCMD, that would only work if they chose at that very instant [quite impossible in my mind]. Those 2 commands are pretty much only good for TrackPopupMenuEx which waits until it returns.
Oh, and a quick way to make the menu work :
Name your menu commands ID_EDIT_CUT ... and such, the drop-down list works. And subclass CRichEditCommands , this is using WTL, so I'm not quite sure if MFC has it. Those commands there and handlers make it quite easy because you wont have to catch the WM_COMMAND messages as long as you chain the messages [not sure if thats MFC'able .. havent worked much with MFC at all.. but its theory-ish stuff ya could get]
Hope my reply has been useful, because this article was very useful also.
|
|
|
|
|
Thank you for the tips. It is very helpful.
I would suggest people to use the OnNotify method rather than the PreTranslateMessage.
I wrapped an CRichEditCtrl within an ActiveX control and I used the PreTranslateMessage to deal with RBUTTONUP message at first. In some rare combination of mouse clicks(especially double click of RButton), the PreTranslateMessage may not receive the mouse event anymore until the window is repainted. It confused me and I used the Spy++ to track down the message going thru the olecontrol and it did receive the RBUTTONUP message but not going thru PreTranslateMessage . After I changed it to OnNotify, it worked everytime.
|
|
|
|
|
Sublcassing the WndProc and intercepting the message WM_CONTEXTMENU does the trick too. The message WM_CONTEXTMENU is automatically sent when the user release the right-click and/or press the context-menu key (VK_APP).
|
|
|
|
|
Can you provide an example of how to do this, please?
Thanks in advance.
|
|
|
|
|
#define NA 0 // Not Applicable. The parameter has a value of zero because it does not apply to the context of the other parameters.
void
AppendMenuCommand(
INOUT HMENU hMenuResult,
UINT idMenuItem,
LPCTSTR pszMenuItemText,
UINT idMenuInsertAfter = 0)
{
ASSERT(hMenuResult != NULL);
MENUITEMINFO mii;
ZeroMemory(OUT &mii, sizeof(mii));
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_ID | MIIM_TYPE;
mii.wID = idMenuItem;
mii.fType = MFT_STRING;
mii.dwTypeData = (TCHAR *)pszMenuItemText;
BOOL fSuccess = InsertMenuItem(hMenuResult, idMenuInsertAfter, FALSE, IN &mii);
ASSERT(fSuccess);
}
#define EnableMenuCommand(hMenu, idMenuItem, fEnable) ::EnableMenuItem(hMenu, idMenuItem, (fEnable) ? MF_ENABLED : MF_GRAYED)
int
RichEdit_DisplayContextMenu(HWND hwndRichEdit)
{
ASSERT(IsWindow(hwndRichEdit));
HMENU haMenu = ::CreatePopupMenu();
AppendMenuCommand(INOUT haMenu, ID_EDIT_UNDO, "Undo");
AppendMenuCommand(INOUT haMenu, ID_EDIT_REDO, "Redo");
AppendMenuCommand(INOUT haMenu, ID_EDIT_CUT, "Cut");
AppendMenuCommand(INOUT haMenu, ID_EDIT_COPY, "Copy");
AppendMenuCommand(INOUT haMenu, ID_EDIT_PASTE, "Paste");
EnableMenuCommand(haMenu, ID_EDIT_UNDO, SendMessage(hwndRichEdit, EM_CANUNDO, NA, NA));
EnableMenuCommand(haMenu, ID_EDIT_REDO, SendMessage(hwndRichEdit, EM_CANREDO, NA, NA));
DWORD dwStyle = GetWindowLong(hwndRichEdit, GWL_STYLE);
DWORD ichSelectionStart;
DWORD ichSelectionEnd;
SendMessage(hwndRichEdit, EM_GETSEL, OUT (WPARAM)&ichSelectionStart, OUT (LPARAM)&ichSelectionEnd);
int cchSelection = ichSelectionEnd - ichSelectionStart;
EnableMenuCommand(haMenu, ID_EDIT_CUT, cchSelection && (dwStyle & ES_READONLY) == 0);
EnableMenuCommand(haMenu, ID_EDIT_COPY, cchSelection);
#define _CF_ALL 0 // All the clipboard formats accepted by the rich-edit
EnableMenuCommand(haMenu, ID_EDIT_PASTE, SendMessage(hwndRichEdit, EM_CANPASTE, _CF_ALL, NA));
POINT pt;
::GetCursorPos(OUT &pt);
int nCmdId = ::TrackPopupMenu(
haMenu,
TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,
pt.x, pt.y,
0, g_hwndMain, NULL);
::DestroyMenu(haMenu);
return nCmdId;
}
void
RichEdit_ExecuteMenuCommand(HWND hwndRichEdit, int nCmdId)
{
ASSERT(IsWindow(hwndRichEdit));
UINT uMessage = 0;
switch (nCmdId)
{
#ifdef DEBUG
default:
ASSERT(FALSE && "Unknown command for rich-edit control");
case 0:
return;
#endif
case ID_EDIT_UNDO:
uMessage = EM_UNDO;
break;
case ID_EDIT_REDO:
uMessage = EM_REDO;
break;
case ID_EDIT_CUT:
uMessage = WM_CUT;
break;
case ID_EDIT_COPY:
uMessage = WM_COPY;
break;
case ID_EDIT_PASTE:
uMessage = WM_PASTE;
break;
case ID_EDIT_SELECT_ALL:
::SendMessage(hwndRichEdit, EM_SETSEL, 0, -1);
break;
case ID_EDIT_CLEAR_ALL:
::SendMessage(hwndRichEdit, WM_SETTEXT, NA, NULL);
break;
}
if (uMessage != 0)
::SendMessage(hwndRichEdit, uMessage, NA, NA);
}
void
RichEdit_DisplayAndExecuteContextMenuCommand(HWND hwndRichEdit)
{
RichEdit_ExecuteMenuCommand(hwndRichEdit, RichEdit_DisplayContextMenu(hwndRichEdit));
}
static WNDPROC g_pfnWndProcRichEditOld;
LRESULT CALLBACK
WndProcRichEdit(HWND hwndRichEdit, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
ASSERT(g_pfnWndProcRichEditOld != NULL);
switch (uMsg)
{
case WM_CONTEXTMENU:
RichEdit_DisplayAndExecuteContextMenuCommand(hwndRichEdit);
return 0;
}
return ::CallWindowProc(g_pfnWndProcRichEditOld, hwndRichEdit, uMsg, wParam, lParam);
}
void
RichEdit_Subclass(HWND hwndRichEdit)
{
ASSERT(IsWindow(hwndRichEdit));
g_pfnWndProcRichEditOld = (WNDPROC)SetWindowLong(hwndRichEdit, GWL_WNDPROC, (LONG)WndProcRichEdit);
}
|
|
|
|
|
In MicroSoft Word, there is "Show or hide formatting marks" to show or hide the space and newline(Enter key) by chars 0xB6 and 0xB7. How can I achievie this function inside my CRichEditCtrl? Thanks for help.
|
|
|
|
|
Hard not to say thank you.
Your article saved me many time for research.
Thanks again!
|
|
|
|
|
oh *gosh*
Bryce
|
|
|
|
|
Very helpfull.
|
|
|
|
|
When I Build your exemple of popup menu in mode Release, i can use the popup menu with click right but i select exemple copy and i click richedit and select copy with click right and I have a access violation.
What is the probleme ?
Can you help me ?
excuse me for my english
|
|
|
|
|
By using
afx_msg LRESULT OnCopy(WPARAM/*wparam*/,LPARAM/*lparam*/);(also for OnSelctAll..)
we can fix .
|
|
|
|
|
"But alas CRicheditCtrl had no direct method for displaying a context menu..."
Actually, the RichEdit control has an interface called IRichEditOleCallback, which has a method called GetContextMenu:
HRESULT GetContextMenu(
WORD seltype,
LPOLEOBJECT lpoleobj,
CHARRANGE FAR *lpchrg,
HMENU FAR *lphmenu
);
It calls this method each time the user has right clicked in its window. If you're unfamiliar with COM, you can take a look at the CRichEditView source that implements this method.
|
|
|
|
|
cool, I didnt know that!
thanks
I should maybe append the article to reflect this
Bryce
|
|
|
|
|
I might add that your work isn't at all useless , since one need to do like you've done in order to display an ownerdrawn context menu. For instance, if want it to have the Office XP-look, this would be an excellent way to get started.
|
|
|
|
|
excuse me , It's CRichEditCtrlView but not CRichEditCtrl implements GetContextMenu() method.so,there is nothing wrong !
|
|
|
|
|
CRichEditCtrl::SetOLECallback();
|
|
|
|
|