Chapter 4. Forms, Controls, and Other Useful Objects

Introduction

If you are writing a desktop application, you are dealing with forms and controls. Since its first release, Visual Basic has made the dream of drag-and-drop programming possible: just add some controls to a form, press F5, and go.

While this method works, it allows you to design only the most rudimentary applications. Most programs require gobs of code for each on-screen control. Fortunately, .NET simplifies a lot of the plumbing associated with complex controls, so you can just focus on the logic that responds directly to a user action. This chapter shows you how to take advantage of the control features included with .NET’s Windows Forms library.

4.1. Creating and Adding Controls at Runtime

Problem

You need to add one or more controls to a form dynamically at runtime. You used to do something similar to this in Visual Basic 6.0 using control arrays, but those do not exist in Visual Basic 2005.

Solution

Sample code folder: Chapter 04\DynamicControls

You can add any control to a form at runtime just by creating an instance of it. Your code can define the initial properties, such as the location of the control on the form, at runtime. You can also connect events for these runtime controls to event handlers, although the handler methods must exist at design time. (Technically, it’s possible to write a method at runtime, but such programming is beyond the scope of this book and is generally frowned upon.)

Discussion

To test this method of dynamically creating controls, start by creating a new Windows Forms application and add the following source code to Form1’s code template:

	Private Sub ShowTheTime(ByVal sender As System.Object, _
	      ByVal e As System.EventArgs)
	   ' ----- Display the time in the text box, if it exists.
	   Dim theTextBox As TextBox

	   ' ----- Locate and update the text control.
	   theTextBox = Me.Controls("TimeTextBox")
	   If (theTextBox IsNot Nothing) Then
	      theTextBox.Text = Now.ToLongTimeString( )
	   End If
	End Sub

	Private Sub Form1_Load(ByVal sender As Object, _
	      ByVal e As System.EventArgs) Handles Me.Load
	   ' ----- Add controls at runtime.
	   Dim dynamicText As TextBox = Nothing
	   Dim dynamicButton As Button

	   ' ----- Dynamically add a text box control to the form.
	   dynamicText = New Windows.Forms.TextBox
	   dynamicText.Name = "TimeTextBox"
	   dynamicText.Location = New System.Drawing.Point(8, 8)
	   dynamicText.Size = New System.Drawing.Size(232, 20)
	   dynamicText.TabIndex = 0
	   Me.Controls.Add(dynamicText)

	   ' ----- Dynamically add a button control to the form.
	   dynamicButton = New Windows.Forms.Button
	   dynamicButton.Location = New System.Drawing.Point(144, 32)
	   dynamicButton.Size = New System.Drawing.Size(99, 23)
	   dynamicButton.Text = "Get Time"
	   dynamicButton.UseVisualStyleBackColor = True
	   dynamicButton.TabIndex = 1
	   Me.Controls.Add(dynamicButton)

	   ' ----- Connect the button to an event handler.
	   AddHandler dynamicButton.Click, AddressOf ShowTheTime
	End Sub

When you run the program, you will see two controls—a TextBox control and a Button control—magically appear on the previously empty form. Clicking the button calls the prewritten event handler, which inserts the current time into the text box, as shown in Figure 4-1.

In Visual Basic 6.0, if you wanted to add a control to a form at runtime it was necessary to create a design-time control just like it, and create a dynamic copy of it at run-time. This was due, in part, to the special design-time method used to record form controls. If you opened up the .frm file for a Visual Basic 6.0 form, you would see non–Visual Basic code at the top of the file that defined the controls and the form itself.

Dynamically generated controls on a form
Figure 4-1. Dynamically generated controls on a form

In Visual Basic 2005, all form controls, and even the form itself, exist through standard object creation. When Form1 appears on the screen in a running program, it’s because somewhere in your program there is code that creates a new instance of Form1 and calls its Show method:

	(New Form1).Show

Although you add controls to your form using the Visual Studio Form Designer, Visual Studio actually generates runtime code for you that dynamically creates the controls and adds them to the form. All this code is generally hidden in the form’s designer file. To view this file, select the Project → Show All Files menu command, and expand the branch for one of your forms in the Solution Explorer panel. By default, Form1’s designer file is named Form1.Designer.vb.

To create the source code for this project, we added a TextBox and a Button control to the form and then opened the designer code file. We then copied selected lines from that file and made slight adjustments before pasting that code into the form’s Load event handler. Finally, we deleted the design-time controls from the form.

See Also

Recipes 4.2 and 4.3 also discuss features that are replacements for Visual Basic 6.0 control arrays.

4.2. Iterating Through All Controls on a Form

Problem

You need to make updates to some or all controls on a form at runtime, and all in a common way. You aren’t excited about copying and pasting the same lines over and over again to make the changes to every instance of the same control type.

Solution

Sample code folder: Chapter 04\IteratingControls

The form maintains a collection of all controls on the form. Iterate through this collection, and make your changes as you pass by each item.

Discussion

