View Single Post
 
Old 07-24-2005, 09:09 AM
Iceplug's Avatar
Iceplug Iceplug is offline
MetaCenturion

Retired Moderator
* Guru *
 
Join Date: Aug 2001
Location: Iowa, USA
Posts: 16,583
Thumbs up Using OwnerDraw For Good-Looking Controls

OwnerDrawing allows you to draw the graphics of a particular control yourself. This opens up a lot of opportunities for expressing your artistic mind. Are you tired of that boring old menu-colored menu? Spice it up with a bright new yellow color or a hot new blue. Want to add a little something to that listbox? OwnerDraw it?

First, we're going to look at OwnerDraw on Menu controls.

First, add a MainMenu component to your form, and change its name to something besides MainMenu1.
Now add one menu header to it... let's call it FileMenu and set the caption to be File (no '&' yet).
Next, let's set the OwnerDraw property for FileMenu to True.
When you run your program now, you won't see the menu, because it is up to you to draw it.

Beginning OwnerDraw
To do OwnerDraw for this menu, we go to the events for this FileMenu.
The first event to use is the MeasureItem event.
The MeasureItemEventArgs has only 4 properties - we're only going to look at one of them for now... that's the .ItemWidth. (ItemHeight is fixed, nothing will happen)
We will set this value to the width that the want our menu canvas to be, and after this event is complete, the menu will be created to this size.

After we've set those, it's time to move on to the DrawItem event. This is, of course, where you'll be doing your drawing.
The DrawItemEventArgs are more robust (slightly).

You can add e.DrawBackground to get just a simple menu interfacing - white when not selected and the selected color when selected.

After our background is drawn, we can draw the menu text with .DrawString; the graphics object is available at e.Graphics.
The string can be taken from either FileMenu.Text, or you can DirectCast the sender into a Menu... but for our case, it should be "File".
The Font is included with the DrawItemEventArgs: e.Font.
Brushes.Black is typical of the brush that would be used to draw text.
The X and Y property should be e.Bounds.X and e.Bounds.Y. You can add some value to it, but without e.Bounds, you'd be drawing on the icon in the titlebar.
These are just defaults. You can set them to whatever you want to do: you can use Wingdings with a Red Brush and draw the string in uppercase if you want.
Code:
Private Sub FileMenu_MeasureItem(ByVal sender As Object, ByVal e As System.Windows.Forms.MeasureItemEventArgs) Handles FileMenu.MeasureItem e.ItemWidth = 56 End Sub Private Sub FileMenu_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles FileMenu.DrawItem e.DrawBackground() e.Graphics.DrawString(FileMenu.Text, e.Font, Brushes.Black, e.Bounds.X, e.Bounds.Y) End Sub

Expanding your Drawing Ability and State Checking
Now, you have drawn your own menu. But, you can do more, of course.
Instead of doing e.DrawBackground, you can easily draw your own background rectangle:

e.Graphics.FillRectangle(Brushes.LightBlue, e.Bounds)
For a lightblue menu.
e.Graphics.FillRectangle(SystemBrushes.Menu, e.Bounds)
For a menu-colored menu.

When you run your project however, you will notice that your menu won't highlight. This is because e.DrawBackground() properly checked the e.State, but at the moment, we aren't checking the state.

A menu object goes more often through two basic states:
State.None and State.Selected
Depending on the value of the state that is in e.State(), we should draw an appropriate background for it.
To check if the menu is selected through the e.State (flag) enumeration, we do any flag checking method available.
For example:
If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
will check if it is selected.

In this If block, we draw the background for when the menu is selected. In the Else part, we can draw the background for when the menu is not selected.
Code:
'Check if the menu is selected. If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then 'If menu selected, we draw a yellowgreen highlighting e.Graphics.FillRectangle(Brushes.YellowGreen, e.Bounds) Else 'Not selected, we draw this light blue. e.Graphics.FillRectangle(Brushes.LightBlue, e.Bounds) End If

OwnerDraw on Submenus
Now, let's add a few submenus to the FileMenu menuheader... say,
New, Open, Save, and Close.
Set all of their name properties to something and set all of their OwnerDraw properties to True. I am using FileNew, FileOpen, FileSave, and FileClose.

To save the time of making DrawItem and MeasureItem event handlers for all of these, you can just create a DrawItem and MeasureItem for one control, and then set them to handle the others, as shown below.
Code:
Private Sub FileNew_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles FileNew.DrawItem, _ FileOpen.DrawItem, FileSave.DrawItem, FileClose.DrawItem 'draw item. End Sub Private Sub FileNew_MeasureItem(ByVal sender As Object, ByVal e As System.Windows.Forms.MeasureItemEventArgs) Handles FileNew.MeasureItem, _ FileOpen.MeasureItem, FileSave.MeasureItem, FileClose.MeasureItem 'measure item. End Sub
In the MeasureItem, we'll set the width for all of these menu items.
In addition, we can now set .ItemHeight, since these are not menu headers.
Code:
Private Sub FileNew_MeasureItem(ByVal sender As Object, ByVal e As System.Windows.Forms.MeasureItemEventArgs) Handles FileNew.MeasureItem, _ FileOpen.MeasureItem, FileSave.MeasureItem, FileClose.MeasureItem e.ItemWidth = 72 e.ItemHeight = 24 End Sub
Then, in DrawItem we do our drawing.
We should first DirectCast the sender into a Menu object:
Dim M As MenuItem = DirectCast(sender, MenuItem)

Then, our drawing is exactly the same as it is in the first part.
The value of e.Bounds properly moves everything so that it does not fall into the place of one of the other menus. The only change that you might make is to draw M.Text in the .DrawString call (that's why the DirectCast is up there).
Code:
Private Sub FileNew_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles FileNew.DrawItem, _ FileOpen.DrawItem, FileSave.DrawItem, FileClose.DrawItem Dim M As MenuItem = DirectCast(sender, MenuItem) If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then e.Graphics.FillRectangle(Brushes.LightCoral, e.Bounds) Else e.Graphics.FillRectangle(Brushes.LawnGreen, e.Bounds) End If e.Graphics.DrawString(M.Text, e.Font, Brushes.Brown, e.Bounds.X, e.Bounds.Y) End Sub
You'll even get to see your drawing fade out.

Notice that you are always using a Graphics object to do your drawing, so whatever you can draw with Graphics can be drawn on a menu... that includes Images (picture of a disk for Save, perhaps?), LinearGradientBrushes and PathGradientBrushes on your menus to give you that new office look, TextureBrushes so that your menu can have a background, and much more...

Attached are some menus that I've created.
Attached Files
File Type: zip Overdose.zip (27.4 KB, 185 views)
__________________

Iceplug, USN
Quadrill 1 Quadrill 2 (full) Quadrill 3 JumpCross .NET Website is ALIVE! - DL Platform Tour for VB.NET! Posting Guidelines Hint: Specify your location in your user cp profile if you want compassion!
Reply With Quote