Home All Groups Group Topic Archive Search About

alter built-in ContextMenu

Author
28 Dec 2005 11:36 PM
Graham Charles
I'm writing a control inheriting from ComboBox, and I'd like to add an
entry to the ContextMenu for that combo box. I have no problem if I
create an entirely new ContextMenu from scratch, but I'd prefer just to
tack my MenuItem on to the existing, built-in ContextMenu (Cut, Copy,
Paste, etc.):

            _RemoveMRUItemMenu = New MenuItem
            _RemoveMRUItemMenu.Text = "&Remove Item"
            _RemoveMRUItemMenu.Enabled = False

            MyBase.ContextMenu.MenuItems.Add(_RemoveMRUItemMenu)

            AddHandler _RemoveMRUItemMenu.Click, AddressOf
RemoveCurrentMRUItem


This doesn't work; MyBase.ContextMenu returns Nothing, even though
there *is* an existing ContextMenu (the built-in one).

Is the only way to write code to manually perform the usual TextBox
context menu commands? Or can I write a ContextMenu inheriting from
something like "TextBoxContextMenu"?

Thanks,

g.

Author
29 Dec 2005 3:17 AM
Jeffrey Tan[MSFT]
Hi Graham,

Thanks for your post.

No, normally, there is no easy way to get this done. The default context
menu for the combobox/textbox is a win32 build-in context menu, which is
not a .Net ContextMenu class. The only way to get rid of it is supply a
.Net ContextMenu for the Control.ContextMenu property, then the .Net
ContextMenu will replace the default context menu.

I am not sure of why  you do not want to use a new .Net ContextMenu to
substitute the default context menu. Actually, we can implement the default
context menu cut/copy/paste operations in the new .Net ContextMenu without
any problem. Below is a little VB.net sample:
http://www.startvbdotnet.com/controls/contextmenu.aspx

If you have any further concern, please feel free to tell me, I will work
with you. Thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
Author
29 Dec 2005 7:10 AM
Graham Charles
Jeffrey:

To make this approach robust, I had to write a class deriving from
ContextMenu that would:

  1. Override OnPopup in order to selectively enable/disable MenuItems
that aren't usable. (E.g., you can't "cut" or "copy" when there's no
selected text.)

  2. Handle both TextBoxBase controls and ComboBox controls, although
the method for obtaining the handle to both is different.

Now, 200ish lines later (didn't you say this would be simple? :)), I've
got a derived ContextMenu that still has these shortcomings:

  1. It's not generic, requiring different handlers for TextBoxBase and
ComboBox controls (and their derivatives). I'd prefer if this class
were constructed generically (that is, if it could be applied to any
control), but I don't see how to do that, given the various methods for
obtaining the correct handle to the editable portion of the control.

  2. It's not international: I'd prefer to read the values for "Cut,"
and so on, from the system's regional settings, but I've got no idea
where I'd locate those.

Here's where I am so far. Any comments would be lovely.

g.



''' <summary>
''' A context menu (derived from the System.Windows.Forms.ContextMenu
class) that
''' replicates the standard edit context menu controls (Copy, Cut,
Paste, Undo, and Select
''' All) so that the ContextMenu can be expanded upon.
''' </summary>
''' <remarks>
''' The EditContextMenu supports ComboBox and TextBoxBase controls (and
any controls
''' that derive from them).
''' <br>For more information about the development of this class,
''' contact Graham Charles (gra***@aiid.com).
''' </remarks>
''' <example>
'''     To replace a control's default Win32 ContextMenu with this
expandable class, do the
'''     following:
'''     <code lang="VB" title="Constructor example (derived control)"
'''     description="This example demonstrates how to instantiate an
aiEditContextMenu object
''' in a control that derives from ComboBox or TextBoxBase.">
''' Public Class aiComboBox
'''     Inherits System.Windows.Forms.ComboBox
'''
'''     Private myContext As New aiEditContextMenu(Me)
'''
'''     ' ... remainder of code
'''     </code>
'''     <code title="Contstructor example (control on form)">
''' </code>
'''     <code lang="VB" title="Adding Menu Items example"
'''     description="This example demonstrates how to add a MenuItem to
the custom edit
''' context menu.">
''' ' / in declarations section
''' Private myMenuItem As MenuItem
'''
''' ' / in Form_Load or other initialization area
''' If myMenuItem Is Nothing Then
'''     myMenuItem = New MenuItem("My Caption")
'''     myContext.MenuItems.Add(myMenuItem)
'''     AddHandler myMenuItem.Click, AddressOf myMenuItemHandler
''' End If
'''     </code>
''' </example>
Public Class aiEditContextMenu
    Inherits System.Windows.Forms.ContextMenu

