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—aTextBox control and aButton 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 runtime. 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.
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. WhenForm1appears on the screen in a running program, it’s because somewhere in your program there is code that creates a new instance ofForm1and calls itsShowmethod:
(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 aTextBoxand aButton 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’sLoadevent 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.
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.
Iterating Through All Controls on a Form
Discussion
Create a new Windows Forms application, and add threeLabelcontrols toForm1. Name the controls whatever you want, and change theirTextproperties to anything you want as well. Next, add twoButton controls to the form, namedActRedandActNormal. Set theirTextproperties toRed andNormal, 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.
Figure 4-2. All labels set to red
All of a form’s controls appear in a collection accessed through the form’sControlsproperty. Because it is a standard collection, you can iterate through it using theFor Eachstatement, 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 theControlscollection, you can still determine their derived data types using theTypeOfstatement, 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.
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.
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’sHandlesclause to include all the control events it should handle.
Create a new Windows Forms application, and add three newTextBoxcontrols toForm1. By default, they are namedTextBox1,TextBox2, andTextBox3. Add aLabelcontrol namedShowInfo. 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, theShowInfolabel 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.
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.
You need to have some action occur on a regular basis in your form.
Solution
Sample code folder: Chapter 04\ClockTimer
Use aTimercontrol, and set it for the desired interval. Create a new Windows Forms application, and add aLabelcontrol namedCurrentTimeto the form. Also add aTimercontrol to the form, and name itClockTimer. Set itsIntervalproperty to1000, and set itsEnabled property toTrue. 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 Timercontrol’sIntervalproperty sets the time betweenTickevents in milliseconds (1,000 per second). Although you can set theIntervalas low as one millisecond, the timer’s resolution is limited by your system’s hardware and operating-system-level factors.
TheTickevent fires at approximately the interval you specify, if nothing more important is going on. If the code within yourTickevent handler is still running when the nextTickevent should occur, that subsequentTick 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.
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 faisl because the control is disabled. While you could check theEnabledflag 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 isTrue. Using theCanFocus()method provides a more accurate method of determining when it is safe to call theFocus()method.
Please check back next week for the continuation of this article.