Dropdown Menu – Custom ContextMenuStrip (Multilevel) – C# & WinForms

Hola, en este tutorial aprenderá a crear un menú desplegable (ContextMenuStrip personalizado) con 2 estilos de diseño:

  • Estilo principal, tiene un fondo oscuro y de mayor dimensión, se puede personalizar el color del texto y el color del estilo (Primario), lo que afecta el color del icono y el elemento seleccionado.
  • Estilo secundario, tiene un fondo claro, que se puede utilizar en el contenido de los formularios, se puede personalizar el color del texto y el color primario, que afecta al icono y al elemento seleccionado.

Para crear el menú desplegable o una franja de menú contextual personalizada, es necesario 3 componentes:

Dropdown Menu - Custom ContextMenuStrip
  1. Una clase que hereda de la clase ContextMenuStrip, un control tradicional de la librería de Windows Forms.
  2. Luego, a través de la propiedad Renderer, establecer un renderizador personalizado para personalizar la apariencia del menú. Para ello, la clase debe heredar de la clase TooStripProfessionalRenderer.
  3. Finalmente, heredar la clase ProfessionalColorTable y crear una instancia de la tabla de colores personalizada en el parámetro del constructor de la clase base de la clase anterior (Custom renderer), de esa manera modificar la pintura del menú y los elementos.

Código

Clase MenuColorTable (ProfessionalColorTable)

public class MenuColorTable : ProfessionalColorTable
{
    //Fields
    private Color backColor;
    private Color leftColumnColor;
    private Color borderColor;
    private Color menuItemBorderColor;
    private Color menuItemSelectedColor;

    //Constructor
    public MenuColorTable(bool isMainMenu, Color primaryColor)
    {
        if (isMainMenu)
        {
            backColor = Color.FromArgb(37, 39, 60);
            leftColumnColor = Color.FromArgb(32, 33, 51);
            borderColor = Color.FromArgb(32, 33, 51);
            menuItemBorderColor = primaryColor;
            menuItemSelectedColor = primaryColor;
        }
        else
        {
            backColor = Color.White;
            leftColumnColor = Color.LightGray;
            borderColor = Color.LightGray;
            menuItemBorderColor = primaryColor;
            menuItemSelectedColor = primaryColor;
        }
    }

    //Overrides
    public override Color ToolStripDropDownBackground { get { return backColor; } }
    public override Color MenuBorder { get { return borderColor; } }
    public override Color MenuItemBorder { get { return menuItemBorderColor; } }
    public override Color MenuItemSelected { get { return menuItemSelectedColor; } }
    public override Color ImageMarginGradientBegin { get { return leftColumnColor; } }
    public override Color ImageMarginGradientMiddle { get { return leftColumnColor; } }
    public override Color ImageMarginGradientEnd { get { return leftColumnColor; } }
}

Clase MenuRenderer (ToolStripProfessionalRenderer)

public class MenuRenderer : ToolStripProfessionalRenderer
{
    //Fields
    private Color primaryColor;
    private Color textColor;
    private int arrowThickness;

    //Constructor
    public MenuRenderer(bool isMainMenu, Color primaryColor, Color textColor)
        : base(new MenuColorTable(isMainMenu, primaryColor))
    {
        this.primaryColor = primaryColor;
        if (isMainMenu)
        {
            arrowThickness = 3;
            if (textColor == Color.Empty) //Set Default Color
                this.textColor = Color.Gainsboro;
            else//Set custom text color 
                this.textColor = textColor;
        }
        else
        {
            arrowThickness = 2;
            if (textColor == Color.Empty) //Set Default Color
                this.textColor = Color.DimGray;
            else//Set custom text color
                this.textColor = textColor;
        }
    }

    //Overrides
    protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
    {
        base.OnRenderItemText(e);
        e.Item.ForeColor = e.Item.Selected ? Color.White : textColor;
    }

    protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
    {
        //Fields
        var graph = e.Graphics;
        var arrowSize = new Size(5, 12);
        var arrowColor = e.Item.Selected ? Color.White : primaryColor;
        var rect = new Rectangle(e.ArrowRectangle.Location.X, (e.ArrowRectangle.Height - arrowSize.Height) / 2,
            arrowSize.Width, arrowSize.Height);
        using (GraphicsPath path = new GraphicsPath())
        using (Pen pen = new Pen(arrowColor, arrowThickness))
        {
            //Drawing
            graph.SmoothingMode = SmoothingMode.AntiAlias;
            path.AddLine(rect.Left, rect.Top, rect.Right, rect.Top + rect.Height / 2);
            path.AddLine(rect.Right, rect.Top + rect.Height / 2, rect.Left, rect.Top + rect.Height);
            graph.DrawPath(pen, path);
        }
    }

}

Clase MenuRenderer (ToolStripProfessionalRenderer)

public class RJDropdownMenu : ContextMenuStrip
{
    //Fields
    private bool isMainMenu;
    private int menuItemHeight = 25;
    private Color menuItemTextColor = Color.Empty;//No color, The default color is set in the MenuRenderer class
    private Color primaryColor = Color.Empty;//No color, The default color is set in the MenuRenderer class

    private Bitmap menuItemHeaderSize;

    //Constructor
    public RJDropdownMenu(IContainer container)
        : base(container)
    {

    }

    //Properties
    //Optionally, hide the properties in the toolbox to avoid the problem of displaying and/or 
    //saving control property changes in the designer at design time in Visual Studio.
    //If the problem I mention does not occur you can expose the properties and manipulate them from the toolbox.
    [Browsable(false)]
    public bool IsMainMenu
    {
        get { return isMainMenu; }
        set { isMainMenu = value; }
    }

    [Browsable(false)]
    public int MenuItemHeight
    {
        get { return menuItemHeight; }
        set { menuItemHeight = value; }
    }

    [Browsable(false)]
    public Color MenuItemTextColor
    {
        get { return menuItemTextColor; }
        set { menuItemTextColor = value; }
    }

    [Browsable(false)]
    public Color PrimaryColor
    {
        get { return primaryColor; }
        set { primaryColor = value; }
    }

    //Private methods
    private void LoadMenuItemHeight()
    {
        if (isMainMenu)
            menuItemHeaderSize = new Bitmap(25, 45);
        else menuItemHeaderSize = new Bitmap(20, menuItemHeight);

        foreach (ToolStripMenuItem menuItemL1 in this.Items)
        {
            menuItemL1.ImageScaling = ToolStripItemImageScaling.None;
            if (menuItemL1.Image == null) menuItemL1.Image = menuItemHeaderSize;

            foreach (ToolStripMenuItem menuItemL2 in menuItemL1.DropDownItems)
            {
                menuItemL2.ImageScaling = ToolStripItemImageScaling.None;
                if (menuItemL2.Image == null) menuItemL2.Image = menuItemHeaderSize;

                foreach (ToolStripMenuItem menuItemL3 in menuItemL2.DropDownItems)
                {
                    menuItemL3.ImageScaling = ToolStripItemImageScaling.None;
                    if (menuItemL3.Image == null) menuItemL3.Image = menuItemHeaderSize;

                    foreach (ToolStripMenuItem menuItemL4 in menuItemL3.DropDownItems)
                    {
                        menuItemL4.ImageScaling = ToolStripItemImageScaling.None;
                        if (menuItemL4.Image == null) menuItemL4.Image = menuItemHeaderSize;
                        ///Level 5++
                    }
                }
            }
        }
    }

    //Overrides
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        if (this.DesignMode == false)
        {
            this.Renderer = new MenuRenderer(isMainMenu, primaryColor, menuItemTextColor);
            LoadMenuItemHeight();
        }
    }
}

Video tutorial