Reviewing the Forms Namespace
This section offers an overview of some of the members of the System.Windows.Forms namespaceas opposed to those in the System.Web namespace, commonly referred to as WebFormsthat you will use frequently to design Windows applications.
Visual Basic .NET allows you to write an Imports statement that includes the class name . For example, Imports System.Windows.Forms.Form is valid in Visual Basic .NET, but you can't use the class, Form, in C# (C Sharp).
A discussion of all the members of the System.Windows.Forms namespace and their respective elements would require a book of its own. Hence, we will limit our discussion to a sampling of the classes and other elements of the Forms namespace. For a complete reference, check out the System.Windows.Forms namespace help topic. (Throughout the rest of this section, we will refer to the System.Windows.Forms namespace as the Forms namespace for brevity.)
Classes in the Forms Namespace
There are dozens of classes in the Forms namespace. In this section we will cover a few of them and demonstrate some interesting characteristics or uses for each of the classes covered. (Keep in mind that the list isn't comprehensive.)
The Application class is similar to the VB6 App object. In Visual Basic .NET, the application class is defined to contain all shared members; an alternative would be to implement the Application object as a singleton instance.
Only one instance of a singleton class is created. Singleton objects are created implicitly or on demand and often represent a single physical object. For example, a Printer object might represent the notion of the single printer in use at any given time.
The Application class contains shared methods and events that allow you to manage a Windows application. The Application class represents a single Windows application. You can use the shared ExecutablePath property to get the path of the executable that started the application.
The DoEvents method has been moved to the Application class. DoEvents processes all messages in the message queue. Just as in VB6, you will want to call Application. DoEvents when you are performing loop- intensive processes like loading a file, filling a list, or processing a custom sort ; if you don't, your application may appear to be sluggish when it comes to repainting or responding to user feedback.
DoEvents can cause code such as event handlers to be re-entrant .
Exit and ExitThread
The Application.Exit method causes all message queues to shut down and closes all forms. The Exit method doesn't force the application to shut down but does cause the Application.Run method to return.
If you want to exit the current thread only, call Application.ExitThread. Keep in mind that Visual Basic .NET supports multiple threading, so exiting a thread may become a common occurrence.
Applications tend to have a tremendous amount of idle time. To get background tasks to process, we implemented Timer event handlers in VB6 and we can use the Idle event in Visual Basic .NET.
The Idle event is raised when your application isn't actively processing. Idle processor time may be less prevalent with multithreaded Visual Basic .NET, but is likely to still exist. Use the Idle event to perform short, staccato tasks that won't adversely affect your application's performance if it's not called occasionally. Listing 15.1 demonstrates using the Idle event to update a clock on a status bar.
Listing 15.1 Using the Application.Idle event to perform lightweight background processing
1: Private Sub OnIdle(ByVal sender As Object, _ 2: ByVal e As System.EventArgs) 3: 4: StatusBarPanelClock.Text = TimeOfDay 5: 6: End Sub 7: 8: Private Sub Form1_Load(ByVal sender As System.Object, _ 9: ByVal e As System.EventArgs) Handles MyBase.Load 10: 11: AddHandler Application.Idle, AddressOf OnIdle 12: 13: End Sub
The Application.Idle event requires an EventHandler delegate; that is, it requires the address of a procedure that has two parameters: Object and System.EventArgs. The OnIdle procedure was added manually and the delegate was created and associated with the Application.Idle event on line 11 of Listing 15.1. (You can also define a WithEvents statement and add the Handles clause to the OnIdle event handler, as demonstrated in Chapter 8, "Adding Events.")
The StatusBar was added from the Windows Forms tab of the toolbox and the panel StatusBarPanelClock was added using the Panels collection in the Properties window. (Also, set the StatusBar.ShowPanels property to True.)
If you need lightweight processing, use the Idle event of the Application object. If you need serious processing power, the best way to get it is to create a new thread, instead of using a Timer or the Idle event.
Context menus are all those menus that pop up when you right-click over controls that have a menu associated with them. Context menus are also referred to as pop-ups, speed menus, or right-click menus . The ContextMenu control is added to the component tray, as shown in Figure 15.1.
Figure 15.1. Nonvisual controls are added to the component tray as shown.
Context menus work by adding menu items, implementing event handlers to respond to menu clicks, and associating the context menu with one or more controls. Essentially, a ContextMenu control is designed like any menu (see the section on the Menu class coming up) and is associated with a second control's ContextMenu property. For example, to have ContextMenu1 (shown in Figure 15.1) pop up when the user right-clicks over the form shown in the figure, assign Form1's ContextMenu property to ContextMenu1 (see Figure 15.2).
Figure 15.2. Associating a ContextMenu with a control via the ContextMenu property.
The Cursor class represents the iconic pointing device. There is a collection of cursors that you can choose from to indicate what is occurring internally in your application. For example, if you have a long startup process, you might want to display the AppStarting cursor. The Load event demonstrated earlier isn't particularly long, but we will use it to demonstrate how to change cursors.
Listing 15.2 Displaying the AppStarting cursor and using a resource protection block to make sure the cursor is reset
1: Private Sub Form1_Load(ByVal sender As System.Object, 2: ByVal e As System.EventArgs) Handles MyBase.Load 3: 4: Cursor = Cursors.AppStarting 5: Try 6: AddHandler Application.Idle, AddressOf OnIdle 7: Finally 8: Cursor = Cursors.Default 9: End Try 10: 11: End Sub
Use a Try..Finally block to ensure that resources, like the Cursor, are managed.
Listing 15.2 displays the AppStarting cursor while the application is loading (line 4) and uses a resource protection blockrepresented by the Try..Finally..End Try blockto ensure that the cursor is reset to the default cursor. There is nothing more confusing than an inappropriate cursor.
The Cursors class contains all shared properties, each representing a specific cursor object. In Listing 15.2, the Cursors.AppStarting and the Cursors.Default properties are demonstrated.
The Cursors class contains methods for hiding and showing the cursor, changing the position, and customizing the appearance of the cursor. For example, you can call Cursor.DrawStretched to make a small image fill up the cursor image space.
The FileDialog class is an abstract class. FileDialog is the base class for the OpenFileDialog and SaveFileDialog controls. OpenFileDialog and SaveFileDialog replace the CommonDialog from VB6 and allow you to navigate through the file system to open and save files respectively.
The easiest way to use the FileDialog controls is to add one each to a form and call the ShowDialog method. Comparing the return value of ShowDialog to one of the DialogResult enumerations will allow you to determine user feedback.
FileDialog controls display the same dialog boxes you see when you are interacting with Windows (see Figure 15.3).
Figure 15.3. OpenFileDialog displays the same dialog box used by the Windows Explorer.
The HelloWorld.sln demonstrates several capabilities of the RichTextBox control, including the ability to load and save rich text or plain text files. To load the contents of a text file into a RichTextBox control, call RichTextBox.LoadFile( path ). An overloaded version allows you to indicate what kind of text is in the file. By default, the RichTextBox control attempts to load the file as rich text. If the file contains plain text, an exception is raised. To indicate that you want to load a plain text file, call the file as follows :
Passing the enumerated value PlainText indicates the type of contents.
Similar to the CommonDialog in VB6, it's helpful to specify a value for the Filter property to help constrain the type of files returned in the list. (The Filter property is applicable to the SaveFileDialog and OpenFileDialog. Assuming that you have an OpenFileDialog1 control added to your application, the following statement would add filters to the Files of Type list (shown in Figure 15.3).
OpenFileDialog1.Filter = "Text Files (*.txt)*.txt" & _ "Rich Text (*.rtf)*.rtfAll Files(*.*)*.*"
The same filter would work adequately for a SaveFileDialog, too. The following code fragment demonstrates how to initialize the filter, show the Open dialog box, and load the value of a file into a RichTextBox.
OpenFileDialog1.Filter = "Text Files (*.txt)*.txt" & _ "Rich Text (*.rtf)*.rtfAll Files(*.*)*.*" If (OpenFileDialog1.ShowDialog() = DialogResult.OK) Then RichTextBox.LoadFile(FileName, RichTextBoxStreamType.PlainText) End If
Similar code could be used to save the contents of the RichTextBox.
Forms are full-fledged classes in Visual Basic .NET with no hinky stuff going on in the background. Form classes have constructors and destructors and can be created and used like any other class. Distinct from VB6, too, is the fact that forms are contained in a .vb file as a class, and the file can contain more than one class, structure, or module.
The visual description of a form is contained in a file with a .resx extension. All the code is contained in a file with a matching name and a .vb extension.
Because this chapter is about using Windows Forms as well as the Forms namespace, we will look at how to take full advantage of the Form class after we finish a tour of the Forms namespace.
The Help class encapsulates the HTML Help 1.0 Engine. You can't create an instance of the Help class. You will need to add a HelpProvider control to your application and call the static methods Help.ShowHelp and Help.ShowHelpIndex.
To show the help index, call the shared method Help.ShowHelpIndex. If you want to associate an HTML help file with controls on your form, use the HelpProvider control. The code fragment that follows demonstrates how to display the demo help file index created with RoboHelp HTML:
Help.ShowHelpIndex(Me, "..\ ..\ Help\ DemoHelp\ DemoHelp.chm")
The help window is shown in Figure 15.4.
Figure 15.4. The DemoHelp file created for the HelloWorld, Windows Forms sample appli-cation.
RoboHelp is a product developed by Blue Sky Software. Discussions of RoboHelp are beyond the scope of this book, but RoboHelp HTML is very easy and intuitive to use.
The example uses a relative file path. Alternatively, the HelpNamespace property of a HelpProvider could be used to satisfy the url argument containing the help file.
The Menu class is the abstract base class for menus in Visual Basic. NET. The sample HelloWorld application demonstrates using the MainMenu and MenuItems controls.
Unlike menus in VB6, menus are classes in Visual Basic .NET. You can add a MainMenu control to the component tray, and the menu design process is WYSIWYG. Visual Basic .NET doesn't use a separate dialog box for menus. To create the sample menu demonstrated in the HelloWorld.sln for Chapter 15, follow these steps:
Notice that when you select a menu with text, a child and sibling position (with the text "Type Here") are added automatically.
To insert child or sibling menus, select the child or sibling at the insertion point and press the Insert key. Use the same technique to delete child or sibling menus. With any menu item selected, you can also use the context menu in the IDE for managing menus, but you probably won't need to.
The MainMenu and all MenuItems are objects. You can refer to the main menu or its items directly by name, or you can access menu items through the MainMenu.MenuItems collection. The following statement shows the text property of the first menu's submenu:
In the HelloWorld.sln example, the message box will display the text "&New".
The MessageBox class has one practical, shared, overloaded method named Show. MessageBox.Show displays a dialog box identical to the MsgBox function from VB6. More than likely, the MsgBox function in Visual Basic .NET is implemented in terms of the MessageBox class.
The MsgBox function is introduced in the Microsoft.VisualBasic namespace and isn't part of the CLR. You can still use the MsgBox function if you would like to, but it's the MessageBox class in Systems.Windows.Forms that's part of the CLR. There are 12 overloaded Show methods in the MessageBox class; for the most part, you will use MessageBox.Show just as you would the old MsgBox function.
The preceding text yields the same visual result as MsgBox("Hello World!"). IntelliSense is a quick way to identify acceptable variations of methods and other class members as you are developing your code. When you type the Show method and then type its opening parenthesis, for example, IntelliSense displays a scrollable list of all 12 versions of the Show method.
The MouseEventArgs class encapsulates the information necessary to determine the mouse state when a mouse event occurs. When you create a mouse event handler, you will get a MouseEventArgs parameter as the second argument of the event handler.
Fundamental members of the MouseEventArgs class include the Button, Clicks, Delta, X, and Y properties. Button indicates which mouse button was clicked. Clicks indicates the number of times the button was clicked. Delta indicates the number of detents the mouse wheel was rotated as a signed value ( detents are the notches on the internal mouse wheel). Delta is signed. A positive number indicates a mouse wheel forward movement, and a negative number indicates backward movement. X and Y indicate the x and y offsets of the mouse position. The top-left corner of a control is 0, 0 and the lower-right corner is width, height.
The NativeWindow class provides a low-level encapsulation of the WndProc and window Handle. NativeWindow implements AssignHandle, CreateHandle, DestroyHandle, and the WndProc procedure.
The WindowTarget property allows you to assign OnMessage and OnHandleChange delegates to a window control, providing you with low-level message access and handle change notification.
Assigning the WindowTarget property of a window control to a NativeWindow variable provides you with direct access to the underlying native Windows handle and window procedures of a control. IntelliSense provides no clue that WindowTarget exists, suggesting that WindowTarget has little pedestrian use.
Some members are considered advanced members by Microsoft developers. If you choose Tools, Options, and choose the Basic folder of the Text Editor Options, you can uncheck Hide Advanced Members. Unchecking this option allows IntelliSense to show advanced members, but WindowTarget and NativeWindow still will not be displayed.
Only Microsoft knows the reasoning here, which is one reason why many people are pushing for them to release all of the CLR code to developers. An excellent way to learn is by examining the code that supports the framework. Having access to the CLR code beats the pants off guesswork.
The NotifyIcon control is placed in the component tray. NotifyIcon allows you to associate an icon and ContextMenu with the control and shows the icon in the system tray (the rectangular area on the lower-right side of the taskbar).
Generally , system tray icons are used to access background applications. Figure 15.5 shows the U.S. flag in the system tray for the HelloWorld.sln.
Figure 15.5. Use the NotifyIcon control to add an icon and ContextMenu to the system tray shown.
To use the NotifyIcon control, add the control to the Visual Basic .NET component tray. Assign an icon to the NotifyIcon.Icon property and a ContextMenu to the NotifyIcon. ContextMenu property. When you run your application, the icon and menu will be accessible from the system tray.
Rich text is a type of hypertext, just as HTML is a type of hypertext. Rich text formatting allows control tags to be embedded in plain text, and a rich text control can evaluate the text to determine how to display the content. Rich text uses tags and was the precursor to HTML, WML, and XML.
HTML refers to hypertext markup language. WML refers to wireless markup language, and XML refers to extensible markup language. Markup languages generally are plain text with embedded tags that are interpreted and used to display the text in the format we specify.
The RichTextBox control makes it easy to load and save text and facilitates text formatting and searching. Sample code earlier in the chapter demonstrates how to load and retrieve text using a combination of the RichTextBox and dialog controls.
The Splitter control is a visual control used to manage graphical real estate between nested window controls. Applied to the HelloWorld sample program, you might choose to display two text controls. One control shows the formatted rich text, and the second shows the raw text. By placing the splitter control between the two TextBoxBase-based controls, you allow the end user to visually apportion screen real estate.
The StatusBar control is used to create a displayable text region at the bottom of forms. Generally, status bars are used to provide textual indicators of what is going on in your application. (In the HelloWorld sample application, the system time is displayed.)
The new StatusBar is object oriented. When you use the Panels property to create subpanels on the StatusBar, the panels themselves are generated as independent controls. The result is that you can refer to individual panels through the StatusBar.Panels collection or directly using the StatusBarPanel control (refer to Listing 15.1 for an example).
You are familiar with the Timer control from VB6. Use the Timer control when you want some lightweight processing to occur at regular intervals.
Of course, Visual Basic .NET supports multithreading, but you should avoid rampant thread usage. Consider using the Application.Idle event or the Timer control for simple background processing and use an additional thread if you have some processor-intensive process that can run suitably in the background.
The Timer control is located on the Windows Forms tab of the toolbox.
To use the Timer, add a Timer control to the component tray and double-click the control to generate the Tick event handler. Earlier in the chapter, we used the Idle event of the Application object to update the clock in the StatusBar of the HelloWorld.sln. Alternately, we could use a timer to ensure the clock was updated at regular intervals, rather than the sporadic intervals of the Idle event.
Interfaces in the Forms Namespace
As you can determine from the preceding sampling of some of the classes in the Forms namespace, Forms has a lot of good stuff in it. As you might expect, all the common controls for building Windows applicationssuch as combo boxes, text boxes, and labelsare in the Windows Forms namespace, in addition to several new interfaces.
This section provides a sampling of interfaces in the Forms namespace; however, as we have already discussed, you will need to explore the .NET Framework to find out more.
The Button and LinkLabel control implement this interface. The IButtonControl introduces the DialogResult property and the NotifyDefault and PerformClick methods. Implement this interface for controls that behave like buttons .
DialogResult returns an enumeration indicating the value returned to a parent form when the button is clicked. NotifyDefault notifies a control that it's the default control, and PerformClick invokes the click behavior of the control.
The Splitter control is the only control that implements the IMessageFilter interface. The only instance method you have to implement for this interface is the PreFilterMessage method. PreFilterMessage allows controls that implement this interface to filter the message. PreFilterMessage is a function; if it returns True, the control doesn't receive the message.
Call Application.AddMessageFilter to your class that implements IMessageFilter to add a message filter to the application message queue. Listing 15.3 demonstrates how to implement the IMessageFilter.
Listing 15.3 Implementing the IMessageFilter and viewing all messages processed in the application queue
1: Public Class Form1 2: Inherits System.Windows.Forms.Form 3: Implements IMessageFilter 4: 5: Private Function PreFilterMessage(ByRef m As Message) _ 6: As Boolean Implements IMessageFilter.PreFilterMessage 7: 8: Try 9: RichTextBox1.Text &= m.ToString & vbCrLf 10: Catch 11: RichTextBox1.Text = vbNullString 12: End Try 13: 14: Return False 15: End Function 16: 17: [ Windows Form Designer generated code ] 18: 19: Private Sub Form1_Load(ByVal sender As System.Object, _ 20: ByVal e As System.EventArgs) Handles MyBase.Load 21: 22: RichTextBox1.Clear() 23: Application.AddMessageFilter(Me) 24: End Sub 25: End Class
Filtering messages can cause sluggish application performance.
Listing 15.3 implements the IMessageFilter on line 3. Lines 5 through 15 implement the only required interface method. The PreFilterMessage method writes the message to a RichTextBox. By returning False, the application gets all messages (line 14). You could provide conditional logic and selectively filter messages by returning True. Line 23 adds the Formusing the reference to self, Meas a recipient of messages from the application's message queue.
The IWin32Window interface is implemented in the class Control. This interface provides access to a single property, Handle, enabling you to access the window handle of the control.
Structures in the Forms Namespace
In Chapter 4, "Macros and Visual Studio Extensibility," you learned that the Visual Basic .NET Structure replaces the VB6 Type but is more closely related to the C++ struct; Visual Basic .NET structures support methods, properties, and constructors. (Read Chapter 4 for a complete discussion of the Structure idiom in Visual Basic .NET.)
There are just a couple of ValueType definitions in the Forms namespace, including the most essential one, Message, and the LinkArea structure.
The Message structure implements the Windows messages. To create an instance of a Windows message structure, use the Create method. Two important members of the Message structure include the handle represented by the HWnd property and the Msg property.
The LinkArea structure represents the hyperlink portion of the LinkLabel control.
Delegates in the Forms Namespace
Delegates encapsulate function pointers to event handlers. A subclass of the Delegate class is the multicast delegate. There are several Delegate subclasses in the Forms namespace to facilitate the needs of handling specific kinds of device-related events. Keyboard handlers need key information. Mouse handlers need mouse state information, different from keyboard state information, and paint messages need a Graphics object indicating the paint surface.
This section covers some of the specific delegates necessary to make the rich graphical user interface environment work interactively.
The ControlEventHandler is used to respond to ControlAdded and ControlRemoved events. These two events fire when a control is added to or removed from a control's Controls collection.
The ControlEventHandler for ControlAdded and ControlRemoved events combined provides behavior similar to the Object Pascal Notification method. (If you are familiar with Delphi, this information will be useful.)
The event handler has the following form:
Sub eventhandlername (ByVal sender As Object, ByVal e As ControlEventArgs)
The second parameter contains a reference to the control being added or removed. These events are especially useful when you are adding and removing controls dynamically (as Visual Studio .NET does when you are designing in the IDE) and are maintaining references to those controls. The following code fragment demonstrates a scenario in which the ControlAdded event would be raised:
Dim AButton As New Button() AButton.Text = "Click" AButton.Name = "AButton" AButton.Top = 0 : AButton.Left = 0 Controls.Add(AButton) AddHandler AButton.Click, AddressOf MyClick
The first line creates the control, followed by lines that add a caption, name the control, and define a top-left corner position. The line Controls.Add(AButton) adds the button to the Controls collection and consequently raises the ControlAdd event. The last line demonstrates how to dynamically add an event handler for the button itself.
KeyEventHandler delegates describe method pointers that respond to KeyUp and KeyDown events. The second parameter is a KeyEventArgs class that contains the key that was pressed and any key modifiers like Ctrl, Alt, or Shift.
The KeyPressHandler responds to key-press events, passing a KeyPressEventArgs object to the event handler. The KeyPressEventArgs object contains a KeyChar property, which represents the value of the key including the modifiers. For example, pressing Shift+P raises key-down, key-press, and key-up events in that order. KeyDown and KeyUp get the raw key and modifier Shift and KeyPress would receive a capital P as the value of the KeyChar property.
The MouseEventHandler receives a MouseEventArgs object that contains the mouse state information as described in the earlier section "MouseEventArgs Class" in this chapter.
The PaintEventHandler receives a PaintEventArgs object that contains a Graphics object representing the graphics device context of the control that needs to respond to a paint event. (See the section later in this chapter, "Overriding the OnPaint Method," for an example of using the Graphics object.)
Enumerations in the Forms Namespace
You will find that enumerations make your code more comprehensible than literal integral values. There are a few in the Forms namespace that you will find useful.
The BootMode enumeration indicates the mode that the computer was booted in. The enumerations include FailSafe, FailSafeWithNetwork, and Normal.
FailSafe BootMode is commonly referred to as safe mode.
FailSafe mode indicates that the computer was started with basic files and drivers only. FailSafeWithNetwork includes the FailSafe mode and network drivers sufficient to enable networking. Normal mode is self-explanatory: It starts your computer with everything you would normally expect to start, load, and run.
CharacterCasing is the enumeration used to indicate the case of characters displayed in the TextBox control. The three enumerated values are Lower, Normal, and Upper.
The Day enumeration is used by the MonthCalendar. Day defines the days of the week in whole-word form: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, and Saturday. In addition, the Day enumeration contains a Default enumerated element, which is equivalent to 7.
The System.Windows.Forms.Keys enumeration contains a huge list of enumerated elements representing individual keys like the letter S, CapsLock, BrowserForward, and HanguelMode to facilitate the symbolic processing of keyboard input.