stigzler
stigzler

Reputation: 993

Getting all ContextMenuStrips

I have a UserControl which has several ContextMenuStrips add via VS Designer. They are not assigned at design time to any controls, because they are dynamically assigned to one "dropdown" button which is context sensative.I have a Class that iterates through all the controls within a control to theme those controls (i.e. dark mode). The code:

Private ControlList As List(Of Control) = New List(Of Control)()
Private Sub GetAllControls(ByVal container As Control)
    For Each c As Control In container.Controls
        GetAllControls(c)
        ControlList.Add(c)
    Next
End Sub

ContextMenuStrips are not detected. From reading, I understand that these are not a Control, but a Component. I tried one suggested solution via:

Private Sub GetAllComponents(container As Object)
    For Each co As System.ComponentModel.Component In container.components.Components
        If TypeOf co Is ContextMenuStrip Then
            ControlList.Add(co)
        End If
    Next
End Sub

However I get an error at run-time:

System.MissingMemberException: 'Public member 'components' on type 'ObjectSelector' not found.'

ObjectSelector is the UserControl.

The theming Class is used by lots of other objects (forms, usercontrols) so it is difficult to have a specific property for this one UserControl. How can I get a list of all the ContextMenuStrips on a UserControl?

Upvotes: 1

Views: 143

Answers (2)

Reza Aghaei
Reza Aghaei

Reputation: 125312

Note: A totally different approach, so I'll post it as another answer. It may be more interesting for some users; however this one is also a hacky way.


The other solution relies on components private member of the controls to get all the components recursively, however if you are just focusing on getting ToolStrip and all its descendants, here is another option.

Getting all ToolStrip, ContextMenuStrip, MenuStrip and StatusStrip in the app

The ToolStripManager has an internal property, ToolStrips, which keeps a reference to all ToolStrip instances (including ToolStrip, MenuStrip, ContextMenuStrip, StatusStrip) that you have in your application. In fact when you create an instance of those components, they add themselves to the toolstrips collection of the toolstrip manager. You can get all tool strips, context menu strips, menu strips and status strips using reflection using that property.

Here is the code:

public static IEnumerable<ToolStrip> ToolStrips()
{
    var toolStrips = (typeof(ToolStripManager).GetProperty("ToolStrips",
            System.Reflection.BindingFlags.Static |
            System.Reflection.BindingFlags.NonPublic)
            .GetValue(null, null) as System.Collections.IList);
    for (int i = 0; i < toolStrips.Count; i++)
        yield return toolStrips[i] as ToolStrip;
}

And to limit it to the context menu strips:

var ctxMenuStrips = ToolStrips().OfTypeOf<ContextMenuStrip>();

Upvotes: 0

Reza Aghaei
Reza Aghaei

Reputation: 125312

When you use designer and drop a component on the form, the designer creates a components private member field form the form. To get that member you need to rely on reflection.

Before I post the code I'd like to highlight the following points:

  • The right way of changing theme for ContextMenuStrip, ToolStrip, MenuStrip and StatusStrip is assigning a themed renderer to ToolStripManager.Renderer.
  • Another idea for a theme manager for all the controls is, creating an extender provider which detects all the controls and component at design time and assign their properties or handles their events to change their theme.
  • When you get all the controls and component in run-time, there might be some unwanted controls and components in the result; for example the tool strip of a property grid or the updown buttons of a numeric updown.

Get All descendant components of a control (recursively)

Anyways, I'll post an answer for learning purpose to show you how you can get a list of all components on a control and it's children. To so, I'll create an extension method which you can use like this:

var ctxMenuStrips = this.FindForm().AllComponents().OfTypeOf<ContextMenuStrip>();

Here's the code of the extension method:

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
public static class ControlExtensions
{
    public static IEnumerable<Component> AllComponents(this Control control)
    {
        var componentsFields = control.GetType().GetField("components",
            System.Reflection.BindingFlags.NonPublic |
            System.Reflection.BindingFlags.Instance);
        var components = componentsFields?.GetValue(control) as IContainer;
        if (components != null)
            foreach (Component component in components.Components)
                yield return component;

        foreach (Control child in control.Controls)
            foreach (Component c in child.AllComponents())
                yield return c;
    }
    public static IEnumerable<Control> AllControls(this Control control)
    {
        foreach (Control c in control.Controls)
        {
            yield return c;
            foreach (Control child in c.AllControls())
                yield return child;
        }
    }
}

Upvotes: 1

Related Questions