Visual C#. NET 2003 Unleashed

Using the DrawMode Property

The DrawMode property is implemented by many Windows Forms controls. It dictates the method by which the control is rendered on the surface of a form or within another container control. Three different drawing modes are available:

  • DrawMode.Normal This mode is the default. When a control is set to this drawing mode, it will render itself according to the properties of the control using its default display. Every element in this control is drawn by the operating system, and each element within the control is a uniform size.

  • DrawMode.OwnerDrawFixed This mode will override the default drawing behavior, allowing manual drawing of each element. Each element will, however, be assumed to be the same size.

  • DrawMode.OwnerDrawVariable This mode allows for the manual drawing of each element. Each of those elements can be a different size.

Elements refer to items within a control such as a ListBox , a ComboBox, a ListView, and so on. When you indicate that you will be manually drawing the items within a control, you need to provide your own implementation for two events: DrawItem and MeasureItem. The next section will illustrate the use of custom drawing methods to create advanced controls.

Creating a Custom ListBox

The first custom-drawn items we will experiment with are the items of a ListBox. If you have seen any of the Longhorn demonstrations, you've seen how the Longhorn ListBox can automatically size each individual item and can contain anything, including graphics. Its default behavior has a nice gradient in the background.

Until Longhorn comes out, developers have to code behavior like this by hand. Fortunately, with a little GDI+ magic, it isn't all that difficult to do as long as you remember the two important methods: DrawItem and MeasureItem.

To get started, create a new Windows Forms application called CustomElements. Don't bother deleting Form1; it's just a testbed anyway. Drop a ListBox onto the main form and set its DrawMode property to DrawMode.OwnerDrawVariable. Now switch over to the events list (the lightning-bolt Icon in the Properties window) and double-click the blank spaces next to DrawItem and MeasureItem to create some new event handlers.

The MeasureItem event handler is designed to provide the programmer with the ability to tell the Windows Forms system the dimensions of the custom item being drawing. It wouldn't matter if we were using OwnerDrawFixed, the custom item would be confined to the standard item space provided by the control.

The DrawItem event handler is called when Windows wants the code to perform the actual graphical output of the control. This is where we draw each item within the control. The method comes with a DrawItemEventArgs parameter that contains some valuable information, such as the bounding rectangle in which the item is being rendered. The code in Listing 17.5 shows these two event handlers for the ListBox that was added to the form.

Listing 17.5. The DrawItem and MeasureItem Event Handlers for a ListBox

