VB-Helper: Hot Properties
VB.NET's controls come with a whole panoply of new properties, methods, events, and bugs. Many of these (excluding the bugs) can be powerful tools for those with the persistence to find them. This month, Property Proselytizer Rod Stephens describes some of his favorite new properties and the controls that go with them.
VB.NET's controls provide most of the same features included with the good old VB6 controls. Some of the names have been changed to confuse the innocent, but the new versions use the same old concepts for sizing, positioning, coloring, and clicking.
The new controls also include some really useful additions that address specific issues we've all been struggling with for years in previous versions of VB. This article describes some of the more useful (and sometimes surprising) properties I've stumbled across so far.
AnchorProbably the most common reason to write a Resize event handler in VB6 is to make controls use the available space on the form. For example, consider the dialog shown in Figure 1. Suppose the user makes the form bigger. The First Name, Last Name, Street, and City text boxes should expand to display more data. Of course, that means the OK and Cancel buttons need to move to the right to make room. Oh, and the Notes text box can also grow wider and taller–and display more text.
In VB6, you can perform all of these rearrangements in the form's Resize event handler. You also need to watch for a couple of finicky details as you resize the controls. For example, if the user shrinks the form, you need to be careful not to set a control's width to a negative value.
In VB.NET, the Anchor and Dock properties can handle a lot of this rearrangement automatically. The Anchor property determines which of a control's edges keep a fixed distance from the corresponding edge of its container. This property can be None or a combination of the values Top, Bottom, Left, and Right. For example, if a control's Anchor property is "Left," its left edge stays a fixed distance from the left edge of the form or control that contains it.
The default Anchor value is "Left, Top," so a control remains the same distance from the left and top edges of its container. This is exactly the way VB6 handles control positioning, so it should be no giant bombshell. It's the other values that are more interesting.
If you include the Right value in the Anchor property, the control's right edge moves with its container's right edge. If you also include the Left value, the control automatically stretches or shrinks as its container resizes.
In Figure 1, the OK and Cancel buttons have Anchor properties set to "Top, Right," so they move with the right edge of the form. The First Name, Last Name, Street, and City text boxes have Anchor set to "Top, Left, Right," so they stretch or shrink horizontally when the form resizes. The Notes text box has Anchor value "Top, Bottom, Left, Right," so it grows or shrinks both vertically and horizontally when the form resizes. All this rearrangement without a single line of code!
One other use for Anchor bears mention, although it's not demonstrated by the dialog in Figure 1. If you remove either the vertical or horizontal values (for instance, don't include either Left or Right), the control's corresponding center remains a fixed distance from the center of the container. For example, if you set Anchor to "Bottom" so it doesn't specify Left or Right, the control's horizontal center keeps a fixed distance from its container's horizontal center. If the control is centered on the form, it will remain centered as the form resizes. If the control is just left of center, it will remain just left of center. (Try placing a row of buttons centered at the bottom of a form and setting their Anchor properties to Bottom. As you resize the form, the row will remain centered at the bottom of the form as a group.)
By the way, if you need to perform more elaborate control rearrangements than are possible using Anchor and Dock (described shortly), Microsoft says you should now do it in the Layout event–not the Resize event. Whatever.
MinimumSize and MaximumSizeThe dialog shown in Figure 1 looks great if you enlarge it, but it starts to look funny if you make it too small. As the form shrinks, the ZIP text box starts poking off of the right edge of the form, and fields start dropping off the bottom.
To handle this in VB6, you can install a new WindowProc to subclass the form, catch its events, look for resize events, and ensure that the form's width and height don't go below some minimum values. If you've ever subclassed before, you know how dicey this can be. You need to remember to un-subclass the form before the program exits. At some point, you'll probably forget and exit the program prematurely by clicking the Run menu's End command, crashing the application and the whole development environment and losing any changes you've made recently. Sound familiar? Yeah, I know.
In VB.NET, Windows forms have MinimumSize and MaximumSize properties that make restricting the form's size completely trivial. The program shown in Figure 1 has its MinimumSize property set to "320, 248" so the user can't make the form less than 320 pixels wide and 248 pixels tall. Any simple property that removes a reason to subclass gets my vote of approval.
AcceptButton and CancelButtonWhen you build a dialog in VB6, you need to do some work to make the OK and Cancel buttons work correctly. The buttons need to store a value somewhere so the calling routine can tell which button the user clicked. They also need to hide the dialog so execution returns to the calling routine. After building a few dialogs, you probably worked out your own "system" but often had to fiddle a bit anyway.
VB.NET makes life a lot easier. Create a new form and give it OK and Cancel buttons. Set the form's AcceptButton property to the OK button, and set its CancelButton property to the Cancel button.
Open the OK button's code in the code editor. Add any validation code you need to make sure the user filled in all the fields, entered the phone number in the correct format, selected the correct battery size, and so forth. If the data passes muster and you want to close the dialog, add the following statement:
Me.DialogResult = DialogResult.OK
Believe it or not, that's all there is to it! The main program can use code similar to the following to display the dialog and decide whether the user clicked OK:
Dim dlg As New dlgNewCustomer If dlg.ShowDialog() = DialogResult.OK Then ' The user clicked OK. Process the data. ' ... End If
If the user presses the Enter or Escape key, the dialog automatically clicks its AcceptButton or CancelButton, respectively. The CancelButton automatically sets the form's DialogResult property to DialogResult.Cancel and closes the form. The single line of AcceptButton code shown previously sets the form's DialogResult property to DialogResult.OK, and that automatically closes the form. It's hard to imagine anything much simpler than building a dialog with two properties and one short line of code.
Dock (and the Splitter control)Like the Anchor property, the Dock property determines a control's size and position when its container resizes. The Dock property can take exactly one–not a combination, like the Anchor property–of the values None, Left, Right, Top, Bottom, or Fill. These values generally make the control stick to the corresponding edge of its container much as a toolbar usually does. Top makes the control stick to the top edge, filling the container's width. Right makes the control stick to the right edge, filling the container's height. Fill makes the control stick to all of the edges so it always fills its container. And I imagine you can guess what None does.
At first glance, you may think this isn't a big improvement on the Anchor property. A Dock value of Top is similar to setting Anchor to "Left, Right, Top" and sizing the control to fill the container horizontally. The Dock value Fill is similar to setting Anchor to "Left, Right, Top, Bottom" and sizing the control to fill the container completely.
The twist is that Dock doesn't really fit the control to its container. Instead, it fits the control to the area in the container that isn't already filled by another control using the Dock property. For example, suppose you build a form, add a button, and set its Dock property to Top so it sticks to the top of the form. Next, you add another button and set its Dock property to Top. This button sticks to the top of the form area that isn't occupied by the first button. In other words, this button sits below the first button. If you add other buttons with Dock = Top, you'll get a series of buttons all stuck together at the top of the form. If you set Dock in the last button to Fill, that button fills the rest of the form.
Instead of setting Dock = Top for every control on the form, you can mix and match Dock values to break the form up in different ways. You could dock a toolbar to the top of the form and then put two group boxes below it, one docked on the left and one with Dock = Fill to fill the rest of the form. Using different Dock values, you can tile a form in all sorts of ways.
Remember that VB.NET does all of the arranging for you. When the form resizes, its controls resize to fill their designated nooks and crannies. In particular, controls with Dock = Fill takes up any surplus space the form might have. You can even place controls inside other containers and set their Dock properties. You might give a form two group boxes with Dock = Left and Dock = Fill. Inside those group boxes you could place other controls with their own Dock properties to make them carve up the space inside the group boxes.
This is all very ingenious (and possibly a bit too complex for everyday use), but there's one very important occasion when the Dock property is invaluable–when using the Splitter control. The Splitter lets the user drag a divider vertically or horizontally to divvy up the available space between the areas on either side.
For example, take a look at Figure 2. A Splitter between the Orders and Order Details group boxes lets the user make one of those group boxes larger while making the other smaller. (You see the left-right resize mouse cursor because the user is in the process of dragging.)
To use a vertical Splitter like the one shown in Figure 2, place a GroupBox on the form and set its Dock property to Left. Then create a Splitter control and set its Dock property to Left (that's the default). This makes the Splitter stick to the left edge of the unused area, right next to the GroupBox. Now add another GroupBox to the form and set its Dock property to Fill. That makes it fill the rest of the unused area, to the right of the Splitter. Bravo! You're done. When the user drags the Splitter at runtime, the Splitter automatically resizes the GroupBox to its left. The Splitter's Dock property makes it follow along, and the other GroupBox's Dock property makes it fill the remaining area.
You can add more Splitters to the equation by setting their Dock properties and those of the controls between them appropriately. You can even mix and match vertical and horizontal Splitters. Imagine two GroupBox or Panel controls split horizontally, each containing other controls split vertically.
Note: Figure 2 contains two GroupBoxes separated by a vertical Splitter. The ListBox in the left GroupBox has Dock set to Fill so it fills the display area inside its GroupBox. The single-line fields in the right GroupBox have fixed sizes, but the ListView control on the bottom has Anchor set to "Top, Bottom, Left, Right" so it resizes when its GroupBox does.
The one last docking tidbit that you should understand is the order in which the Dock property is applied. VB.NET lets controls colonize their containers according to their stacking order or z-order. For example, suppose you make four buttons that overlap slightly. You can easily see which one is on the bottom and which one is on the top of the stack. If you set Dock = Top for all of them, VB lets the one on the bottom move in first. After that button is comfortably squatting on the top edge of the form, it lets the next button claim its share of the form. VB.NET continues positioning controls in the order of their stacking order until all of the controls with Dock values are positioned.
If you get confused about which control is getting priority in the mad dash for form real estate, set all of the controls' Dock properties to None, change their stacking order using the Format menu's Order submenu, and then set the Dock properties back to the values they should have. That said, it's still confusing when you add Splitters to the mix, because a Splitter can't have a Dock value of None. In some cases, I've found that it's easier to throw everything away and start over, carefully building the controls in the right order the first time around.
AutoScroll (and the Panel control)One of the glaring omissions in previous versions of VB is a scrolled window control. Because VB6 doesn't have one, you have to build one for yourself or do without.
Happily, VB.NET actually has a control that works like a scrolled window, but it's "buried" under the guise of the Panel control. However, if you set a Panel control's AutoScroll property to True, the control automatically gives itself working scroll bars whenever it can't display its contents all at once. Try it–use a Panel control, set its AutoScroll property to True, and put other controls inside it. The rest is automatic.
DisplayRectangleIf you've ever tried to programmatically arrange controls inside a GroupBox using VB6, you know how, well, "fiddly" the process can be. You need to guess how far from the top and sides of the control to place things so they don't cover the GroupBox's caption and border.
In VB.NET, the GroupBox control has a DisplayRectangle property that tells you the area inside the control where it expects you to display stuff. This area is safely inside the control's decorations, so you know exactly where you can place the child controls.
The program shown in Figure 3 demonstrates the DisplayRectangle property. The two GroupBoxes look the same at design time. When the form loads, it uses the following code to position the two labels in the GroupBox on the right. It makes lblClientRectangle fill the GroupBox's client area and it makes lblDisplayRectangle fill the display rectangle. It also calls the BringToFront method to ensure that lblDisplayRectangle isn't hidden below lblClientRectangle.
lblClientRectangle.Bounds = _ grpRectangles.ClientRectangle lblDisplayRectangle.Bounds = _ grpRectangles.DisplayRectangle lblDisplayRectangle.BringToFront()
All controls have a DisplayRectangle property, although very few seem to differentiate between their ClientRectangles and their DisplayRectangles. In particular, it would be nice if the other container controls such as TabControl and Panel got a clue. Those controls don't let you cover their decorations (borders, tabs, and so forth) with child controls anyway, so it's not a big issue for them, it's just less flexible and consistent than necessary.
ContextMenuBuilding context menus in VB6 isn't too painful, but it could be easier. You add the submenu to the form's main menu and set its Visible property to False. You add items to the submenu and give them event handlers. In the control where you want to display the context menu, you catch the MouseDown event, see if the user is right-clicking, and display the context menu.
Building context menus in VB.NET is even easier. You add a ContextMenu control to the form and give it menu items just as you give items to any other menu. You select the control that you want to display the context menu and set its ContextMenu property to the menu you just created. VB handles the rest. It keeps the menu hidden until the user right-clicks the control and then displays the menu automatically. If the control is a TextBox, the context menu automatically replaces the normal Copy/Cut/Paste menu that TextBoxes like to display.
SummaryVB.NET includes several new properties that can make your life easier. Some, like ContextMenu and CancelButton, simplify a task that's a bit more complicated in VB6. Others, like Anchor and Dock, can save you a lot of event handler code. Still others, such as MinimumSize and MaximumSize, let you avoid the gruesome and dangerous process of subclassing.
Spend a few minutes digging through VB.NET's online Help and looking over the new properties and methods. A few minutes of review now may save you a lot of time later.
To find out more about Hardcore Visual Basic and Pinnacle Publishing, visit their Web site at http://www.pinpub.com/
Note: This is not a Microsoft Corporation Web site. Microsoft is not responsible for its content.
This article is reproduced from the May 2004 issue of Hardcore Visual Basic. Copyright 2004, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Hardcore Visual Basic is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-788-1900.