Click here to Skip to main content
Click here to Skip to main content

How to modify a CEdit context menu

By , 16 Sep 2001
 

Introduction

Have you ever wanted to modify the context menu of an edit control? You would start by overriding the OnInitMenuPopup() function only to find that the edit control does not post a WM_INITMENUPOPUP message, so your function is never called. Well, here is a simple CEdit derived class that you can use.

The CMenuEdit class

The CMenuEdit class does its job with just two functions, OnContextMenu() and OnCommand().

The OnContextMenu() function gets called when a user right-clicks on the edit control. In our override, we create a new popup menu that exactly duplicates the default context menu, and we call TrackPopupMenu() on that menu. By doing this, a WM_INITMENUPOPUP message is posted, which can then be handled in a class derived from CMenuEdit.

The OnCommand() function handles commands generated when a user selects an item from the menu. If the command is not generated by our menu, it is passed onto CEdit::OnCommand().

You can either derive your edit class from CMenuEdit or include these two functions in your class.

Updates

September 17, 2001 - Now handles read-only edit controls

The Source Files

The Header file

// MenuEdit.h : header file
// Written by PJ Arends
// pja@telus.net
// http://www3.telus.net/pja/

#if !defined(AFX_MENUEDIT_H__8EA53611_FD2B_11D4_B625_D04FA07D2222__INCLUDED_)
#define AFX_MENUEDIT_H__8EA53611_FD2B_11D4_B625_D04FA07D2222__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif 

class CMenuEdit : public CEdit
{
public:
    CMenuEdit() {};

protected:
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
    afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);

    DECLARE_MESSAGE_MAP()
};

#endif 

The Source file

// MenuEdit.cpp : implementation file
// Written by PJ Arends
// pja@telus.net
// http://www3.telus.net/pja/

#include "stdafx.h"
#include "MenuEdit.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define MES_UNDO        _T("&Undo")
#define MES_CUT         _T("Cu&t")
#define MES_COPY        _T("&Copy")
#define MES_PASTE       _T("&Paste")
#define MES_DELETE      _T("&Delete")
#define MES_SELECTALL   _T("Select &All")
#define ME_SELECTALL    WM_USER + 0x7000

BEGIN_MESSAGE_MAP(CMenuEdit, CEdit)
    ON_WM_CONTEXTMENU()
END_MESSAGE_MAP()

void CMenuEdit::OnContextMenu(CWnd* pWnd, CPoint point)
{
    SetFocus();
    CMenu menu;
    menu.CreatePopupMenu();
    BOOL bReadOnly = GetStyle() & ES_READONLY;
    DWORD flags = CanUndo() && !bReadOnly ? 0 : MF_GRAYED;
    menu.InsertMenu(0, MF_BYPOSITION | flags, EM_UNDO,
        MES_UNDO);

    menu.InsertMenu(1, MF_BYPOSITION | MF_SEPARATOR);

    DWORD sel = GetSel();
    flags = LOWORD(sel) == HIWORD(sel) ? MF_GRAYED : 0;
    menu.InsertMenu(2, MF_BYPOSITION | flags, WM_COPY,
        MES_COPY);

    flags = (flags == MF_GRAYED || bReadOnly) ? MF_GRAYED : 0;
    menu.InsertMenu(2, MF_BYPOSITION | flags, WM_CUT,
        MES_CUT);
    menu.InsertMenu(4, MF_BYPOSITION | flags, WM_CLEAR,
        MES_DELETE);

    flags = IsClipboardFormatAvailable(CF_TEXT) &&
        !bReadOnly ? 0 : MF_GRAYED;
    menu.InsertMenu(4, MF_BYPOSITION | flags, WM_PASTE,
        MES_PASTE);

    menu.InsertMenu(6, MF_BYPOSITION | MF_SEPARATOR);

    int len = GetWindowTextLength();
    flags = (!len || (LOWORD(sel) == 0 && HIWORD(sel) ==
        len)) ? MF_GRAYED : 0;
    menu.InsertMenu(7, MF_BYPOSITION | flags, ME_SELECTALL,
        MES_SELECTALL);

    menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON |
        TPM_RIGHTBUTTON, point.x, point.y, this);
}

