• 0

VB.Net OwnerDrawn menus tutorial


Question

Some of you have asked me how to create your own OwnerDrawn menus.

Since finding info on how to create my own menus was very hard, and I had tried everywhere (maybe i'm just dumb or unlucky..) and found nearly nothing, here is my tutorial. I hope you enjoy it!

This tutorial is NOT complete, so don't complain about it... I expect to complete it today or tomorrow, but decided to post what I already have so you can learn something, search the web or MSDN for it and maybe, who knows, you won't need the rest of the tutorial at all!

Request: Please DO post any errors you have found or any suggestions you have. If you can explain something better than me, please send me a PM with it and I will add it to this post and give you credit for it.

Also, if you want to, post screenshots of your own menus!

So.. here we go!

Understanding how Inheritance works

The first thing you need to understand is how Inheritance works in .Net.

Imagine this. You buy a brand new car. It has everything you need, so you use it as it is. But time goes by and you find you need somethings the car as it is can't give you, maybe you need a radio and a GPS or you feel you need to completely change the looks of the car.

To do this, you have two options:

  • You create your own car from scratch adding exactly what you want and how you want it
  • You take the car you have which already has most of what you need and simply change it

Which option do you think is the best? With the first you have to recreate most of what you already have and which is want you want. The car you have already has seats, wheels, direction, engine, etc. So why recreate all that?

With the second options, however, this is solved. You use want you want from the car, and create ONLY what the car does not give you.

This is what inheritance is all about. You take something with basic functionality and you take it from there, adding or changing things as you please.

Indeed, when you create a new windows form, the first lines of code are

Public Class Form1
    Inherits System.Windows.Forms.Form

The first line says you are creating a new class called Form1, and the second line says that class inherits from System.Windows.Forms.Form. That is to say, the basic funcionality of the form is already there (like the title bar and the minimize, maximize and close buttons and the area where you place your controls) and you just have to build upon it.

The same happens when you create your own menuitem. The basic funcionality, like controling the mouseovers and click and etc are alredy in the Mouseitem class. If you inherit it, you won't have to do it all through Windows APIs and your work is greatly simplified.

I could have easily skipped this and you would not lose much, but I preferred to add it so you can understand better how the whole thing works.

So, the first step to create your own menus is a very simple one: Create a new class and inherit from the MenuItem class.

So, create a Windows Forms project in Visual Basic. Add a new class to it. Finally inherit from the MenuItem class! Simple, isn't it?

You can also create a separate project so you can create a .Net Component and use those menus in different projects, but i'll cover that later on.

So far, your code should be something like

Public Class Menu1
    Inherits System.Windows.Forms.MenuItem

Beeing, of course, Menu1 the name you want to give your own menus.

The basics of Ownerdrawn menus I - OnMeasureItem

Now that your class is created, you need to understand the basics of the Ownerdrawn menus.

One thing can easily see when using any application it that different menus have almost always different heights and different widths. That is to say, a menu with three items has a smaller witdh than a menu with 10 items, and that "this is my very long menu caption" and "file" would produce menus with very different widths.

So, before drawing your menu you have to measure it so that Windows can create the canvas where you will draw.

The measure is done overridding OnMeasureItem.

So, place the following code on your class.

Protected Overrides Sub OnMeasureItem(ByVal e As System.Windows.Forms.MeasureItemEventArgs)

End Sub

(This is the same as chosing "(Overrides)" from the left drop down list and then chosing "OnMeasureItem" from the right drop down list)

As you can see, that sub has one parameter, e. It is through e that the measurement will be made. e has two properties, ItemHeight and ItemWidth, and it is by setting their values that the blank are where you draw will be created. It sums the Height of all the items and uses the width of the longest item.

However, these values will depend of several things, like how you wish to draw your menus (if you wish to add an icon you have to leave space for it, for instance) or the font you use.

I, for one, think it is best to create a Font variable in the class where you set the font you want to use. That way, you only have to change the code on one single place if you wish to change the font later on, and you can even create a property so you can change the code from VS.net IDE or at run time.

To create this variable, just type (outside of any "sub", of course...)

Dim myFont As Font

myFont being the name of the variable. And don't forget to set the font...

Now you need to check the lenght and width of the text using that font. If a font has size 8, it is smaller than a size 20 font.

So, a possibility for this would be

           e.ItemHeight = CInt(e.Graphics.MeasureString(MyBase.Text, myFont, 10000).Height) + 4
            e.ItemWidth = CInt(e.Graphics.MeasureString(MyBase.Text, myFont, 10000).Width) + 5
            If Me.MenuItems.Count > 0 Then e.ItemWidth += 10

The MeasureString method returns the width and heigth of the rectangle containg a specific string, using a specific font. The last line checks if the MenuItem beeing measured has any child menuitems, and if so it adds 10 pixels to its width so there is space for the arrow to be drawn. If you go to the "File" menu in Windows Explorer, you will see that the "New" item has an arrow and that if you hover it, a new menu will appear. This is what I am referring to.

The basics of Ownerdrawn menus II - OnDrawItem

Now that you have the items measured, you need to draw them, which is done overriding the OnDrawItem sub.

So you should add a new sub, as shown below.

   Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)

    End Sub

