Click here to Skip to main content
15,884,237 members
Articles / Programming Languages / XML

Working with OwnerDraw Menus in VB.NET

Rate me:
Please Sign up or sign in to vote.
4.68/5 (19 votes)
17 Nov 2003CPOL3 min read 149.4K   791   51   20
This article describes the processes involved with using owner draw menu items

Introduction

We all like the cool icons that adorn a modern application's menu items. You would have thought that .NET would have directly supported it out of the box. Well it does, sort of.

When a menu is constructed with the VS IDE menu editor, it's very easy to add menu items and set up the hierarchy of the menu structure. Using just a few mouse clicks and a few keystrokes, your whole menu structure can created in just a few minutes. "So", you may ask, "how do we get these cool icons like you promised?"

Background

To use the supplied code, you will need to know how to create menu items with the VS IDE menu editor

Using the Code

When you use the menu editor, you are creating individual MenuItems. As you can guess, MenuItems contains various properties, methods, and events. The primary property we are concerned with is the OwnerDraw Property. The default setting for a MenuItem for OwnerDraw is False. This causes the CLR to draw the text and checks on the item without any intervention, giving you the standard menu look and feel.

Setting the OwnerDraw property to True, however, puts all the control into your hands. When this property is true, the CLR cause two events to be fired - MeasureItem and DrawItem. The help documentation defines these properties as follows:

MeasureItem Occurs when the menu needs to know the size of a menu item before drawing it
DrawItem Occurs when the OwnerDraw property of a menu item is set to true and a request is made to draw the menu item

When the MeasureItem event is fired, you tell the CLR how big your MenuItem should be as follows:

VB
e.ItemWidth = 80
e.ItemHeight = 24    

This would tell the CLR that the menu item is 80 pixels wide and 24 pixels high. This is important, because these are the dimensions used to construct a graphics area for you to draw into in the DrawItem event, demonstrated below:

VB
e.DrawBackground()
Dim TextPoint As PointF = New PointF(18, 0)
e.Graphics.DrawIcon(New Icon(New Icon("c:\icons\myicon.ico"), 16, 16), _
    New Rectangle(0, 0, 16, 16))
e.Graphics.DrawString(sender.Text, MyFont, New SolidBrush(Black), TextPoint)    

The above code will draw the icon on the menu item and print the text defined in your menu. This is a very simple example, but it demonstrates the power of owner draw menus.

Sample Class

The below class takes this example and goes a bit further. Not only does the class draw the menu item, but it also sets the OwnerDraw property and handles the MeasureItem and DrawItem events for you, so that all you have to do is add a menu item to the class and it does all the work. The class creates an ArrayList and populates it with items of the LocalMenuItem structure type. This makes it easier to identify and work with the menu items.

Several Add Method constructors are provided which construct and add the individual LocalMenuItem items to the ArrayList. Some things are vitally important when adding items to the class:

  • All MenuItems should be added - the total menu will not be drawn correctly if items are skipped
  • The MenuItemNumber parameter determines the position of the item in the menu, starting at 0 - these must be sequential as this is used to determine the position and graphics area of the MenuItem
  • The IsSeparator parameter flags the menu item as a separator
  • Pass Nothing as the icon if the item is a separator or you don't want an icon drawn for the item
  • Quick access keys (mnemonics) are not supported with the sample

The class only handles vertical (sub menu) items and does not handle the MainMenu items, although it could be modified to draw these also. It also handles separators properly.