#Region "/// Declarations "
    Private Declare Auto Function GetWindow Lib "user32.dll" (ByVal
hwnd As IntPtr, ByVal wCmd As Int32) As IntPtr
    Private Declare Auto Function SendMessage Lib "user32.dll" (ByVal
hwnd As IntPtr, ByVal wMsg As Int32, ByVal wParam As Boolean, ByVal
lParam As Int32) As Int32
    Private Const EM_UNDO = &HC7
    Private Const EM_CANUNDO = &HC6
    Private Const WM_CUT = &H300
    Private Const WM_COPY = &H301
    Private Const WM_PASTE = &H302
    Private Const WM_CLEAR = &H303
    Private Const GW_CHILD As Int32 = 5


    Public MenuItemCopy As New MenuItem("&Copy", New
System.EventHandler(AddressOf MenuCopy))
    Public MenuItemDelete As New MenuItem("&Delete", New
System.EventHandler(AddressOf MenuDelete))
    Public MenuItemPaste As New MenuItem("&Paste", New
System.EventHandler(AddressOf MenuPaste))
    Public MenuItemCut As New MenuItem("Cu&t", New
System.EventHandler(AddressOf MenuCut))
    Public MenuItemUndo As New MenuItem("&Undo", New
System.EventHandler(AddressOf MenuUndo))
    Public MenuItemSelectAll As New MenuItem("Select &All", New
System.EventHandler(AddressOf MenuSelectAll))

    Private _ComboBox As System.Windows.Forms.ComboBox       ' /
ListControl object
    Private _TextBoxBase As System.Windows.Forms.TextBoxBase   ' /
TextBoxBase object

#End Region