The drawing is handled one menu item at a time. So, if you have four menu items, for instance, "New", "Open", "Save" and "Close", this method will be called for each and every one of those items as each item has its own rectangle.

Remember the "e" object used in the OnMeasureItem? It will be used here as well, but for a different purpose. Here you use it to retreave the bounds of the item's rectangle and to draw to that rectangle using the "e"'s graphics object.

A simple way to draw the item's text to the rectangle would be

       MyBrush = New SolidBrush(Color.Green)
        e.Graphics.DrawString(Me.Text, Myfont, MyBrush, e.Bounds.X + 2, e.Bounds.Y + 2)

MyBrush is a new global variable I created. It is advisable to create brushes,pens, etc as global variables instead of creating new objects inside the sub.

Are you ready to test your menus? Here we go!

The basics of Ownerdrawn menus III - Using them on a form

Create a new form and add a new menus to it. Now go to code view and change all references to Windows.Forms.Windows.MenuItem to (your name here).

So if your class is named "MyMenu", you would have

   Friend WithEvents MenuItem2 As MyMenu
    Friend WithEvents MenuItem3 As MyMenu
    Friend WithEvents MenuItem4 As MyMenu
(...)
        Me.MenuItem2 = New MyMenu
        Me.MenuItem3 = New MyMenu
        Me.MenuItem4 = New MyMenu

Run your project. Open the menu and... nothing happens!

So what have you done wrong? Absolutely nothing.

You simply have to set the menu's "Ownerdraw" property to true. While this can be done in the IDE one menuitem at a time, there is a more efficient and quick way.

You simply use the "New" sub to set the menu item to Ownerdraw = true and to set a default font (or else the Myfont object is set to nothing and your program will crash).

   Public Sub New()
        MyBase.OwnerDraw = True
        Myfont = New Font("Comic Sans MS", 12)
    End Sub

If you run your program now, the menus will work. However, there are two problems.

  • The menu item displayed in the form is drawn as if it was in the sub menu
  • When the mouse hovers the menu items, they don't change

These are very easy to fix, and that's what we will do now

(to be continued)

Andr? Nogueira

Edited by anog
Link to comment
https://www.neowin.net/forum/topic/194617-vbnet-ownerdrawn-menus-tutorial/
Share on other sites

6 answers to this question

Recommended Posts

  • 0

Hi there! I just got back from vacations :cool:

So, I'd like to know if u want me to continue this tutorial or not! Like, is it good? is it worth reading?

If you've come across a page with a similar but better tutorial, just post it here and I'll link to it.

Thanks!

This topic is now closed to further replies.
  • Recently Browsing   0 members

    • No registered users viewing this page.