BOOL CMenuEdit::OnCommand(WPARAM wParam, LPARAM lParam)
{
    switch (LOWORD(wParam))
    {
    case EM_UNDO:
    case WM_CUT:
    case WM_COPY:
    case WM_CLEAR:
    case WM_PASTE:
        return SendMessage(LOWORD(wParam));
    case ME_SELECTALL:
        return SendMessage (EM_SETSEL, 0, -1);
    default:
        return CEdit::OnCommand(wParam, lParam);
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

PJ Arends
President
Canada Canada
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 4memberFalkir26 Mar '11 - 8:42 
Concise, precise, helpful
GeneralMy vote of 5memberYangyong Qin1 Mar '11 - 13:22 
Quite useful!
GeneralExample program would be usefulmemberl_d_allan1 Jan '04 - 2:24 
This is a nice program, and much appreciated. However, a simple dialog application that used the control would complete its usefulness.
 
If this was done, could you make the entire project available as a download?
 
TIA,
QuestionDoes this work on CDialogBar?memberSteveBob3 Jun '03 - 14:21 
I am trying to use CMenuEdit with a CDialogBar control. Does this class not work with CDialogBar, or am i using it wrong?
 
-- Steve
AnswerRe: Does this work on CDialogBar?memberPJ Arends4 Jun '03 - 21:13 
This class is a simple subclassed CEdit control, so there is no reason why it should not work. But I am not familiar with CDialogBars as I have never had a need to use them so I do not know if there is a gotcha to look out for.
 






Sonork 100.11743 Chicken Little
 
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
 
Within you lies the power for good - Use it!
GeneralA small questionmemberDoan Quang Minh31 May '03 - 17:57 
I've read your article, and I want to ask you a small question: If I have a ComboBox, how can I modify a context menu of the editor of this ComboBox (I known that a combobox is combined by a Listbox and a edit). Please help me! Thanks
 
Never say Goodbye!
GeneralRe: A small questionmemberPJ Arends13 Jul '03 - 0:57 
Better late then neverBig Grin | :-D
 
I had to do just what you want, so I finally figured it out.
 
Subclass the CComboBox, and add a CMenuEdit control variable to your class.
 
#include "MenuEdit.h"
 
class CMyComboBox : public CComboBox
{
...
protected:
    CMenuEdit m_MyEditControl;
...
Then add the PreSubclassWindow() virtual function
void CMyComboBox::PreSubclassWindow()
{
    CComboBox::PreSubclassWindow();
 
    m_MyEditControl.SubclassDlgItem(1001, this);
}

 






Sonork 100.11743 Chicken Little
 
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
 
Within you lies the power for good - Use it!
GeneralRe: A small questionmemberDoan Quang Minh14 Jul '03 - 0:21 
Thank for your help
 
Never say Goodbye!
GeneralRe: A small questionmemberhoc9629 Jul '03 - 22:11 
Thank you very much for this elegant solution.
 
Hoc Ngo
QuestionHow to access datamemberJack_pt15 Nov '02 - 16:55 
I created a test CFormView app, included MenuEdit.h, added a CEdit control and changed it to type CMenuEdit. When I right click on the edit control the new menu shows up. Now, if I click on the part of the menu I added and have it execute a function that should return a string, how do I get access to that string so that I can copy it into the edit control?
AnswerRe: How to access datamemberPJ Arends16 Nov '02 - 9:10 
I'm not quite sure where your problem is, but you could just call SetWindowText() on the returned value.
 
void CMyMenuEdit::OnMyMenuItem()
{
    SetWindowText(MyFunctionThatReturnsAString());
}
If this does not help, then could you please give me a little more detail (post some code) on your problem.
 


CPUA 0x5041
 
Sonork 100.11743 Chicken Little
 
"So it can now be written in stone as a testament to humanities achievments "PJ did Pi at CP"." Colin Davies
 
Within you lies the power for good - Use it!
GeneralRe: How to access datamemberJack_pt18 Nov '02 - 3:08 
Duh. Sorry to have bothered you. I was in the middle of adding a dialog box when I added your code. So I was thinking (not thinking really) I had to return the result. It works great. Thanks for doing this.Smile | :)
GeneralWay to avoid overriding OnCommandmemberPaul S. Vickery9 Aug '02 - 0:59 
It's a matter of preference, but I would choose to get TrackPopupMenu return the selected menu item ID, and handle it all in OnContextMenu. This means that the code in OnCommand doesn't get processed every time a WM_COMMAND message is received, just in case it was from the context menu.
 
To acheive this, the code from, and including, the call to TrackPopupMenu can be replaced with the following:
    int nCmd = menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON | 
	TPM_RETURNCMD | TPM_RIGHTBUTTON, point.x, point.y, this);
    menu.DestroyMenu();
    if (nCmd < 0)
      return;
    switch (nCmd)
    {
    case EM_UNDO:
    case WM_CUT:
    case WM_COPY:
    case WM_CLEAR:
    case WM_PASTE:
        SendMessage(nCmd);
    case ME_SELECTALL:
        SendMessage(EM_SETSEL, 0, -1);
    default:
        SendMessage(WM_COMMAND, nCmd);
    }

 
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
GeneralRe: Way to avoid overriding OnCommandmemberPJ Arends9 Aug '02 - 5:55 
Thanks for this Paul.Smile | :)
 


CPUA 0x5041
 
Sonork 100.11743 Chicken Little
 
"So it can now be written in stone as a testament to humanities achievments "PJ did Pi at CP"." Colin Davies
 