private void lbCustomDraw_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e) { string displayText = (string)lbCustomDraw.Items[ e.Index ]; SizeF size = e.Graphics.MeasureString( displayText, this.Font ); size.Height += 10; e.ItemHeight = (int)size.Height; } private void lbCustomDraw_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e) { // used to keep the background drawing consistent in case our code // doesn't completely cover the background e.DrawBackground(); Rectangle r = new Rectangle(0,0, lbCustomDraw.Width, 100); bool selected= ((e.State & DrawItemState.Selected) == DrawItemState.Selected); LinearGradientBrush lgb = null; if (!selected) lgb = new LinearGradientBrush( r, Color.Red, Color.Yellow, LinearGradientMode.Horizontal); else lgb = new LinearGradientBrush( r, Color.Cyan, Color.White, LinearGradientMode.Horizontal); e.Graphics.FillRectangle( lgb, e.Bounds ); e.Graphics.DrawRectangle(SystemPens.WindowText, e.Bounds); Rectangle r2 = e.Bounds; string displayText = (string)lbCustomDraw.Items[ e.Index ]; SizeF size = e.Graphics.MeasureString( displayText, this.Font ); r2.Y = (int)(r2.Height / 2) - (int)(size.Height/2) + e.Bounds.Y; r2.X = 2; e.Graphics.DrawString( displayText , this.Font, Brushes.Black, r2 ); e.DrawFocusRectangle(); }

The first method, MeasureItem, sets the item's height to 10 pixels more than the height required to draw the string contained in that item. You might also notice that I'm referring to lbCustomDraw directly. This is actually something to avoid, particularly if you are planning to create reusable controls out of the new display logic. You should instead refer to sender as a ListBox if you know ahead of time that the custom code is applicable for ListBox controls.

The second method, DrawItem, actually performs the rendering of the item. First, we call DrawBackGround so that we can be sure that the control has taken care of all transparency and overlay issues for us. Second, we create a LinearGradientBrush. This brush will fade from red to yellow if the ListBox item is not selected, and it will face from cyan to white if the item is selected. Finally, we fill the gradient into the appropriate rectangle and use DrawString to place the appropriate text into the rendered area.

Don't worry if the gradient code seems strange. As long as you know what methods to implement for your custom controls, you can always use a good GDI+ reference to aid you in drawing what you need.

Creating Custom Menu Items

The next thing to do is create some custom menu items. The good thing about this example is that if you know how to put a gradient behind an item, you know how to do virtually anything to it, including putting an image or an icon in front of it. Rather than showing you how to add icons to your menus, the code will illustrate how to add a graphical flare to them with more gradients.

Add about 10 menu items to the form in a MainMenu control. It doesn't matter how many; just make sure that you add a few top-level menus and a few regular menu items. Don't use the ampersand (&) in menu item names because the custom rendering code in this sample won't handle it.

After you have added your new menu items, set the OwnerDraw property on all of them to true, and then make sure that the DrawItem and MeasureItem event handlers for all of those menu items point to the same two methods. Now set the code for your measure and draw methods to the code shown in Listing 17.6.

Listing 17.6. The DrawItem and MeasureItem Event Handlers for Custom MenuItems

private void mniTEst_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e) { e.DrawBackground(); string displayText= (string)(sender as MenuItem).Text; SizeF size = e.Graphics.MeasureString( displayText, SystemInformation.MenuFont ); LinearGradientBrush lgb = null; bool selected= ((e.State & DrawItemState.Selected) == DrawItemState.Selected ); if (selected) lgb = new LinearGradientBrush( e.Bounds, Color.LightCoral, Color.AntiqueWhite, LinearGradientMode.Horizontal); else lgb = new LinearGradientBrush( e.Bounds, Color.LightBlue, Color.AntiqueWhite, LinearGradientMode.Horizontal); e.Graphics.FillRectangle( lgb, e.Bounds ); Rectangle r2 = e.Bounds; r2.Y = (int)(e.Bounds.Height / 2) - (int)(size.Height/2) + e.Bounds.Y; r2.X = e.Bounds.X + 1; e.Graphics.DrawString( displayText, this.Font, Brushes.Black, r2 ); } private void mniTEst_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e) { string displayText = (string)(sender as MenuItem).Text; SizeF size = e.Graphics.MeasureString( displayText, this.Font ); size.Width = 50; size.Height += 4; if ( (sender as MenuItem).Text == "-") e.ItemHeight = 3; else e.ItemHeight = 22; e.ItemWidth = (int)size.Width + 30; }

The event handlers for these menu items are virtually identical to those for the list box. Pay close attention to the way the e.Bounds property is used, and how the Windows Forms GUI system informs your application of where it needs to render specific items.

You will probably be unable to make out the colors in Figure 17.4, but it should illustrate just how much of a radical difference you can make in the visual quality of your application just by taking a little extra effort to make some OwnerDraw controls.

Figure 17.4. The OwnerDraw sample application, with colorful list boxes and menu items.

Nothing shown in this chapter has been dramatic or earth-shattering. On the contrary, this code is fairly simple. The next time your design calls for some visual effect that doesn't come standard with the .NET controls, take a look at OwnerDraw controls instead of backing away in fear of GDI+. There is no reason why every application developer shouldn't have a library of reusable controls that go beyond the capabilities provided by the .NET controls. The controls that come with Visual Studio .NET should be considered starter controls.

    Категории