Create a new Windows Forms application, and add three Label controls to Form1. Name the controls whatever you want, and change their Text properties to anything you want as well. Next, add two Button controls to the form, named ActRed and ActNormal. Set their Text properties to Red and Normal, respectively. Then add the following source code to the form’s code template:

	Private Sub ActRed_Click(ByVal sender As System.Object, _
	      ByVal e As System.EventArgs) Handles ActRed.Click
	   ' ----- Set the background of all labels to red.
	   UpdateAllLabels(Color.Red)
	End Sub

	Private Sub ActNormal_Click(ByVal sender As System.Object, _
	      ByVal e As System.EventArgs) Handles ActNormal.Click
	   ' ----- Set the background of all labels to the
	   '       standard color.
	   UpdateAllLabels(SystemColors.Control)
	End Sub

	Private Sub UpdateAllLabels(ByVal withColor As Drawing.Color)
	   ' ----- Scan all controls, looking for labels.
	   For Each scanControls As Control In Me.Controls
	      If (TypeOf scanControls Is Label) Then
	          scanControls.BackColor = withColor
	      End If
	   Next scanControls
	End Sub

When you run the code and click on each button, the background color of the three labels changes as indicated by the clicked button. Figure 4-2 shows a sample use of this code.

All labels set to red
Figure 4-2. All labels set to red

All of a form’s controls appear in a collection accessed through the form’s Controls property. Because it is a standard collection, you can iterate through it using the For Each statement, or any other technique that accesses elements of a collection. You can also reference controls by string name:

	Dim firstButton = Me.Controls("ActRed")

Although controls of all types are added to the Controls collection, you can still determine their derived data types using the TypeOf statement, as is done in this recipe’s sample code. This can help you limit updates to a certain type of control in the collection.

See Also

Recipes 4.1 and 4.3 also discuss features that are replacements for Visual Basic 6.0 control arrays.

4.3. Sharing Event-Handler Logic Among Many Controls

Problem

You have many controls that should use identical event-handler logic for some of their events. You don’t want to rewrite the logic for each control. You accomplished this in Visual Basic 6.0 using control arrays, but they no longer exist in Visual Basic 2005.

Solution

Sample code folder: Chapter 04\SharingControlLogic

You can use a single .NET method as the event handler for any number of control events on the form, as long as those events share a common set of event arguments.

Discussion

Visual Basic 6.0 included a feature called control arrays that allowed developers to share a single event-handler procedure among multiple controls. The controls in the array had to be of the same type and share a common name. They differed only by the values of their numeric Index properties. Each event handler also included an extra argument that identified the index of the control triggering the event.

Visual Basic in the .NET world no longer allows control arrays, but you can still share event handlers. To do this, you alter the event method’s Handles clause to include all the control events it should handle.

Create a new Windows Forms application, and add three new TextBox controls to Form1. By default, they are named TextBox1, TextBox2, and TextBox3. Add a Label control named ShowInfo. Then add this source code to the form’s code template:

	Private Sub MultipleEvents(ByVal sender As Object, _
	      ByVal e As System.EventArgs) Handles _
	      TextBox1.Enter, TextBox2.Enter, TextBox3.Enter, _
	      TextBox1.TextChanged, TextBox2.TextChanged, _
	      TextBox3.TextChanged

	   ' ----- Report the current status of this field.
	   Dim activeControl As TextBox
	   activeControl = CType(sender, TextBox)
	   ShowInfo.Text = "Field #" & _
	      Microsoft.VisualBasic.Right(activeControl.Name, 1) & _
	      ", " & activeControl.Text.Length & " character(s)"
	End Sub

Run this program. As you move from text box to text box and type things in, the ShowInfo label updates to show you which text box you are in (based on the number extracted from its control name) and the length of its content. Figure 4-3 shows the form in use.

A single event handler dealing with multiple events
Figure 4-3. A single event handler dealing with multiple events

See Also

Recipes 4.1 and 4.2 also discuss features that are replacements for Visual Basic 6.0 control arrays.

4.4. Working with Timers

Problem

You need to have some action occur on a regular basis in your form.

Solution

Sample code folder: Chapter 04\ClockTimer

Use a Timer control, and set it for the desired interval. Create a new Windows Forms application, and add a Label control named CurrentTime to the form. Also add a Timer control to the form, and name it ClockTimer. Set its Interval property to 1000, and set its Enabled property to True. Then add the following source code to the form’s code template:

	Private Sub ClockTimer_Tick(ByVal sender As System.Object, _
	      ByVal e As System.EventArgs) Handles ClockTimer.Tick
	   CurrentTime.Text = Now.ToLongTimeString
	End Sub

When you run the program, that label updates once each second with the current time.

Discussion

The Timer control’s Interval property sets the time between Tick events in milliseconds (1,000 per second). Although you can set the Interval as low as one millisecond, the timer’s resolution is limited by your system’s hardware and operating-system-level factors.

The Tick event fires at approximately the interval you specify, if nothing more important is going on. If the code within your Tick event handler is still running when the next Tick event should occur, that subsequent Tick event is disposed without a call to the event handler.

See Also

Recipe 14.8 shows how to have a section of code sleep, or take a small break. Some older Visual Basic code used timers for this purpose, although a timer is not the best solution in this case.

4.5. Determining If a Control Can Take the Focus

Problem

You need to move the current focus to a specific control, but you want to avoid conditions where the focus-setting action would fail.

Solution

Use the control’s CanFocus() method to determine whether the application can take the focus or not:

	If (SomeControl.CanFocus() = True) Then _
	   SomeControl.Focus()

Discussion

Event-driven programming can lead to many runtime surprises based on timing. Depending on how you write your code, it’s possible that an event handler will be temporarily interrupted so that another event handler can run instead. Or, more commonly, unrelated event handlers may fire in an order you did not anticipate because of some interesting input action by the user.

