vb.netwinformsgraphicscontextmenucontextmenustrip

Change space between Image and Text in ContextMenuStrip


In my application, I have a ContextMenuStrip with two items. Each item has an image and a text. There's a default gap between the Image section of the Menu Items and their text, as shown in the image below (the gap is indicated by red arrows).

enter image description here

I want to reduce the horizontal gap by moving the text towards the left, so that the gap is reduced to a maximum of 1 pixel.

Is it possible? If yes how can I?


Solution

  • A sample setup that shows how to handle a generic ToolStripProfessionalRenderer and a connected ProfessionalColorTable, used to personalize the rendering and presentation of ToolStrips (MenuStrip, ContextMenuStrip etc.).

    It's organized in different objects with different responsibilities:

    ▶ A Handler class (here, named MenuDesigner) which is responsible for the initialization of the other objects (Renderer, ColorTable and Color definitions).
    It also exposes public properties and methods that allow to customize the rendering and the aspect of the MenuItems.
    It's the only object that consumers should be allowed to interact with.

    ▶ A class object derived from ToolStripProfessionalRenderer (here, named MenuDesignerRenderer), responsible for the rendering of the Menu Items, overriding (partially or completely) the default behavior. In this example (in relation to the question), it overrides OnRenderItemText - to customize the position of the MenuItems Text based on the value of the TextOffset custom property and in relation to the ToolStrip padding - and OnRenderSeparator, which draws the Separator Items, if any, to adjust to the new position of the Items Text.
    The Text offset is set using the MenuDesigner handler's TextOffset Property.

    ▶ A class object derived ProfessionalColorTable (here, named MenuColorTable), which is used to override some or all the default Properties of the Color Table which define the standard Colors of a ToolStrip/MenuStrip, to assign custom colors.

    ▶ A sealed (set to NotInheritable in VB.Net) class (here, named MenuRendererColors) with static (Shared) properties, stores the custom Color definitions, which are then assigned to the different objects and parts described by the ProfessionalColorTable.
    Some of these Colors can be redefined using the MenuDesigner handler.
    In the sample code, TextColor (Color of the Items Text) and SelectionColor (Color of the Items when selected)


    ● The MenuDesigner is initialized specifying, in its Constructor, the ToolStrip to customize - a ContextMenuStrip in this case. The initialization of the MenuDesigner handler also initializes the Renderer and the ColorTable.
    → Of course, any other ProfessonalColorTable derived class can be used instead of the one presented here.
    → The same applies to the class that defines the custom Colors.

    ● Build a ContextMenuStrip (here named MyContextMenuStrip) in the Form Designer, add a private field that references the MenuDesigner and initialize it in the Form constructor, passing the ContextMenuStrip to customize:

    Public Class SomeForm
        Private toolStripDesigner As MenuDesigner = Nothing
    
        Public Sub New()
            InitializeComponent()
            toolStripDesigner = New MenuDesigner(MyContextMenuStrip)
        End Sub
    
       ' [...]
    End Class
    

    To change the position on the MenuItems Text, set a new value to the MenuDesigner.TextOffset Property:

    ' Move the MenuItems Text 8 pixels to the left
    toolStripDesigner.TextOffset = -8
    

    The TextOffset property limits the offset to the range (-8, 30): 8 pixels is the default padding of the Text, it's hard-coded in the ToolStripDropDownMenu class, as other parts of the DropDownMenus.
    These values are scaled when necessary.

    To change the Color of Text and the Background Color of a selected Item, set the corresponding properties exposed by the MenuDesigner class:

    ' Changes the Color of Text of the MenuItems
    toolStripDesigner.TextColor = Color.LightGreen
    
    ' Changes the Background Color of a selected MenuItems 
    toolStripDesigner.SelectionColor = Color.MidnightBlue
    

    More properties or methods can be added to the MenuDesigner handler to change custom Colors or create custom behaviors at run-time.


    This is how it works:

    Custom ToolStripProfessionalRenderer - ProfessionalColorTable


    MenuDesigner class:
    this is the handler class, used to initialize a ToolStripProfessionalRenderer and the related ProfessionalColorTable.
    This object can expose public property and methods that a consumer can set/call to modify settings of the ColorTable and the behavior of the Renderer.

    It should be the only object responsible and allowed to interact with the other (it acts as a proxy -> here all classes are public - it's easier to test - but all should be internal (Friend) or private, depending on the use case).

    Imports System.Drawing
    Imports System.Windows.Forms
    
    Public Class MenuDesigner
        Private m_TextOffset As Integer = 0
    
        Public Sub New(toolStrip As ToolStrip)
            Renderer = New MenuDesignerRenderer()
            If toolStrip IsNot Nothing Then
                Initialize(toolStrip)
            End If
        End Sub
    
        Public Sub Initialize(toolStrip As ToolStrip)
            toolStrip.Renderer = Renderer
        End Sub
    
        Public ReadOnly Property Renderer As MenuDesignerRenderer
    
        Public Property TextColor As Color
            Get
                Return MenuRendererColors.Text
            End Get
            Set
                MenuRendererColors.Text = Value
            End Set
        End Property
    
        Public Property SelectionColor As Color
            Get
                Return MenuRendererColors.Selection
            End Get
            Set
                MenuRendererColors.Selection = Value
            End Set
        End Property
    
        Public Property TextOffset As Integer
            Get
                Return m_TextOffset
            End Get
            Set
                If Value <> m_TextOffset Then
                    m_TextOffset = Math.Min(Math.Max(-8, Value), 30)
                    Renderer.TextOffset = m_TextOffset
                End If
            End Set
        End Property
    End Class
    

    Renderer class (ToolStripProfessionalRenderer):

    These values and positions, e.ToolStrip.Padding.Left - 2, 3, e.Item.Width, 3, are not magic numbers, these are hard-coded values (as it can be seen in the .Net source code linked before) set by the designers of these classes: 2 pixels is a value added to the default padding, 3 is the offset of the Separator line inside its box of 6 pixels in height.

    Imports System.Drawing
    Imports System.Windows.Forms
    
    Public Class MenuDesignerRenderer
        Inherits ToolStripProfessionalRenderer
    
        Private m_colorTable As ProfessionalColorTable = Nothing
    
        Public Sub New()
            Me.New(New MenuColorTable())
        End Sub
    
        Public Sub New(colorTable As ProfessionalColorTable)
            MyBase.New(colorTable)
            m_colorTable = colorTable
        End Sub
    
        Friend Property TextOffset As Integer = 0
    
        Protected Overrides Sub OnRenderItemBackground(e As ToolStripItemRenderEventArgs)
            MyBase.OnRenderItemBackground(e)
            ' Customize when needed
        End Sub
    
        Protected Overrides Sub OnRenderSeparator(e As ToolStripSeparatorRenderEventArgs)
            MyBase.OnRenderSeparator(e)
            Using penForeground As New Pen(m_colorTable.SeparatorDark, 1),
                penBackground As New Pen(e.Item.BackColor, 1)
                e.Graphics.DrawLine(penBackground, e.ToolStrip.Padding.Left - 2, 3, e.Item.Width, 3)
                e.Graphics.DrawLine(penForeground, e.ToolStrip.Padding.Left + TextOffset, 3, e.Item.Width, 3)
            End Using
        End Sub
    
        Protected Overrides Sub OnRenderItemText(e As ToolStripItemTextRenderEventArgs)
            e.Item.ForeColor = MenuRendererColors.Text
            Dim textRect = e.TextRectangle
            textRect.Offset(TextOffset, 0)
            e.TextRectangle = textRect
            MyBase.OnRenderItemText(e)
        End Sub
    End Class
    

    Color Table class (ProfessionalColorTable):
    This class overrides the properties that assign Colors to MenuItems parts to set the custom color defined in our MenuRendererColors class.

    Imports System.Drawing
    Imports System.Windows.Forms
    
    Public Class MenuColorTable
        Inherits ProfessionalColorTable
    
        Public Overrides ReadOnly Property ToolStripBorder As Color = MenuRendererColors.Background
        Public Overrides ReadOnly Property ToolStripGradientBegin As Color = MenuRendererColors.Background
        Public Overrides ReadOnly Property ToolStripGradientEnd As Color = MenuRendererColors.Background
        Public Overrides ReadOnly Property ToolStripDropDownBackground As Color = MenuRendererColors.Background
    
        Public Overrides ReadOnly Property MenuBorder As Color = MenuRendererColors.Background
        Public Overrides ReadOnly Property MenuItemBorder As Color = MenuRendererColors.ImageBand
        Public Overrides ReadOnly Property MenuStripGradientBegin As Color = MenuRendererColors.Background
        Public Overrides ReadOnly Property MenuStripGradientEnd As Color = MenuRendererColors.Background
    
        Public Overrides ReadOnly Property CheckBackground As Color = MenuRendererColors.Background
        Public Overrides ReadOnly Property CheckPressedBackground As Color = MenuRendererColors.Background
        Public Overrides ReadOnly Property CheckSelectedBackground As Color = MenuRendererColors.Background
    
        Public Overrides ReadOnly Property MenuItemSelected As Color = MenuRendererColors.Selection
        Public Overrides ReadOnly Property MenuItemSelectedGradientBegin As Color = MenuRendererColors.Selection
        Public Overrides ReadOnly Property MenuItemSelectedGradientEnd As Color = MenuRendererColors.Selection
        Public Overrides ReadOnly Property MenuItemPressedGradientBegin As Color = MenuRendererColors.Selection
        Public Overrides ReadOnly Property MenuItemPressedGradientEnd As Color = MenuRendererColors.Selection
    
        Public Overrides ReadOnly Property SeparatorDark As Color = MenuRendererColors.SeparatorDark
        Public Overrides ReadOnly Property SeparatorLight As Color = MenuRendererColors.SeparatorLight
    
        Public Overrides ReadOnly Property ImageMarginGradientBegin As Color = MenuRendererColors.ImageBand
        Public Overrides ReadOnly Property ImageMarginGradientMiddle As Color = MenuRendererColors.ImageBand
        Public Overrides ReadOnly Property ImageMarginGradientEnd As Color = MenuRendererColors.ImageBand
        Public Overrides ReadOnly Property ImageMarginRevealedGradientBegin As Color = MenuRendererColors.ImageBand
        Public Overrides ReadOnly Property ImageMarginRevealedGradientMiddle As Color = MenuRendererColors.ImageBand
        Public Overrides ReadOnly Property ImageMarginRevealedGradientEnd As Color = MenuRendererColors.ImageBand
    End Class
    

    Colors definition class:

    Stores the Colors use to customize the aspect of MenusItems. These can be changed at run-time, if needed, using the manager class, MenuDesigner.

    Imports System.Drawing
    
    Public NotInheritable Class MenuRendererColors
        Public Shared Property Text As Color = Color.White
        Public Shared Property Background As Color = Color.FromArgb(32, 32, 32)
        Public Shared Property Selection As Color = Color.FromArgb(200, Color.DodgerBlue)
        Public Shared Property ImageBand As Color = Color.FromArgb(200, 200, 200)
        Public Shared Property CheckBoxBand As Color = Color.FromArgb(200, 200, 200)
        Public Shared Property SeparatorDark As Color = Color.DodgerBlue
        Public Shared Property SeparatorLight As Color = Color.LawnGreen
    End Class