2

I have a Form that has a menu called MenuEdit, with a ToolStripMenuItem called MenuEditElement inside. When I right click on a listview, I want to create a Context Menu dynamically, containing EditElement, among other things. I'm doing it like this:

        Dim CM As New ContextMenuStrip
        Dim Submenu As ToolStripMenuItem = CM.Items.Add("New", ImageHolder.Images("New"))
        CM.Items.Add(New ToolStripSeparator)
        CM.Items.Add(MenuEditElement)
        CM.Show(ListView, e.Location)

The problem is that, right after CM.Items.Add(MenuEditElement), MenuEditElement disappears from MenuEdit, as if it had been removed from there to be added to the context menu. Is there another way to do this?

I wouldn't want to be creating an identical menu to MenuEditItem, or to clone it. This is because MenuEditItem has 5 subitems, so I would have to create those too, along with attaching their handlers.

I'm using Framework 4.0.

Pona
  • 167
  • 1
  • 17
  • For completeness sake, I'm commenting to state that the part when I say that I don't want to create new subitems was added in the edit, so @Jens' answer below is relevant – Pona Nov 14 '14 at 17:08

3 Answers3

0

A menuitem object can only be an item of one menu at a time. By calling the Add function you assign the menuitem object to another menu. You are not creating a new object.

The Add method of the ToolStripItemCollection calls the ToolStripItemCollection.SetOwner method to replace the last owner of the toolstripitem (your menustrip) with the new owner (your contextmenu)

From System.Windows.Forms:

    Public Function Add(value As ToolStripItem) As Integer
        Me.CheckCanAddOrInsertItem(value)
        Me.SetOwner(value)
        Dim result As Integer = AddressOf MyBase.InnerList.Add(value)
        If Me.itemsCollection AndAlso Me.owner IsNot Nothing Then
            Me.owner.OnItemAdded(New ToolStripItemEventArgs(value))
        End If
        Return result
    End Function
    Private Sub SetOwner(item As ToolStripItem)
        If Me.itemsCollection AndAlso item IsNot Nothing Then
            If AddressOf item.Owner IsNot Nothing Then
                AddressOf AddressOf item.Owner.Items.Remove(item)
            End If
            item.SetOwner(Me.owner)
            If AddressOf item.Renderer IsNot Nothing Then
                AddressOf item.Renderer.InitializeItem(item)
            End If
        End If
    End Sub

You can see that the item is removed from the last menu and added the new one.

So what can you do?

ToolStripMenuItem does not implement IClonable.

What you need to do is instantiate a new object

Dim NewItem as New ToolStripMenuItem
With NewItem
   .Text = MenuEditElement.Text
   .Image = MenuEditElement.Image
   'Rinse repeat for other important properties
End With

Now you also need to wire up the events

AddHandler NewItem.Click, AddressOf HandleEditClicked 'Replace with the method that handles MenuEditItem.Clicked

Then add this item to your context menu instead of the original item

CM.Items.Add(NewItem)

This will create a new item that looks and works the same way as MenuEditItem and this is then placed in the ContextMenuStrip

Jens
  • 6,275
  • 2
  • 25
  • 51
  • Thanks, but this wouldn't actually work for me, because MenuEditItem has 5 subitems, so I would have to manually create those too. I will edit the original post to add this information. It's good to know that a MenuItem can't belong to more than one menu at a time, though, so thank you. For the moment I will try to implement an extension method to ToolStripItem to make it able to clone itself, along with all its subitems (and handlers), though it seems like a lot of work for such a simple problem. – Pona Jul 16 '14 at 18:27
  • Of course, the actual implementation would look differently (and an extension method is an elegant way to do it) but you got the idea. – Jens Jul 16 '14 at 18:43
  • Since there were no other answers, I'm marking this as correct, as it did help me with the issue. Thanks! – Pona Jul 21 '14 at 18:24
  • Don't really know why this answer was marked as unuseful, since it actually was (at least to me, the OP)... – Pona Nov 14 '14 at 17:05
0

Just spent some hours to find out how to solve this problem in an elegant, simple and efficient way:

Requires (e.g.): aboutToolStripMenuItem as ToolStripMenuItem

  1. Create a new ContextMenuStrip:

    ContextMenuStrip ctx = new ContextMenuStrip();

  2. Create a new ToolStripItem[] with the number of the original ToolStripMenuItem items

    ToolStripItem[] itmArray = new ToolStripItem[aboutToolStripMenuItem.DropDownItems.Count];

  3. Copy ToolStripMenuItem items to the new ToolStripItem[]

    aboutToolStripMenuItem.DropDownItems.CopyTo(itmArray, 0);

  4. Finally add the ToolStripItem[] to the ContextMenuStrip and show it at MousePosition

    ctx.Items.AddRange(itmArray); ctx.Show(MousePosition);

  5. Do not forget to put the items back to the original menu item (register Closed-Event of the context menu):

    ctx.Closed += ctx_Closed;

    void ctx_Closed(object sender, ToolStripDropDownClosedEventArgs e) { aboutToolStripMenuItem.DropDownItems.AddRange(itmArray); }

Now all together:

ToolStripItem[] itmArray;

private void dataGridView_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
  if (e.Button == System.Windows.Forms.MouseButtons.Right)
  {
    itmArray = new ToolStripItem[aboutToolStripMenuItem.DropDownItems.Count];
    aboutToolStripMenuItem.DropDownItems.CopyTo(itmArray, 0);

    ContextMenuStrip ctx = new ContextMenuStrip();
    ctx.Closed += ctx_Closed;
    ctx.Items.AddRange(itmArray);
    ctx.Show(MousePosition);
  }
}

void ctx_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
  aboutToolStripMenuItem.DropDownItems.AddRange(itmArray);
}
swissben
  • 1,059
  • 8
  • 13
  • This is a great approach! I don't quite understand step 5, though. Why do you need to copy the items back to _aboutToolStripMenuItem_? Is it because _CopyTo_ has the same effect as the one I describe in the question (the one that makes it disappear from its original menu)? – Pona Nov 14 '14 at 17:03
0

I had a similar problem, and having read much of the useful stuff here, this seems to work pretty well.

Private Sub PopulateContextMenu()
        ContextMenuStrip1.Items.Clear()
        For Each m In MenuStrip2.Items
            Dim clone As New ToolStripMenuItem
            clone = CloneMenu(m)
            If clone IsNot Nothing Then
                ContextMenuStrip1.Items.Add(clone)
            End If
        Next
    End Sub 
Friend Function CloneMenu(m As ToolStripMenuItem) As ToolStripMenuItem
    Dim newitem As New ToolStripMenuItem()
    With newitem
        .Name = m.Name
        .Text = m.Text
        .ToolTipText = m.ToolTipText
        .Enabled = m.Enabled
        'Add any other things you want to preserve, like .Image
        'Not much point preserving Keyboard Shortcuts on a context menu
        'as it's designed for a mouse click.
        If m.HasDropDownItems Then
            For Each sm In m.DropDownItems
                If TypeOf (sm) Is ToolStripMenuItem Then
                    newitem.DropDownItems.Add(CloneMenu(sm))
                End If
            Next
        End If
    End With
    AddHandler newitem.Click, AddressOf m.PerformClick
    Return newitem
End Function