If you have an event handler that disables and enables a specific control, and another handler that sets the focus to that control, some situations may arise in which the focus action fails because the control is disabled. While you could check the Enabled flag before setting the focus, there are other conditions (such as the presence of a separate modal dialog) that can also stop a control from receiving the focus, even when the Enabled flag is True. Using the CanFocus() method provides a more accurate method of determining when it is safe to call the Focus() method.

4.6. Programmatically Clicking a Button

Problem

You want the Click event handler for a button to run, but you want to initiate this action from code instead of waiting for the user to click the button.

Solution

Call the button’s PerformClick() method:

	Button1.PerformClick()

Discussion

While it’s nice that the Button control has a PerformClick() method to run its Click event handler in an object-oriented manner, most controls and most control events have no such related method. If you wish to call an event handler immediately through code, you have to call it like any other method, passing the correct arguments:

	' ---- Call the text box control's GotFocus handler.
	TextBox1_GotFocus(TextBox1, New System.EventArgs)

In this case, calling the TextBox1 control’s GotFocus() event handler will run that handler’s code, but it will not cause the focus to move to the text box. An even better solution would be to write a shared routine that the GotFocus() event handler and your other code both call.

4.7. Drawing a Control

Problem

You want to provide custom drawing code for a control.

Solution

Sample code folder: Chapter 04\ControlDrawing

For most controls, provide an event handler for the Paint event, and add your drawing code there. This event’s second argument includes a Graphics property representing the canvas on which you can issue your drawing commands. Some controls also provide separate DrawItem events that let you draw specific portions of the control, such as distinct items in a ListBox control. You can also draw directly on the form’s surface. This recipe’s code includes samples for all these activities.

Create a new Windows Forms application, and add two controls: a Button control named XButton and a ComboBox control named ColorList. Change the ColorList control’s DrawMode property to OwnerDrawFixed and its DropDownStyle property to DropDownList. Then add the following source code to the form’s code template:

	Private Sub Form1_Load(ByVal sender As Object, _
	      ByVal e As System.EventArgs) Handles Me.Load
	  ' ----- Add some basic colors to the color list.
	  ColorList.Items.Add("Red")
	  ColorList.Items.Add("Orange")
	  ColorList.Items.Add("Yellow")
	  ColorList.Items.Add("Green")
	  ColorList.Items.Add("Blue")
	  ColorList.Items.Add("Indigo")
	  ColorList.Items.Add("Violet")
	End Sub

	Private Sub Form1_Paint(ByVal sender As Object, _
	      ByVal e As System.Windows.Forms.PaintEventArgs) _
	      Handles Me.Paint
	   ' ----- Draw an ellipse on the form.
	   e.Graphics.DrawEllipse(Pens.Black, 10, 10, _
	      Me.ClientRectangle.Width - 20, _
	      Me.ClientRectangle.Height - 20)
	End Sub

	Private Sub XButton_Paint(ByVal sender As Object, _
	      ByVal e As System.Windows.Forms.PaintEventArgs) _
	      Handles XButton.Paint
	   ' ----- Draw a big x in a rectangle on the button surface.
	   Dim usePen As Pen

	   ' ----- Provide a neutral background.
	   e.Graphics.Clear(SystemColors.Control)

	   ' ----- Draw the outline box.
	   usePen = New Pen(SystemColors.ControlText, 3)
	   e.Graphics.DrawRectangle(usePen, XButton.ClientRectangle)

	   ' ----- Draw the x.
	   e.Graphics.DrawLine(usePen, 0, 0, _
	      XButton.Width, XButton.Height)
	   e.Graphics.DrawLine(usePen, 0, _
	      XButton.Height, XButton.Width, 0)
	   usePen.Dispose( )
	End Sub

	Private Sub ColorList_DrawItem(ByVal sendesender As Object, _
	      ByVal e As System.Windows.Forms.DrawItemEventArgs) _
	      Handles ColorList.DrawItem
	   ' ----- Draw the color instead of the text.
	   Dim useBrush As Brush

	   ' ----- Check for a nonselected item.
	   If (e.Index = -1) Then Return

	   ' ----- Set the neutral background.
	   e.DrawBackground( )

	   ' ----- Fill in the color.
	   useBrush = New SolidBrush(Color.FromName(CStr( _
	      ColorList.Items(e.Index))))
	   e.Graphics.FillRectangle(useBrush, _
	      e.Bounds.Left + 2, e.Bounds.Top + 2, _
	      e.Bounds.Width - 4, e.Bounds.Height - 4)
	   useBrush.Dispose( )

	   ' ----- Surround the color with a black rectangle.
	   e.Graphics.DrawRectangle(Pens.Black, _
	      e.Bounds.Left + 2, e.Bounds.Top + 2, _
	      e.Bounds.Width - 4, e.Bounds.Height - 4)

	   ' ----- Show the item selected if needed.
	   e.DrawFocusRectangle( )
	End Sub

	Private Sub XButton_Click(ByVal sender As System.Object, _
	      ByVal e As System.EventArgs) Handles XButton.Click
	   MsgBox("Button clicked.")
	End Sub

Run the program. The XButton control no longer looks like a button; it instead looks like a custom-drawn “X.” Although the button looks strange, it still works. The ellipse we drew directly on the form’s surface is there. Also, the ComboBox control now displays actual colors instead of just the names of colors. This all appears in Figure 4-4.

