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).
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?
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:
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