#Region "/// Constructors "
    ''' <summary> The constructor can take a ComboBox or TextBoxBase
item as an argument. </summary>
    Public Sub New(ByVal vComboBox As ComboBox)
        _ComboBox = vComboBox
        _ComboBox.ContextMenu = Me
        AttachMenuItems()
    End Sub
    Public Sub New(ByVal vTextBoxBase As TextBoxBase)
        _TextBoxBase = vTextBoxBase
        _TextBoxBase.ContextMenu = Me
        AttachMenuItems()
    End Sub

    Private Sub AttachMenuItems()
        ' / create context menu items to simulate standard ones
        MyBase.MenuItems.Add(MenuItemUndo)
        MyBase.MenuItems.Add("-")
        MyBase.MenuItems.Add(MenuItemCut)
        MyBase.MenuItems.Add(MenuItemCopy)
        MyBase.MenuItems.Add(MenuItemPaste)
        MyBase.MenuItems.Add(MenuItemDelete)
        MyBase.MenuItems.Add("-")
        MyBase.MenuItems.Add(MenuItemSelectAll)
    End Sub

#End Region

#Region "/// Menu Handlers"
    Private Sub MenuCopy(ByVal sender As Object, ByVal e As
System.EventArgs)
        Try
            If Not _ComboBox Is Nothing Then

                If Len(_ComboBox.SelectedText) > 0 Then
                    SendMessage(GetWindow(_ComboBox.Handle, GW_CHILD),
WM_COPY, False, 0)
                    'Clipboard.SetDataObject(_ComboBox.SelectedText)
                End If
            End If
            If Not _TextBoxBase Is Nothing Then _TextBoxBase.Copy()
        Catch ex As Exception
            ' / ignore errors caused by unsupported Control types
        End Try

    End Sub
    Private Sub MenuDelete(ByVal sender As Object, ByVal e As
System.EventArgs)
        Try
            If Not _ComboBox Is Nothing Then
                If Len(_ComboBox.SelectedText) > 0 Then
_ComboBox.SelectedText = String.Empty
            End If
            If Not _TextBoxBase Is Nothing Then
                If Len(_TextBoxBase.SelectedText) > 0 Then
_TextBoxBase.SelectedText = String.Empty
            End If
        Catch ex As Exception
            ' / ignore errors caused by unsupported Control types
        End Try

    End Sub
    Private Sub MenuCut(ByVal sender As Object, ByVal e As
System.EventArgs)
        Try
            If Not _ComboBox Is Nothing Then
                SendMessage(GetWindow(_ComboBox.Handle, GW_CHILD),
WM_CUT, False, 0)
            End If
            If Not _TextBoxBase Is Nothing Then _TextBoxBase.Cut()
        Catch ex As Exception
            ' / ignore errors caused by unsupported Control types
        End Try
    End Sub
    Private Sub MenuPaste(ByVal sender As Object, ByVal e As
System.EventArgs)
        Try
            If Not _ComboBox Is Nothing Then
                If CanPaste() Then
                    _ComboBox.SelectedText =
Clipboard.GetDataObject.GetData(DataFormats.Text)
                End If
            End If
            If Not _TextBoxBase Is Nothing Then _TextBoxBase.Paste()
        Catch ex As Exception
            ' / ignore errors caused by unsupported Control types
        End Try
    End Sub
    Private Sub MenuSelectAll(ByVal sender As Object, ByVal e As
System.EventArgs)
        Try
            If Not _ComboBox Is Nothing Then _ComboBox.SelectAll()
            If Not _TextBoxBase Is Nothing Then
_TextBoxBase.SelectAll()
        Catch ex As Exception
            ' / ignore errors caused by unsupported Control types
        End Try
    End Sub

    Private Sub MenuUndo(ByVal sender As Object, ByVal e As
System.EventArgs)
        Try
            If CanUndo() Then
                If Not _ComboBox Is Nothing Then
SendMessage(GetWindow(_ComboBox.Handle, GW_CHILD), EM_UNDO, False, 0)
                If Not _TextBoxBase Is Nothing Then _TextBoxBase.Undo()
            End If
        Catch ex As Exception
            ' / ignore errors caused by unsupported Control types
        End Try
    End Sub
#End Region

#Region "/// Menu Access Flags"
    Private Function CanPaste() As Boolean
        CanPaste =
Clipboard.GetDataObject.GetDataPresent(DataFormats.Text)
    End Function
    Private Function CanUndo() As Boolean
        If Not _ComboBox Is Nothing Then
            CanUndo = (SendMessage(GetWindow(_ComboBox.Handle,
GW_CHILD), EM_CANUNDO, False, 0) <> 0)
        End If
        If Not _TextBoxBase Is Nothing Then CanUndo =
_TextBoxBase.CanUndo

    End Function

#End Region

#Region "/// Overrides"
    Protected Overrides Sub OnPopup(ByVal e As System.EventArgs)
        Dim bCanCutCopy As Boolean
        Dim bCanSelectAll As Boolean

        ' / enable/disable controls
        If Not _ComboBox Is Nothing Then
            bCanCutCopy = (Len(_ComboBox.SelectedText) > 0)
            bCanSelectAll = (Len(_ComboBox.Text) > 0)
        End If
        If Not _TextBoxBase Is Nothing Then
            bCanCutCopy = (Len(_TextBoxBase.SelectedText) > 0)
            bCanSelectAll = (Len(_TextBoxBase.Text) > 0)
        End If

        MenuItemCopy.Enabled = bCanCutCopy
        MenuItemCut.Enabled = bCanCutCopy
        MenuItemDelete.Enabled = bCanCutCopy
        MenuItemSelectAll.Enabled = bCanSelectAll
        MenuItemPaste.Enabled = CanPaste()
        MenuItemUndo.Enabled = CanUndo()

        MyBase.OnPopup(e)
    End Sub

#End Region

End Class
Author
5 Jan 2006 9:24 AM
Jeffrey Tan[MSFT]
Hi Graham,

Form your email, I see that you still have some concern regarding this
issue. Your concern comment listed below:

"I would be curious, however, if you knew where in the Win32 API I could
extract the labels used for the text box context menu ("Cut", "Copy",
etc.). (Without that, I can't internationalize my code --- but I can't find
it anywhere!)"

I am not sure what do you mean by "extracting the labels used for the text
box context menu", if you want to do globalization to your winform, I think
you should use the .Net Winform localization feature which I provided in
last reply. Does it meet your need? If you missed that point, I will
repaste it here:

#2, .Net winform provided localization feature to resolve this problem. We
can first set Form.Localization property to true in propertybrowser, then
choose different language in the Language property. Then we can store
several versions of culture resource in the application resource file. At
runtime, winform will choose different resource based on the current
culture information. The below link contains several good articles
regarding .Net localization:
http://www.windowsforms.net/Articles/default.aspx?PageID=1&Cat=Localization&
ModuleFilter=131&tabindex=3

If I misunderstand your point, please feel free to show your real concern
to me, I will follow up with you. Thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
Author
9 Jan 2006 6:20 AM
Graham Charles
I don't want to internationalize a Winform -- I'm writing a replacement
ComboBox that extends the ComboBox's ContextMenu. (See my original
message.) Unfortunately, that ContextMenu is provided somewhere below
the CLR -- in the Win32 API, presumably, so that the labels on the
ContextMenu (Cut, Copy, etc.) aren't actually coming from any resource
file; they're coming from the localized version of Windows that the
user has installed. Which is fine, generally speaking, except when I
want to re-create that ContextMenu (Cut, Copy, etc.) but add things to
it. Is there an API call I can make to retrieve these labels? I'm
pretty sure we're talking lower than the CultureInfo class.
Author
10 Jan 2006 7:28 AM
Jeffrey Tan[MSFT]
Hi Graham,

Thanks for your feedback.

No, the default contextmenu is not constituted by labels. It is just
resource for many Win32 controls, this resource is embeded in User32.dll,
you can open this user32.dll in System32 directory with VS.net and see the
contextmenu resource.

I am not sure why original suggestion of using a .Net context menu to
implement the default contextmenu function does not meet your need? Can you
show me your key concern to me? Thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
Author
21 Jan 2006 9:59 PM
Graham Charles
Sure, I'll clarify. Your original suggestion ("using a .Net context
menu to implement the default contextmenu function") works fine, it's
just not nearly as simple as you seemed to think it would be -- that's
why I had to write that longish replacement, as posted.

You'll remember, I wanted to add an item to the default ComboBox
ContextMenu. In other words, I wanted all the usual CTextBox menu items
and behaviors (Copy, Cut, Paste, Undo), as well as my own.
Unfortunately, if you set the ContextMenu property of a ComboBox, you
lose the default menu entirely. So I needed a way to replicate the
"normal" CTextBox context menu in a way that is extensible.

However, with my code, you'll get the English menu item labels ("Copy,
Cut") no matter what the user's copy of Windows would indicate. (In
France, the labels are in French.) So I thought I'd see if there was an
easy way to retrieve those labels from the Windows API. There doesn't
appear to be, as far as I can tell.

Yours,

Graham Charles
Author
25 Jan 2006 10:11 AM
Jeffrey Tan[MSFT]
Hi Graham,

Thanks for your feedback.

The default contextmenu is the resource embeded in user32.dll, and the
common control does not expose a way to get this resource. I think we have
to re-implement the contextmenu to get the similiar function.

For the localization issue, just as I pointed in original reply, .Net
introduced Localization feature, which we can use to show different
language context menu. Thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.