Controls drawn with custom code
Figure 4-4. Controls drawn with custom code

Discussion

Some of the controls that support item-level drawing, such as the ListBox and ComboBox controls, include an e.State property in the data passed to the event handler. This value indicates the current state of the item being drawn:selected, not selected, or a half dozen other relevant states. You do not need to take that property into account if your implementation doesn’t require it, but it is generally a good idea to provide feedback to the user in a way the user expects. Adjusting the display based on this property helps achieve that purpose.

As shown in the sample code, the DrawItem event handler includes e.DrawBackground() and e.DrawFocusRectangle() methods that help you properly draw the item. Availability of these methods varies by control type.

See Also

See the recipes in Chapter 9 for examples that use the various GDI+ drawing commands.

4.8. Making a Form the Top-Most Form

Problem

You want a specific form to appear on top of all other forms in your application, no matter which form is selected.

Solution

If you wish to have a Toolbox-type form that is accessible at the same time as other forms but always remains on top, set the form’s TopMost property to True.

Discussion

If you also want to disable access to all other forms, open the important form of the moment using its ShowDialog() method:

	Form1.ShowDialog()

No other forms already displayed by the application will be available until the ShowDialog() form closes.

4.9. Indicating the Accept and Cancel Buttons on a Form

Problem

On a form, you want to have the Enter key trigger a specific button (such as an “OK” button) and have the Escape key trigger another button (such as a “Cancel” button).

Solution

Use the form’s AcceptButton and CancelButton properties to assign the appropriate buttons. In the Visual Studio Form Designer, setting these form properties to the names of buttons on the form will enable the keyboard shortcuts for those buttons.

Discussion

Setting a button to be a form’s CancelButton object has the side effect of changing that button’s DialogResult property to Cancel.

Even if you set an accept button, the Enter key doesn’t always trigger it. For instance, if another button on the form has the focus, that button, and not the form’s accept button, is triggered when the user presses the Enter key.

4.10. Remembering a Form’s Position Between Uses

Problem

You would like the position of a form to be retained between exiting the application (or closing that form) and the next time you access that same form.

Solution

Sample code folder: Chapter 04\RememberFormPosition

Tie the form’s Location property to a member of the My.Settings object. You do this using the form’s application-setting property bindings.

Create a new Windows Forms application. Access the Project Properties window through the Project → WindowsApplication1 Properties (or similar) menu command. Select the Settings tab in this window, as shown in Figure 4-5.

The Settings tab of the Properties window
Figure 4-5. The Settings tab of the Properties window

In the first row of the Settings grid, set the Name field to MainFormLocation, and select System.Drawing.Point in the Type field (Figure 4-6). Close the Project Properties window.

The added MainFormLocation property
Figure 4-6. The added MainFormLocation property

Back on Form1, expand its (ApplicationSettings) property. One of the subproperties should be Location. Change its value to MainFormLocation.

The program is ready to use. Run it, and move the form to a conspicuous location. Then exit the program. When you run the program again, the form will be where you moved it.

Discussion

If, when you expand the (ApplicationSettings) property, you don’t see the Location subproperty, use the (PropertyBinding) subproperty instead. Click on the “…” button in its value area to display the “Application Settings for ‘Form1’” dialog. Locate the Location entry in the form’s settings list, and set its value to MainFormLocation, as shown in Figure 4-7.

The Application Settings dialog for Form1
Figure 4-7. The Application Settings dialog for Form1

Any settings added to the Settings tab in the Project Properties window appear as members of the My.Settings object. In this recipe’s case, you get a new property of type System.Drawing.Point with the name My.Settings.MainFormLocation. You can access this property as needed in your code.

Another way to add a control-linked setting is to skip the trip to the Project Properties’ window’s Settings panel, and add the new setting directly from the control’s list of properties. When you select the (ApplicationSettings) property for the form or control and bring up the Application Settings dialog (Figure 4-7), if you click the drop-down button in the second column for any property, one of the choices that appears is “(new).” Clicking this link brings up the New Application Setting dialog, where you can enter the name and starting value of a new setting. The new property automatically obtains the right data type for the linked field. Figure 4-8 shows this method in action.

Adding a new setting for the form’s Location property
Figure 4-8. Adding a new setting for the form’s Location property

4.11. Attaching a Control to the Edge of a Form

Problem

You want a specific control, such as a toolbar, to always “stick” to one edge of the form, no matter how it is resized.

Solution

Use the control’s Dock property to permanently affix the control to the selected side or other “dock region.”

Discussion

Dock has six possible values:

None

The control performs no docking.

Top

The control attaches itself to the top of the form’s client area and fills the entire width of the client area, if the control supports such resizing.

Bottom

The control attaches itself to the bottom of the form’s client area and fills the entire width of the client area, if the control supports such resizing.

Left

The control attaches itself to the left edge of the form’s client area and fills the entire height of the client area, if the control supports such resizing.

Right

The control attaches itself to the right edge of the form’s client area and fills the entire height of the client area, if the control supports such resizing.

Fill

The control fills the entire client area of the form, if the control supports such resizing.

If multiple controls have Dock settings other than None, they are attached to the form edges according to their z-order settings, starting from the back-most control. To alter the z-order of a control, right-click on the control in the Form Designer and select either “Bring to Front” or “Send to Back” from the shortcut menu. Figures 4-9 and 4-10 show a form with two controls with different z-orders docked to its bottom edge: a MonthCalendar control (notice how it automatically fills the width of the form by adding months) and a StatusStrip control.

The form when the calendar’s z-order is in front
Figure 4-9. The form when the calendar’s z-order is in front

Some controls are designed to dock along a specific edge of the form’s client area. The most obvious example is the StatusStrip control, shown in this recipe’s figures, which is designed to dock along the bottom edge of the form. Other controls, such as the CheckBox control, really aren’t designed for docking. While you can still dock them, they may not look very nice.

Docking also applies to panels and other containers that can include subordinate controls. Figure 4-11 displays a Panel control with an included ComboBox control that is docked along the top edge of the panel.

The form when the calendar’s z-order is in back
Figure 4-10. The form when the calendar’s z-order is in back
Docking within a container
Figure 4-11. Docking within a container

See Also

Recipe 4.12 discusses the Anchor property, which can be used to attach a control to one, two, three, or four sides of the form. The Dock and Anchor properties cannot be used at the same time on the same control. The last one you set on that control is the one used.

4.12. Moving or Resizing Controls as a Form Resizes

Problem

You want a control to move or stretch in proportion to how the form is resized.

Solution

Use the control’s Anchor property to attach it to one or more sides of the form.

Discussion

Setting the Anchor property of a control tells that control to permanently maintain a consistent distance relationship with one or more sides of the form or container. You can anchor a control to any or all of the four sides of a form. By default, controls are anchored to the left and top sides of the form. This means that as the form resizes, the controls remain the same distance from the form’s left and top edges (i.e., they do not appear to move).

The available Anchor property choices include Left, Top, Right, and Bottom, and you can use them in any combination. The following list shows the types of combinations you can use with the Anchor property:

Anchored to one side

As the form is resized, the center point of the control along the anchored edge is matched to a position on that form edge relative to the changing size of the form. The size of the control does not change. For instance, if a control is anchored to the top of a form and the form is made wider, the control moves to the right in proportion to the size of the form, as shown in Figure 4-12.

The top-anchored control moves when the form is resized
Figure 4-12. The top-anchored control moves when the form is resized
Anchored to two adjacent sides

As the form is resized, the control maintains its distance from both anchor sides. In other words, it seems to be joined to the corner that is shared by the two anchor sides. By default, most controls anchor to the left and top sides of the form and do not appear to move when the right and bottom borders of the form are moved in a resize operation.

Anchored to two opposite sides

The anchor sides of the control remain a fixed distance from the anchor borders. For instance, if a control is anchored on the left and right, the control grows by the same number of pixels as the form is widened (see Figure 4-13). When the unanchored direction is resized, the control is moved to keep the portion of space between the unanchored sides and the control the same, but the control is not resized in that direction.

The left-and-right-anchored control stretches as the form widens
Figure 4-13. The left-and-right-anchored control stretches as the form widens
Anchored to three sides

The control resizes between the two anchor sides that are opposite to each other and remains the same distance from the single anchor border, as shown in Figure 4-14.

Anchored to top, left, and right, the edges of the control remain a fixed distance from all but the bottom edge of the form
Figure 4-14. Anchored to top, left, and right, the edges of the control remain a fixed distance from all but the bottom edge of the form
Anchored to all four sides

The control is continually resized with the form. All its sides stay the same distance from all anchored form borders, as shown in Figure 4-15.

Anchored to top, left, bottom, and right, the proportions of the control change in concert with the form’s proportions
Figure 4-15. Anchored to top, left, bottom, and right, the proportions of the control change in concert with the form’s proportions

Anchoring also applies to panels and other containers that can include subordinate controls.

See Also

See Recipe 4.11 for details on the Dock property, which you can use to attach a control to one side of a form’s client area. The Dock and Anchor properties cannot be used at the same time on the same control. The last one you set on that control is the one used.

4.13. Limiting the Sizing of a Form

Problem

You want the user to be able to resize a form, but within limits.

Solution

Use the MinimumSize and MaximumSize properties of the form to limit the user’s adjustments of the form’s size. As with the standard Size property, these two properties encapsulate separate width and height values. Figure 4-16 shows these settings in use in the Properties panel.

MaximumSize and MinimumSize properties in use
Figure 4-16. MaximumSize and MinimumSize properties in use

Discussion

These properties do limit the size of the form, whether the user is resizing the form directly or your code sets the Size property. You will usually want to set the form’s FormBorderStyle property to Sizable, and you must set the MaximizeBox property to False (or in some other way hide the maximize box, such as by setting the ControlBox property to False).

4.14. Centering a Form

Problem

You want a form to be in the center of the display when it first appears.

Solution

Set the form’s StartPosition property to CenterScreen.

Discussion

That was easy, but there may be cases where you need to set this property to Manual, but you still want the form to appear centered sometimes. To accomplish this, add the following code to the Load event handler for your form:

	Me.Location = New Point(( _
	   Screen.PrimaryScreen.Bounds.Width - Me.Width) / 2, _
	   (Screen.PrimaryScreen.Bounds.Height - Me.Height) / 2)

4.15. Creating and Moving a Borderless Form

Problem

You want to display a form without any of the typical window border elements. Also, you want the user to be able to move the window around by clicking and dragging a PictureBox control.

Solution

Sample code folder: Chapter 04\MoveBorderlessForm

Turning off the border elements is easy: set the form’s FormBorderStyle property to None. Then you can manage the drawing of the form elements yourself.

Creating a fake titlebar that moves the form is a little more involved. Create a new Windows Forms application, and add two controls: a Button control named ActClose and a PictureBox control named DragBar. Change the button’s Text property to Close. Change the picture box’s BackColor property to ActiveCaption, one of the system colors. Also, change the form’s FormBorderStyle property to None. The form should look something like Figure 4-17.

A borderless form with a pretend titlebar
Figure 4-17. A borderless form with a pretend titlebar

Now, add the following source code to the form’s code template:

	Const HT_CAPTION As Integer = &H2
	Const WM_NCLBUTTONDOWN As Integer = &HA1

	Private Sub DragBar_MouseDown(ByVal sender As Object, _
	      ByVal e As System.Windows.Forms.MouseEventArgs) _
	      Handles DragBar.MouseDown
	  ' ----- When the user clicks the left mouse button, act
	  '       as if the user actually clicked on the form's
	  '       title bar.
	  If (e.Button = Windows.Forms.MouseButtons.Left) Then
	      ' ----- Don't hold on to the mouse locally.
	      DragBar.Capture = False

	      ' ----- Trick the form into thinking it received a
	      '       title click.
	      Me.WndProc(Message.Create(Me.Handle, WM_NCLBUTTONDOWN, _
	         CType(HT_CAPTION, IntPtr), IntPtr.Zero))
	  End If
	End Sub
	Private Sub ActClose_Click(ByVal sender As System.Object, _
	      ByVal e As System.EventArgs) Handles ActClose.Click
	   Me.Close( )
	End Sub

Run the program, and drag the colored picture box control to move the form around the display.

Discussion

All of the activity within a Windows form happens through messages being processed through a Windows procedure, or WndProc. This method has existed since the introduction of Windows. The .NET Framework put a bunch of pretty classes around the messy parts of this messaging system, but it’s still there, and you can interact with it to suit your needs.

Normally, when you left-click on a form window (or a control, which is just a different type of window), a WM_LBUTTONDOWN message is passed to the relevant Windows procedure. That message ultimately triggers a call to one of your form’s MouseDown event handlers.

Your application includes a “message pump” that makes calls to each form’s WndProc procedure for message processing. But there is nothing to stop you from calling that procedure yourself. In fact, it’s exposed as a form class member.

When the DragBar picture box control receives the mouse down event, it says, “Hey, I’ll just send a fake message to my window’s WndProc routine so that it thinks the user clicked on the titlebar.” And that’s what the code does. It sends a WM_NCLBUTTONDOWN message to the form. The “NCL” part of that code means “Non-Client,” the area that contains the titlebar and borders. The HT_CAPTION flag tells the message that the click occurred in the caption area (the titlebar). This is all that’s needed to trick the form.

4.16. Creating a Fading Form

Problem

You want a form to fade out and disappear.

Solution

Sample code folder: Chapter 04\FadingForm

Use the form’s Opacity property to slowly fade it out. Create a new Windows Forms application, and add a Button control named ActClose to the form. Change the button’s Text property to Close. Then add the following source code to the form’s code template:

	Private Sub ActClose_Click(ByVal sender As Object, _
	      ByVal e As System.EventArgs) Handles ActClose.Click
	   ' ----- Fade out the form.
	   Dim counter As Integer

	   For counter = 90 To 10 Step -20
	      Me.Opacity = counter / 100
	      Me.Refresh()
	      Threading.Thread.Sleep(50)
	   Next counter

	   Me.Close()
	End Sub

Run the program, and click on the Close button to see the form fade away.

Discussion

You’ll find that on some systems, the form momentarily blinks to black right when it makes the transition from an opacity of 1.0 to any other opacity value. On such systems, setting the Opacity property to a non-1.0 value during the Load event handler still causes a blink, but it does so when the form first opens, not during the cool fadeout.

	Private Sub AboutProgram_Load(ByVal sender As Object, _
	      ByVal e As System.EventArgs) Handles Me.Load
	   ' ----- Prepare the form for later fade-out.
	   Me.Opacity = 0.99
	End Sub

4.17. Creating a Nonrectangular Form

Problem

You want to display a form that is nonrectangular; that is, you want some of the form to be invisible.

Solution

Sample code folder: Chapter 04\PartialInvisibility

Use the form’s TransparencyKey property to identify a color that will be invisible. The sample code in this recipe uses fuchsia for its “invisible color,” but you can choose any color that meets your display requirements.

Create a new Windows Forms application. Change Form1’s FormBorderStyle property to None, its StartPosition property to CenterScreen, and its TransparencyKey property to Fuchsia. Then add the following source code to the form’s code template:

	Private Sub Form1_Click(ByVal sender As Object, _
	      ByVal e As System.EventArgs) Handles Me.Click
	   ' ----- Any click closes the form.
	   Me.Close()
	End Sub

	Private Sub Form1_Paint(ByVal sender As Object, _
	      ByVal e As System.Windows.Forms.PaintEventArgs) _
	      Handles Me.Paint
	   ' ----- Draw a nice logo form.
	   e.Graphics.Clear(Color.Fuchsia)
	   e.Graphics.FillRectangle(Brushes.Gold, 0.0F, _
	      Me.ClientRectangle.Height / 3.0F, _
	      CSng(Me.ClientRectangle.Width), _
	      Me.ClientRectangle.Height / 3.0F)
	   e.Graphics.FillPolygon(Brushes.Gold, New PointF() { _
	      New Point(Me.ClientRectangle.Width / 4, 0), _
	      New Point(Me.ClientRectangle.Width / 2, _
	      Me.ClientRectangle.Height / 2), _
	      New Point(Me.ClientRectangle.Width / 4, _
	      Me.ClientRectangle.Height), _
	      New Point(0, Me.ClientRectangle.Height / 2)})
	   Dim largerFont = New Font(Me.Font.Name, 20)
	   e.Graphics.DrawString("My Nice Program", _
	      largerFont, Brushes.Black, 20, _
	      (Me.ClientRectangle.Height / 2) - _
	      (largerFont.Height / 2))
	   End Sub

When you run the program, it appears similar to the display in Figure 4-18. (We left the development environment behind the form so that you could see the invisibility.)

A form with transparent portions
Figure 4-18. A form with transparent portions

Discussion

The initial release of Visual Basic 2005 included a bug that prevented the transparency color from properly appearing as transparent in some cases. Specifically, if your form included an image that contained the transparency color, and the workstation was using more than 24 bits of color for its display, the image appeared as opaque. To get around this problem, you need to set transparency on the image manually before you draw it:

	Private Sub Form1_Paint(ByVal sender As Object, _
	      ByVal e As System.Windows.Forms.PaintEventArgs) _
	      Handles Me.Paint
	    ' ----- This code assumes that the form's
	    '       TransparencyKey property is "Fuchsia".
	    Dim logoImage As Bitmap = Bitmap.FromFile( _
	       "C:\MyLogo.bmp")
	    logoImage.MakeTransparent(Color.Fuchsia)
	    e.Graphics.DrawImage(logoImage, 0, 0)
	End Sub

The Microsoft Knowledge Base number for this article is 822495.

See Also

Recipe 9.10 discusses invisibility colors and the TransparencyKey property in more detail.

4.18. Changing Menus at Runtime

Problem

You want to customize the menu structure in your main form at runtime. The structure should be based on settings made available by some user-configurable method.

Solution

Sample code folder: Chapter 04\RuntimeMenus

The menu-specific classes included in the Windows Forms library can be created at either design time or runtime. This recipe’s code adds a basic menu to a form at design time and enhances it at runtime by adding the user’s Internet Explorer “Favorites” to one of the menus.

Create a new Windows Forms application, and add a MenuStrip control named MainMenu to the form. Perform the following actions on this menu:

  • Add a top-level menu named MenuFile, using &File for its Text property.

  • Add a top-level menu named MenuFavorites, using Fa&vorites for its Text property.

  • Add a menu item named MenuExitProgram that is subordinate to MenuFile, using E&xit for its Text property. Set its ShortcutKeys property to Alt+F4.

  • Add a menu item named MenuNoFavorites that is subordinate to MenuFavorites, using (empty) for its Text property. Set its Enabled property to False.

Figure 4-19 shows a partial look at this form’s menu structure in design mode.

The initial menus for the runtime menu sample
Figure 4-19. The initial menus for the runtime menu sample

Next, replace the form’s code template with the following code. I’ve highlighted the lines that do the actual adding of menu items:

	Imports MVB = Microsoft.VisualBasic

	Public Class Form1
	   Private Declare Auto Function GetPrivateProfileString _
	      Lib "kernel32" _
	      (ByVal AppName As String, _
	      ByVal KeyName As String, _
	      ByVal DefaultValue As String, _
	      ByVal ReturnedString As System.Text.StringBuilder, _
	      ByVal BufferSize As Integer, _
	      ByVal FileName As String) As Integer

	   Private Sub MenuExitProgram_Click( _
	         ByVal sender As System.Object, _
	         ByVal e As System.EventArgs) _
	         Handles MenuExitProgram.Click
	      ' ----- Exit the program.
	      Me.Close()
	   End Sub

	   Private Sub Form1_Load(ByVal sender As Object, _
	         ByVal e As System.EventArgs) Handles Me.Load
	      ' ----- Scan through the user's "Favorites" and
	      '       add them as menu items.
	      Dim favoritesPath As String

	      ' ----- Determine the location of the "Favorites"
	      '       folder.
	      favoritesPath = Environment.GetFolderPath( _
	         Environment.SpecialFolder.Favorites)
	      If (favoritesPath = "") Then Return
	      If (My.Computer.FileSystem.DirectoryExists( _
	         favoritesPath) = False) Then Return

	      ' ----- Call the recursive routine that builds the menu.
	      BuildFavorites(MenuFavorites, favoritesPath)

	      ' ----- If favorites were added, hide the
	      '       "no favorites" item.
	      If (MenuFavorites.DropDownItems.Count > 1) Then _
	         MenuNoFavorites.Visible = False
	   End Sub

	   Private Sub BuildFavorites(ByVal whichMenu As _
	         ToolStripMenuItem, ByVal fromPath As String)
	      ' ----- Given a starting directory, add all files
	      '       and directories in it to the specified menu.
	      '       Recurse for suborindate directories.
	      Dim oneEntry As String
	      Dim menuEntry As ToolStripMenuItem
	      Dim linkPath As String
	      Dim displayName As String

	      ' ----- Start with any directories.
	      For Each oneEntry In My.Computer.FileSystem. _
	            GetDirectories(fromPath)
	         ' ----- Create the parent menu, but don't
	         '       attach it yet.
	         menuEntry = New ToolStripMenuItem( _
	            My.Computer.FileSystem.GetName(oneEntry))
	         ' ----- Recurse to build the sub-directory branch.
	         BuildFavorites(menuEntry, oneEntry)

	         ' ----- If that folder contained items,
	         '       then attach it.
	         If (menuEntry.DropDownItems.Count > 0) Then _
	             whichMenu.DropDownItems.Add(menuEntry)
	      Next oneEntry

	      ' ---- Next, build the actual file links. Only
	      '      look at ".url" files.
	      For Each oneEntry In My.Computer.FileSystem. _
	            GetFiles(fromPath, FileIO.SearchOption. _
	            SearchTopLevelOnly, "*.url")
	         ' ----- Build a link based on this file. These
	         '       files are old-style INI files.
	         linkPath = GetINIEntry("InternetShortcut", _
	            "URL", oneEntry)
	         If (linkPath <> "") Then
	            ' ----- Found the link. Add it to the menu.
	            displayName = My.Computer.FileSystem. _
	               GetName(oneEntry)
	            displayName = MVB.Left(displayName, _
	               displayName.Length - 4)
	            menuEntry = New ToolStripMenuItem(displayName)
	            menuEntry.Tag = linkPath
	            whichMenu.DropDownItems.Add(menuEntry)

	            ' ----- Connect this entry to the event handler.
	            AddHandler menuEntry.Click, _
	               AddressOf RunFavoritesLink
	         End If
	      Next oneEntry
	   End Sub

	   Private Sub RunFavoritesLink( _
	         ByVal sender As System.Object, _
	         ByVal e As System.EventArgs)
	      ' ----- Run the link.
	      Dim whichMenu As ToolStripMenuItem

	      whichMenu = CType(sender, ToolStripMenuItem)
	      Process.Start(whichMenu.Tag)
	   End Sub

	   Private Function GetINIEntry(ByVal sectionName As String, _
	         ByVal keyName As String, _
	         ByVal whichFile As String) As String
	      ' ----- Extract a value from an INI-style file.
	      Dim resultLength As Integer
	      Dim targetBuffer As New System.Text.StringBuilder(500)

	      resultLength = GetPrivateProfileString(sectionName, _
	         keyName, "", targetBuffer, targetBuffer.Capacity, _
	         whichFile)
	      Return targetBuffer.ToString()
	   End Function
	End Class

Run the program, and access its Favorites menu to browse and open the current user’s Internet Explorer favorites.

Discussion

The bulk of this recipe’s code deals with scanning through a directory structure and examining each file and subdirectory. Most of the files in the “Favorites” folder have a .url extension and contain data in an “INI file” format.

Here’s a sample link to a popular search engine:

	[DEFAULT]
	BASEURL=http://www.google.com/
	[InternetShortcut]
	URL=http://www.google.com/

The last “URL=” line provides the link we need to enable favorites support in our program.

The important part of the program is the building of the menu structure. Each menu item attached to the form’s main menu MenuStrip control is a related ToolStripMenuItem class instance. These can be attached to the menu at any time through its DropDownItems collection. Each menu item in turn has its own DropDownItems collection that manages subordinate menu items.

To make each new menu item do something, as you add them, connect them to the previously written RunFavoritesLink method:

	AddHandler menuEntry.Click, AddressOf RunFavoritesLink

4.19. Creating Shortcut Menus

Problem

You want to display a custom shortcut menu to users when they right-click on a form or one of its controls.

Solution

Sample code folder: Chapter 04\ShortcutMenus

Use the ContextMenuStrip control to design a shortcut menu (also called a context or pop-up menu) that you can attach to the controls (or form) of your choice.

Create a new Windows Forms application, and add a ContextMenuStrip control named MainShortcutMenu to the form. When you select that control, it adds a temporary standard menu to the control that you can use to add new menu items (see Figure 4-20).

Shortcut menus in design mode
Figure 4-20. Shortcut menus in design mode

Add two menu items to this shortcut menu:

  • A menu item named MenuHello, using Say Hello for its Text property

  • A menu item named MenuGoodbye, using Say Goodbye for its Text property

Select the form itself, and then change its ContextMenuStrip property to MainShortcutMenu.

Now, add the following source code to the form’s code template:

	Private Sub MenuHello_Click(ByVal sender As System.Object, _
	      ByVal e As System.EventArgs) Handles MenuHello.Click
	   MsgBox("Hello")
	End Sub

	Private Sub MenuGoodbye_Click(ByVal sender As System.Object, _
	      ByVal e As System.EventArgs) Handles MenuGoodbye.Click
	   MsgBox("Goodbye")
	End Sub

Run the program, and right-click on the form. The shortcut menu will present itself, as shown in Figure 4-21. Clicking on the items puts up a message box saying “Hello” or “Goodbye.”

The shortcut menu in use
Figure 4-21. The shortcut menu in use

Discussion

Each form and control includes a ContextMenuStrip property that you can assign to any ContextMenuStrip control included with your form. You can create as many shortcut menus as needed for your controls.

Some controls, such as the TextBox control, already include default shortcut menus. If you wish to enhance one of these menus, you will have to design your own menu from scratch and provide your own implementations for menu items previously found in that control’s shortcut menu.

Get Visual Basic 2005 Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.