VB
Imports System.Drawing
Imports System.Windows.Forms
Public Class OwnerDrawMenu
  'holds menu items
    Private _MenuItems As New ArrayList()
    'default font
    Private _Font As Font = SystemInformation.MenuFont
    'default text color
    Private _TextColor As Color = System.Drawing.SystemColors.MenuText
    'constants
    Private Const NORMALITEMHEIGHT As Integer = 20
    Private Const SEPITEMHEIGHT As Integer = 12
    Private Const EXTRAWIDTH As Integer = 30
    Private Const ICONSIZE16 As Integer = 16

  'structure to hold menu items
    Private Structure LocalMenuItem
        Dim MenuItemNumber As Integer
        Dim MenuItem As Windows.Forms.MenuItem
        Dim Icon As System.Drawing.Icon
        Dim IconRectangle As System.Drawing.Rectangle
        Dim TextLeft As Integer
        Dim TextTopPosition As Integer
        Dim Font As System.Drawing.Font
        Dim TextColor As System.Drawing.Color
        Dim Height As Integer
        Dim Width As Integer
        Dim IsSeperator As Boolean
    End Structure

    Public Sub New()
        '
    End Sub
    'various constructors for the class
    Public Sub New(ByVal Font As System.Drawing.Font)
        _Font = Font
        If _Font.Size > 12 Then
            _Font = New Font(_Font.Name, 12, _Font.Style)
        End If
    End Sub
    Public Sub New(ByVal TextColor As System.Drawing.Color)
        _TextColor = TextColor
    End Sub
    Public Sub New(ByVal Font As System.Drawing.Font, _
      ByVal TextColor As System.Drawing.Color)
        _TextColor = TextColor
        _Font = Font
        If _Font.Size > 12 Then
            _Font = New Font(_Font.Name, 12, _Font.Style)
        End If
    End Sub
  'various constructors for the add method
    Public Sub Add(ByVal MenuItem As Windows.Forms.MenuItem, _
      ByVal Icon As System.Drawing.Icon, _
      ByVal MenuItemNumber As Integer, _
      ByVal IsSeperator As Boolean)
        Me.Add(MenuItem, Icon, MenuItemNumber, IsSeperator, _Font, _TextColor)
    End Sub
    Public Sub Add(ByVal MenuItem As Windows.Forms.MenuItem, _
      ByVal Icon As System.Drawing.Icon, _
      ByVal MenuItemNumber As Integer, _
      ByVal IsSeperator As Boolean, _
      ByVal Font As System.Drawing.Font)
        Me.Add(MenuItem, Icon, MenuItemNumber, IsSeperator, Font, _TextColor)
    End Sub
    Public Sub Add(ByVal MenuItem As Windows.Forms.MenuItem, _
      ByVal Icon As System.Drawing.Icon, _
      ByVal MenuItemNumber As Integer, _
      ByVal IsSeperator As Boolean, _
      ByVal Font As System.Drawing.Font, _
      ByVal TextColor As System.Drawing.Color)
    'hold and save the last top and left position
        Static LastTopPosition As Integer
        Static LastLeftPosition As Integer
        Dim li As New LocalMenuItem()
        If MenuItemNumber = 0 Then
            LastLeftPosition = 2
            LastTopPosition = 0
        Else
      'calculate the new top position
            LastTopPosition = LastTopPosition + IIf(IsSeperator, _
          SEPITEMHEIGHT, NORMALITEMHEIGHT)
            LastLeftPosition = 2
        End If
        Const ICONWIDTH As Integer = ICONSIZE16
        Const ICONHEIGHT As Integer = ICONSIZE16
        Dim IconRect As Rectangle
        'calculate new drawing rectangle for icon
        If IsSeperator Then
            IconRect = New Rectangle(LastLeftPosition, LastTopPosition, _
          ICONWIDTH, ICONHEIGHT)
        Else
            IconRect = New Rectangle(LastLeftPosition, LastTopPosition + 2, _
          ICONWIDTH, ICONHEIGHT)
        End If
        'you don't need to set ownerdraw - the class does it for you
        MenuItem.OwnerDraw = True
        With li
            .MenuItemNumber = MenuItemNumber
            .Font = Font
            .MenuItem = MenuItem
            .Icon = Icon
            .TextLeft = LastLeftPosition + ICONWIDTH
            .TextTopPosition = LastTopPosition
            .IconRectangle = IconRect
            .TextColor = TextColor
            .IsSeperator = IsSeperator
        End With
        _MenuItems.Add(li)
        'set the handlers for the menuitems
        AddHandler MenuItem.DrawItem, AddressOf Me.DrawItemHandler
        AddHandler MenuItem.MeasureItem, AddressOf Me.MesaureItemHandler
    End Sub

    Private Sub DoDraw(ByVal LI As LocalMenuItem, _
      ByRef e As System.Windows.Forms.DrawItemEventArgs)
        e.DrawBackground()
        Const LastLeftPosition As Integer = 2
        Const ICONWIDTH As Integer = ICONSIZE16
        Dim ThisMenuItem As MenuItem = LI.MenuItem
        Dim MenuItemGraphics As Graphics = e.Graphics
        Dim bBypassString As Boolean
        'set size and textpoint for our text
        Dim SizeF As SizeF = e.Graphics.MeasureString(LI.MenuItem.Text, _Font)
        Dim TextPoint As PointF = New PointF(LI.TextLeft, _
        LI.TextTopPosition + ((NORMALITEMHEIGHT - SizeF.Height) / 2))
        Dim RectHeight As Integer = SizeF.Height
        If Not LI.Icon Is Nothing Then
      'draw the icon
            MenuItemGraphics.DrawIcon(New Icon(LI.Icon, _
          ICONSIZE16, ICONSIZE16), LI.IconRectangle)
        ElseIf LI.IsSeperator Then
      'draw the separator
            MenuItemGraphics.DrawLine(New Pen(LI.TextColor, 1), _
          TextPoint.X, TextPoint.Y + 11, _
          TextPoint.X + LI.Width + EXTRAWIDTH, TextPoint.Y + 11)
            bBypassString = True
        End If
        If Not bBypassString Then
      'bypass string if separator
      'draw differently if enabled/dsabled
            If LI.MenuItem.Enabled Then
                MenuItemGraphics.DrawString(Replace(LI.MenuItem.Text, "&", ""), _
          LI.Font, New SolidBrush(LI.TextColor), TextPoint)
            Else
                MenuItemGraphics.DrawString(Replace(LI.MenuItem.Text, "&", ""), _
          LI.Font, New SolidBrush(Drawing.SystemColors.GrayText), TextPoint)
            End If
        End If
    End Sub
    Private Sub DoMeasure(ByVal LI As LocalMenuItem, _
      ByRef e As System.Windows.Forms.MeasureItemEventArgs)
    'calculate the size of the drawing area
        Dim ThisMenuItem_Strings As String() = LI.MenuItem.Text.Split(",")
        Dim TextSize As SizeF = e.Graphics.MeasureString( _
        ThisMenuItem_Strings(0).Replace("&", ""), LI.Font)
        e.ItemWidth = TextSize.Width + EXTRAWIDTH
        If LI.MenuItem.Text = "-" Then
            e.ItemHeight = SEPITEMHEIGHT
        Else
            e.ItemHeight = NORMALITEMHEIGHT
        End If
        LI.Height = e.ItemHeight
        LI.Width = e.ItemWidth
    End Sub
    Public Sub DrawItemHandler(ByVal sender As Object, _
      ByVal e As System.Windows.Forms.DrawItemEventArgs)
    'look through the items and find out which one we are drawing
        Dim li As LocalMenuItem
        For Each li In _MenuItems
            If li.MenuItem Is sender Then
                DoDraw(li, e)
                Exit For
            End If
        Next
    End Sub
    Public Sub MesaureItemHandler(ByVal sender As Object, _
      ByVal e As System.Windows.Forms.MeasureItemEventArgs)
    'look through the items and find out which one we are measuring
        Dim li As LocalMenuItem
        For Each li In _MenuItems
            If li.MenuItem Is sender Then
                DoMeasure(li, e)
                Exit For
            End If
        Next
    End Sub
End Class

After adding a MainMenu control, add a few menu items. Remember, the class handles setting the OwnerDraw Property for you and also handles the MeasureItem and DrawItem events so you need do nothing else but add it to the class. To do this, in the Form_Load event, add the following code:

VB
Dim odm As New ODM.OwnerDrawMenu(New Font("Tahoma", 8), System.Drawing.Color.Red)
odm.Add(Me.MenuItem3, New Icon("new.ico"), 0, False)
odm.Add(Me.MenuItem2, New Icon("open.ico"), 1, False)
odm.Add(Me.MenuItem4, Nothing, 2, False)
odm.Add(Me.MenuItem5, Nothing, 3, True)
odm.Add(Me.MenuItem6, Nothing, 4, False)    

That's all there is to it! You now have those "cool" icons.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Visual Basic Developer since version 1.0
Java web developer
Currently developing in vb and c#

Comments and Discussions

 
GeneralIcons appear offset vertically in other columns Pin
malcomm30-Jan-07 15:58
malcomm30-Jan-07 15:58 
Questionhow to change the menu bar color in vb.net 2003 Pin
Rmesh20-Dec-06 1:37
Rmesh20-Dec-06 1:37 
QuestionUpgrading this capability in a ToolStripMenuItem Pin
GenoJoeInOhio25-Sep-06 15:31
GenoJoeInOhio25-Sep-06 15:31 
AnswerRe: Upgrading this capability in a ToolStripMenuItem Pin
Peter Ritchie30-Nov-06 6:52
Peter Ritchie30-Nov-06 6:52 
GeneralExecute a command in menu item [modified] Pin
abbasy2-Jul-06 16:56
abbasy2-Jul-06 16:56 
AnswerRe: Execute a command in menu item Pin
Greg Osborne3-Jul-06 4:42
Greg Osborne3-Jul-06 4:42 
GeneralRe: Execute a command in menu item Pin
abbasy3-Jul-06 20:53
abbasy3-Jul-06 20:53 
AnswerRe: Execute a command in menu item Pin
Greg Osborne5-Jul-06 4:00
Greg Osborne5-Jul-06 4:00 
Questionright to left Pin
@amino10-Jan-06 9:55
@amino10-Jan-06 9:55 
GeneralShortcuts Pin
Jan Vingaard16-Aug-05 12:53
Jan Vingaard16-Aug-05 12:53 
GeneralRe: Shortcuts Pin
Greg Osborne17-Aug-05 6:24
Greg Osborne17-Aug-05 6:24 
GeneralRe: Shortcuts Pin
Jan Vingaard17-Aug-05 8:38
Jan Vingaard17-Aug-05 8:38 
GeneralUnable to Compile Project Pin
damian_jay30-May-05 23:31
damian_jay30-May-05 23:31 
GeneralRe: Unable to Compile Project Pin
Greg Osborne31-May-05 3:41
Greg Osborne31-May-05 3:41 
GeneralCompiled Project! Pin
damian_jay31-May-05 22:58
damian_jay31-May-05 22:58 
QuestionWhy no standart separator-line? Pin
dartrax25-Mar-05 3:53
dartrax25-Mar-05 3:53 
QuestionDefault Grey Background? Pin
waughp19-Feb-05 9:09
waughp19-Feb-05 9:09 
AnswerRe: Default Grey Background? Pin
GregOsborne21-Feb-05 4:18
GregOsborne21-Feb-05 4:18 
GeneralOut Of Memory Error Pin
mtone7-Jul-04 2:36
mtone7-Jul-04 2:36 
Every time I try to run your code I get an Out of memory error when I click the menu item File. Any Ideas?

Thanks for the Code
Generalcustom look to the MainMenu Pin
Elagorn18-Jan-04 12:58
Elagorn18-Jan-04 12:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.