Within you lies the power for good - Use it!
GeneralRe: Way to avoid overriding OnCommandmemberPaul Tankard28 Jan '03 - 5:01 
TrackPopupMenu is defined to return a bool, is this an MSDN typo or ? Confused | :confused:
GeneralRe: Way to avoid overriding OnCommandmemberPaul S. Vickery29 Jan '03 - 11:06 
You are correct in saying that TrackPopupMenu is defined as returning a BOOL. However, the documentation for the Win32 function TrackPopupMenu describes a flag of TPM_RETURNCMD (for some reason omitted from the MFC docs), which returns a result to be taken as an int (don't forget that a BOOL is just an int anyway). That's how you can do what I described.
 

 
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
GeneralRe: Way to avoid overriding OnCommandmemberPaul Tankard30 Jan '03 - 0:09 
Cool, thanks for the heads up..
GeneralRe: Way to avoid overriding OnCommandmemberCrnjan22 Oct '04 - 2:30 
You should add break-s within the switch statement, otherwise all the text within edit is selected after a command was executed, which is quite annoying.
 
switch (nCmd)
{
case EM_UNDO:
case WM_CUT:
case WM_COPY:
case WM_CLEAR:
case WM_PASTE:
SendMessage(nCmd);
break;
case ME_SELECTALL:
SendMessage(EM_SETSEL, 0, -1);
break
default:
SendMessage(WM_COMMAND, nCmd);
}
 
TNX for this article, it saved me some reading.
GeneralRe: Way to avoid overriding OnCommandmemberPaul S. Vickery22 Oct '04 - 2:52 
You are absolutely correct - very careless of me! D'Oh! | :doh: Blush | :O Well spotted!
(BTW, in your fix, you have left out a semi-colon Wink | ;) )
 

"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
GeneralThere are some bits missingmemberPaul S. Vickery9 Aug '02 - 0:28 
Good class, but I found a few bits missing:
 
1. The menu is not being destroyed after TrackPopupMenu
2. The menu will not show in the correct place if invoked by the Context Menu key.
 
The solutions:
1. Insert the following code after the call to TrackPopupMenu:
  menu.DestroyMenu();
2. Add the following code before the call to TrackPopupMenu:
  if (point.x == -1 || point.y == -1)
  {
    CRect rc;
    GetClientRect(&rc);
    point = rc.CenterPoint();
    ClientToScreen(&point);
  }

 
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
GeneralRe: There are some bits missingmemberPJ Arends9 Aug '02 - 5:47 
Paul S. Vickery wrote:
1. The menu is not being destroyed after TrackPopupMenu
 
Paul S. Vickery wrote:
1. Insert the following code after the call to TrackPopupMenu:
menu.DestroyMenu();

 
From MSDN:
[quote]
CMenu::DestroyMenu
BOOL DestroyMenu( );
 
Return Value
 
Nonzero if the menu is destroyed; otherwise 0.
 
Remarks
 
Destroys the menu and any Windows resources that were used. The menu is detached from the CMenu object before it is destroyed. The Windows DestroyMenu function is automatically called in the CMenu destructor.
[/quote]
 

Paul S. Vickery wrote:
2. The menu will not show in the correct place if invoked by the Context Menu key.
 
Paul S. Vickery wrote:
2. Add the following code before the call to TrackPopupMenu:
...

 
Thanks for this Paul, I am a heavy mouse user and failed to notice thisWTF | :WTF: WTF | :WTF: Blush | :O
 

Paul S. Vickery wrote:
"The way of a fool seems right to him, but a wise man listens to advice"
 
What are you saying? Is this meant for me?Big Grin | :-D
 


CPUA 0x5041
 
Sonork 100.11743 Chicken Little
 
"So it can now be written in stone as a testament to humanities achievments "PJ did Pi at CP"." Colin Davies
 
Within you lies the power for good - Use it!
GeneralRe: There are some bits missingmemberPaul S. Vickery9 Aug '02 - 6:06 
OK, so you got me on the DestroyMenu: I often use HMENUs directly, where it is important that I remember to destroy them. I guess I've over-indoctrinated myself!

 
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
QuestionQuestion?memberDejan Petrovic27 Sep '01 - 19:44 
This solution seems to be working properly.
However it involves creating and using the derived class CMenuEdit. Is there a way to hook (intercept) the contex menu (of a CEdit) on a dialog level and then alter menu items from there?
Thanks,
 
Dejan
AnswerRe: Question?memberPJ Arends28 Sep '01 - 6:08 
Not that I am aware of. If I knew how I would not have written this little classEek! | :eek:
Anyone else knowConfused | :confused:
 
---
Blessed are those who can laugh at themselves, for they shall never cease to be amused Laugh | :laugh:
GeneralRe: Question?memberzsh21 Oct '08 - 4:10 
You can get the menu context handle with the WM_ENTERIDLE message like that:
 
void CMenuEdit::OnEnterIdle(UINT nWhy, CWnd* pWho) 
{
    CEdit::OnEnterIdle(nWhy, pWho);
    if (nWhy == MSGF_MENU)
    {
        MENUBARINFO mbi;
 
        memset(&mbi, 0, sizeof(MENUBARINFO));
        mbi.cbSize = sizeof(MENUBARINFO);
        GetMenuBarInfo(pWho->m_hWnd, OBJID_CLIENT, 0, &mbi);
        if (GetMenuState(mbi.hMenu, 1001, MF_BYCOMMAND) == -1)
        {
            AppendMenu(mbi.hMenu, MF_STRING, 1001, "New item");
        }
    }
}

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 17 Sep 2001
Article Copyright 2001 by PJ Arends
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid