Search code examples
vb.netwinformscontextmenustriptoolstripitem

How to extract all ToolStripMenuItems of a ContextMenuStrip using recursion?


I am tryng to extract all items from a ContextMenuStrip to a List(Of ToolStripMenuItem).
I can get the reference of the ContextMenuStrip of a DataGridView, then - with a recursive function - I want to extract the Name and Text properties of all the ToolStripMenuItems, excluding the ToolStripSeparators, if any.

I use this code:

allItem = New List(Of Control)
Dim lstDbg As List(Of Control) = FindControlRecursive(allItem, form, GetType(DataGridView))

Dim dictRes As New Dictionary(Of String, String)
For Each dbgCtrl As Control In lstDbg
    If dbgCtrl.Name <> "" Then
        For Each mnuStrip As ToolStripItem In dbgCtrl.ContextMenuStrip.Items
            If mnuStrip.GetType() = GetType(ToolStripMenuItem) Then
                Dim lstItem As New List(Of ToolStripMenuItem)
                Dim lstTS As List(Of ToolStripMenuItem) = FindControlRecursive_ContextMenu(lstItem, mnuStrip, GetType(ToolStripMenuItem))
                    For Each item As ToolStripMenuItem In lstTS
                        dictRes.Add(item.Name, item.Text)
                    Next
             End If
        Next
    End If
Next

The function of recursion is FindControlRecursive_ContextMenu():

 Public Function FindControlRecursive_ContextMenu(ByVal list As List(Of ToolStripMenuItem), ByVal parent As ToolStripMenuItem, ByVal ctrlType As System.Type) As List(Of ToolStripMenuItem)
    If parent Is Nothing Then Return list
    If parent.GetType Is ctrlType Then
        list.Add(parent)
    End If
    For Each child As ToolStripMenuItem In parent.DropDown.Items
        FindControlRecursive_ContextMenu(list, child, ctrlType)
    Next
    Return list
End Function

It works, but if in the DropDown list there is a ToolStripSeparator, I don't have the DropDown.Items element and the function generates an exception.

How can I skip the ToolStripSeparators and call the recursive function with the next "child"?


Solution

  • Since only sub-items of type ToolStripMenuItem are needed, you can simply filter the ContextMenuStrip.Items collection beforehand, collecting only Item of that specific type. The Collection.OfType([Type]) method is commonly used for this. OfType() only returns Items of the type specified: ToolStripSeparator is not of that Type, so it won't be included in the filtered collection (nor will other components types such as ToolStripTextBox, ToolStripComboBox etc.).

    The recursive function can be an iterator which returns an IEnumerable(Of ToolStripMenuItem) that yields each item found in a for each loop.

    Call the main method as, e.g.,:
    ► Set Option Strict, Option Explicit and Option Infer to On

     Dim cmsItems As Dictionary(Of String, String) = GetAllContextMenuItems(contextMenuStrip1)
    
    Imports System.Linq
    
    Private Function GetAllContextMenuItems(contextMenu As ContextMenuStrip) As Dictionary(Of String, String)
        If contextMenu Is Nothing OrElse (Not contextMenu.HasChildren) Then Return Nothing
        Dim dict = New Dictionary(Of String, String)
        For Each item As ToolStripMenuItem In contextMenu.Items.OfType(Of ToolStripMenuItem)
            dict.Add(item.Name, item.Text)
            For Each subItem In GetSubMenuItems(item)
                dict.Add(subItem.Name, subItem.Text)
            Next
        Next
        Return dict
    End Function
    
    Private Iterator Function GetSubMenuItems(parent As ToolStripMenuItem) As IEnumerable(Of ToolStripMenuItem)
        For Each item As ToolStripMenuItem In parent.DropDownItems.OfType(Of ToolStripMenuItem)
            If item.HasDropDownItems Then
                For Each subItem As ToolStripMenuItem In GetSubMenuItems(item)
                    Yield subItem
                Next
            Else
                Yield item
            End If
        Next
    End Function