Standard Controls


A control is a class that derives from the System.Windows.Forms.Control base (either directly or indirectly) and is responsible for drawing a chunk of the container , which is either a form or another control. WinForms comes with several standard controls available by default on the Toolbox in VS.NET. These controls can be broken into the following ad hoc categories:

  • Action controls. Controls such as Button and Toolbar exist to allow the user to click on them to cause something to happen.

  • Value controls. Controls such as Label and PictureBox show the user a value, such as text or a picture, but don't allow the user to change the value. Other value controls, such as TextBox or DateTimePicker, allow the user to change the value being displayed.

  • List controls. Controls such as ListBox and ComboBox show the user a list of data. Other list controls, such as DataGrid, allow the user to change the data directly.

  • Container controls. GroupBox, Panel, and TabControl exist to contain and arrange other controls.

Although Appendix D: Standard WinForms Components and Controls lists and shows each of the standard controls, it's useful to consider each category for the features that the controls share in common.

Action Controls

The action controls are Button, ToolBar, MenuBar, and ContextMenu. [1] These controls exist to provide something for the user to click on to trigger an action in the application. Each of the available actions is labeled and, in the case of ToolBar, can have an optional image. The major event the action controls is the Click event:

[1] Technically, MenuBar and the ContextMenu classes aren't controls because they don't derive from the Control base class, but they fit so nicely into this category that we didn't have the heart to remove them. The details of these two components can be found in Chapter 2: Forms.

 
 Sub button1_Click(sender As Object, e As EventArgs)   MessageBox.Show("Ouch!") End Sub 

Except for Button, the rest of the action controls are actually containers of multiple subobjects that the user interacts with. For example, a MainMenu object contains one or more MenuItem objects, one for each menu item that can fire a Click event:

 
 Sub exitMenuItem_Click(sender As Object, e As EventArgs)   Me.Close() End Sub 

The ToolBar control also contains a collection of objects, of type ToolBarButton. However, when the user clicks, the event is sent for ToolBar itself, so the event handler is responsible for using the Button property of the ToolBarButtonClickEventArgs to figure out which button was pressed:

 
 Sub toolBar1_ButtonClick(_   sender As Object, e As ToolBarButtonClickEventArgs)   If e.Button = fileExitToolBarButton Then       Me.Close()   ElseIf e.Button = helpAboutToolBarButton Then       MessageBox.Show("The standard controls are cool")   End If End Sub 

Because menu items and toolbar buttons often result in the same action, such as showing the About box, it's good practice to centralize the code and call it from both event handlers:

 
 Sub FileExit()   ... End Sub Sub HelpAbout()   ... End Sub Sub fileExitMenuItem_Click(sender As Object, e As EventArgs)   FileExit() End Sub Sub helpAboutMenuItem_Click(sender As Object, e As EventArgs)   HelpAbout() End Sub Sub toolBar1_ButtonClick( _       Sender As Object, e As ToolBarButtonClickEventArgs)   If e.Button = fileExitToolBarButton Then       FileExit()   ElseIf e.Button = helpAboutToolBarButton Then       HelpAbout()   End If End Sub 

If you centralize the handling of an action, you don't have to worry about which controls trigger it; no matter how many do, all of them will get the same behavior.

While we're on the topic of ToolBar control, you may be curious as to how images are assigned to each button. Assigning an image to a toolbar button involves creating and indexing into an ImageList, which is a component that holds a list of images for use by controls that display images. Image lists are discussed later in this chapter.

Value Controls

The value controls make up the set of controls that show and optionally allow editing of a single value. They can be broken down further by the data type of the value:

  • String values: Label, LinkLabel, TextBox, RichTextBox, StatusBar

  • Numeric values: NumericUpDown, HScrollBar, VScrollBar, ProgressBar, TrackBar

  • Boolean values: CheckBox, Radio Button

  • Date values: DateTimePicker, MonthCalendar

  • Graphical values: PictureBox, PrintPreviewControl

The string value controls expose a property called Text that contains the value of the control in a string format. The Label control merely displays the text. The LinkLabel control displays the text as if it were an HTML link, firing an event when the link is clicked. The StatusBar control displays the text in the same way a Label does (but, by default, docked to the bottom of the container), but it also allows for multiple chunks of text separated into panels.

In addition to displaying text, the TextBox control allows the user to edit the text in single or multiline mode (depending on the value of Multiline property). The RichTextBox control allows for editing like TextBox but also supports RTF (Rich Text Format) data, which includes font and color information as well as graphics. When the Text value of either of these controls changes, the TextChanged event is fired .

All the numeric value controls expose a numeric Value property, whose value can range from the Minimum to the Maximum property. The difference is only a matter of which UI you'd like to show to the user. When the Value properties change, the ValueChanged property is fired.

The Boolean value controls ”CheckBox and RadioButton ”expose a Checked property that reflects whether or not they're checked. Both Boolean value controls can also be set to a third, "indeterminate" state, which is one of the three possible values exposed from the CheckState property. When the CheckState is changed, the CheckedChanged and CheckStateChanged events are fired.

The date value controls allow the user to pick one or more instances of the DateTime type. MonthCalendar allows the choice of beginning and ending dates as exposed by the SelectionRange property (signaled by the SelectionRangeChanged event). DateTimePicker allows the user to enter a single date and time as exposed by the Value property (signaled by the Value-Changed event).

The graphical value controls show images, although neither allows the images to be changed. The PictureBox control shows any image as set by the Image property. PrintPreviewControl shows, one page at a time, a preview of print data generated from a PrintDocument object (as described in Chapter 7: Printing).

List Controls

If one value at a time is good, then several values at a time must be better. The list controls ”ComboBox, CheckedListBox, ListBox, DomainUpDown, ListView, DataGrid, and TreeView ”can show more than one value at a time.

Most of the list controls ”ComboBox, CheckedListBox, ListBox, and DomainUpDown ”show a list of objects exposed by the Items collection. To add a new item you use this collection:

 
 Sub Form1_Load(sender As Object, e As EventArgs)   listBox1.Items.Add("an item") End Sub 

This sample adds a string object to the list of items, but you can add any object:

 
 Sub Form1_Load(sender As Object, e As EventArgs)   Dim bday As DateTime = DateTime.Parse("1995-08-30 6:02pm")   listBox1.Items.Add(bday) End Sub 

To come up with a string to display, the list controls that take objects as items call the ToString method. To show your own custom items in a list control, you simply implement the ToString method:

 
 Class Person   Dim myname As String   Dim myage As Integer   Public Sub New(name As String, age As Integer)       myname = name       myage = age   End Sub   Property Name() As String       Get           Return myname       End Get       Set           myname = Value       End Set   End Property   Property Age() As Integer       Get           Return myage       End Get       Set           myage = Value       End Set   End Property   Overrides Function ToString() As String       Return String.Format("{0} is {1} years old", Name, Age)   End Function End Class Sub Form1_Load(sender As Object, e As EventArgs)   Dim boys() As Person = {New Person("Tom", 7), _ New Person("John", 8)}   Dim boy As Person   For Each boy In boys       listBox1.Items.Add(boy)   Next End Sub 

Figure 8.1 shows the instances of the custom type shown in a ListBox control.

Figure 8.1. Custom Type Shown in a ListBox Control

Because the ListView control can show items with multiple columns and states, its Items collection is populated with instances of the ListView-Item class. Each item has a Text property, which represents the text of the first column, and then a collection of subitems that represent the rest of the columns:

 
 Sub Form1_Load(sender As Object, e As EventArgs)   Dim boys() As Person = { New Person("Tom", 7), _ New Person("John", 8)}   Dim boy As Person   For Each boy In boys       ' NOTE: Assumes Columns collection already has 2 columns       Dim item As ListViewItem = New ListViewItem()       item.Text = boy.Name       item.SubItems.Add(boy.Age.ToString())       listView1.Items.Add(item)   Next End Sub 

Figure 8.2 shows the multicolumn ListView filled via this code.

Figure 8.2. Multicolumn ListView

The TreeView control shows a hierarchy of items that are instances of the TreeNode type. Each TreeNode object contains the text, some optional images, and a Nodes collection containing subnodes. Which node you add to determines where the newly added node will show up in the hierarchy:

 
 Sub Form1_Load(sender As Object, e As EventArgs)   Dim parentNode As TreeNode = New TreeNode()   parentNode.Text = "Chris"   ' Add a node to the root of the tree   treeView1.Nodes.Add(parentNode)   Dim childNode As TreeNode = New TreeNode()   childNode.Text = "John"   ' Add a node under an existing node   parentNode.Nodes.Add(childNode) End Sub 

Figure 8.3 shows the result of filling a TreeView control using this sample code.

Figure 8.3. A Parent Node and a Child Node in a TreeView Control

The DataGrid control gets its data from a collection set by using the DataSource property:

 
 Sub Form1_Load(sender As Object, e As EventArgs)   Dim boys() As Person = {new Person("Tom", 7), _      New Person("John", 8)}   dataGrid1.DataSource = boys End Sub 

The DataGrid shows each public property of the objects in the collection as a column, as shown in Figure 8.4.

Figure 8.4. A DataGrid Showing a Collection of Custom Types

A DataGrid can also show hierarchical data and do all kinds of other fancy things. You'll find many more details about the DataGrid control in Chapter 13: Data Binding and Data Grids.

List Item Selection

Each of the list controls exposes a property to report the current selection (or a list of selections, if the list control supports multiple selections) and fires an event when the selection changes. For example, the following code handles the SelectedIndexChanged event of the ListBox control and uses the SelectedIndex property to pull out the currently selected object:

 
 Sub listBox1_SelectedIndexChanged(sender As Object, e As EventArgs)   ' Get the selected object   Dim selection As Object = listBox1.Items(listBox1.SelectedIndex)   MessageBox.Show(selection.ToString())   ' The object is still the same type as when we added it   Dim boy As Person = CType(selection, Person)   MessageBox.Show(boy.ToString()) End Sub 

Notice that the SelectedIndex property is an offset into the Items collection that pulls out the currently selected item. The item comes back as the "object" type, but a simple cast allows us to treat it as an instance of exactly the same type as when it was added. This is useful when a custom type shows data using ToString but has another characteristic, such as a unique identifier, that is needed programmatically. In fact, for the list controls that don't take objects, such as TreeView and ListView, each of the items supports a Tag property for stashing away unique ID information:

 
 Sub Form1_Load(sender As Object, e As EventArgs)   Dim parentNode As TreeNode = New TreeNode()   parentNode.Text = "Chris"   parentNode.Tag = "555-12-4545" ' Put in extra info   treeView1.Nodes.Add(parentNode) End Sub Sub treeView1_AfterSelect(sender As Object, e As TreeViewEventArgs)   Dim selection As TreeNode = treeView1.SelectedNode   Dim tag As Object = selection.Tag ' Pull out extra info   MessageBox.Show(tag.ToString()) End Sub 

List controls support either custom types or the Tag property but not both. The idea is that because the lists contain instances of custom types, any extra information can simply be kept as needed. Unfortunately, the lack of a Tag property makes it more difficult to associate ID information with simple types, such as strings. However, a simple wrapper will allow you to add a tag to a list item of any type:

 
 Class TaggedItem   Dim Item As Object   Dim Tag As Object   Public Sub New(item As Object, tag As Object)       Me.Item = item       Me.Tag = tag   End Sub   Override Function ToString() As String       Return Item.ToString()   End Function End Class Sub Form1_Load(sender As Object, e As EventArgs)   ' Add two tagged strings   comboBox1.Items.Add(New TaggedItem("Tom", "555-12-4547"))   comboBox1.Items.Add(New TaggedItem("John", "555-12-4546")) End Sub Sub comboBox1_SelectedIndexChanged(sender As Object, e As EventArgs)   Dim selection As TaggedItem = _       CType(comboBox1.Items(comboBox1.SelectedIndex), TaggedItem)   Dim tag As Object = selection.Tag   MessageBox.Show(tag.ToString()) End Sub 

The TaggedItem wrapper keeps track of an item and a tag. The ToString method lets the item decide how it should be displayed, and the Item and Tag properties expose the parts of the TaggedItem object for use in processing the current selection.

Container Controls

Whereas the list controls hold multiple objects, the job of the container controls (GroupBox, Panel, and TabControl) is to hold multiple controls. The Splitter control is not itself a container, but it can be used with container controls docked to a container's edge for sizing. All the anchoring, docking, splitting, and grouping principles covered in Chapter 2: Forms also apply to container controls. Figure 8.5 shows examples of container controls in action.

Figure 8.5. Container Controls in Action

Figure 8.5 shows a GroupBox on the left, docked to the left edge of the containing form, and a TabControl with two TabPage controls on the right, split with a Splitter control in the middle.

The GroupBox sets the caption of the group using its Text property. The Panel has no label. The TabControl is really a container of TabPage controls. It's the TabPage controls that contain other controls, and the Text property shows up as the label of the tab.

The only other interesting member of a container control is the Controls collection, which holds the list of contained controls. For example, the list box in Figure 8.5 is contained by the Controls collection of the group box:

 
 Sub InitializeComponent()   ...   ' groupBox1   Me.groupBox1.Controls.AddRange( _       New System.Windows.Forms.Control() { Me.listBox1 })   ...   ' Form1   Me.Controls.AddRange( _       New System.Windows.Forms.Control() { _           Me.tabControl1, _           Me.splitter1, _           Me.groupBox1 })   ... End Sub 

Notice in the form's InitializeComponent that the group box's Controls collection is used to contain the list box and that the form's Controls collection is used to contain the tab control, the splitter, and the group box. It's a child control's container that determines how a control is arranged. For example, when the list box's Dock property is set to Fill, the docking is relative to its container (the group box) and not to the form that actually creates the control. When a control is added to a container's Controls collection, the container control becomes the child control's parent. A child control can discover its container by using its Parent property.

ImageLists

In addition to showing text data, several of the controls ”including TabPage, ToolBarButton, ListView, and TreeView ”can show optional images. These controls get their images from an instance of the ImageList component. The ImageList component provides Designer support for adding images at design time and then exposes them by index number to controls that use them.

Each image-capable control exposes one or more properties of type ImageList. This property is named "ImageList" if the control supports a single set of images. But if the control supports more than one list of images, the property name contains a phrase that includes the term "ImageList." For example, the TabControl exposes the ImageList property for use by all the contained TabPage controls, whereas the ListView control exposes the LargeImageList, SmallImageList, and StateImageList properties for the three kinds of images it can display.

Regardless of the number of ImageList properties a control supports, when an item requires a certain image from the ImageList, the item exposes an index property to offset into the ImageList component's list of images. The following is an example of adding an image to each of the items in a TreeView control:

 
 Sub InitializeComponent()   ...   Me.treeView1 = New TreeView()   Me.imageList1 = New ImageList(Me.components)   ...   ' ImageList associated with the TreeView   Me.treeView1.ImageList = Me.imageList1   ...   ' Images read from Form's resources   Me.imageList1.ImageStream = ...   ... End Sub Sub Form1_Load(sender As Object, e As EventArgs)   Dim parentNode As TreeNode = New TreeNode()   parentNode.Text = "Chris"   parentNode.ImageIndex = 0 ' Dad image   parentNode.SelectedImageIndex = 0   treeView1.Nodes.Add(parentNode)   Dim childNode As TreeNode = New TreeNode()   childNode.Text = "John"   childNode.ImageIndex = 1 ' Son image   childNode.SelectedImageIndex = 1   parentNode.Nodes.Add(childNode) End Sub 

Using the Designer to associate images with the ImageList component causes the images themselves to be stored in form-specific resources. [2] InitializeComponent pulls them in at run time by setting the image list's ImageStream property; InitializeComponent also associates the image list with the tree view by setting the tree view's ImageList property. Each node in a tree view supports two image indexes: the default image and the selected image. Each of these properties indexes into the image list associated with the tree view. Figure 8.6 shows the result.

[2] Resources are covered in detail in Chapter 10: Resources.

Figure 8.6. A TreeView Using an ImageList

When you collect related images in an ImageList component, setting images in a control is as simple as associating the appropriate image list (or image lists) with the control and then setting each image index as appropriate. The control itself handles the work of drawing the image.

Owner-Draw Controls

Image lists allow you to augment the display of certain controls with an image. If you'd like to take over the drawing of a control, owner-draw controls support this very thing. An owner-draw control provides events that allow a control's owner (or the control itself) to take over the drawing chores from the control in the underlying operating system.

Controls that allow owner draw ”such as menus , some of the list controls, the tab page control, and status bar panel control ”expose a property that turns owner drawing on and then fires events to let the container know that it should do the drawing. For example, the ListBox control exposes the DrawMode property, which can be one of the following values from the DrawMode enumeration:

 
 Enum DrawMode   Normal ' Control draws its own items (default)   OwnerDrawFixed ' Fixed-size custom drawing of each item   OwnerDrawVariable ' Variable-size custom drawing of each item End Enum 

Figure 8.7 shows an owner-draw ListBox control that changes the style to Italics when it's drawing the selected item.

Figure 8.7. Owner-Drawn List Box

To handle the drawing for a ListBox, you first set the DrawMode property to something other than Normal (the default), and then you handle the ListBox control's DrawItem event:

 
 Sub InitializeComponent()   ...   Me.listBox1.DrawMode = DrawMode.OwnerDrawFixed   ... End Sub Sub listBox1_DrawItem(sender As Object, e As DrawItemEventArgs)   ' Draw the background   e.DrawBackground()   ' Get the default font   Dim drawFont As Font = e.Font   Dim ourFont As Boolean = False   ' Draw in italics if selected   If (e.State And DrawItemState.Selected) = _     DrawItemState.Selected Then     ourFont = True       drawFont = New Font(drawFont, FontStyle.Italic)   End If   ' Draw the listbox item   e.Graphics.DrawString(listBox1.Items(e.Index).ToString(), _       drawFont, New SolidBrush(e.ForeColor), e.Bounds)   If ourFont Then drawFont.Dispose()   ' Draw the focus rectangle   e.DrawFocusRectangle() End Sub 

The DrawItem method comes with the DrawItemEventArgs object:

 
 Class DrawItemEventArgs   Inherits EventArgs   ' Properties   Property BackColor() As Color   Property Bounds() As Rectangle   Property Font() As Font   Property ForeColor() As Color   Property Graphics() As Graphics   Property Index() As Integer   Property State() As DrawItemState   ' Methods   Overridable Sub DrawBackground()   Overridable Sub DrawFocusRectangle() End Class 

The DrawItem event is called whenever the item is drawn or when the item's state changes. The DrawItemEventArgs object provides all the information you'll need to draw the item in question, including the index of the item being drawn, the bounds of the rectangle to draw in, the preferred font, the preferred color of the foreground and background, and the Graphics object to do the drawing on. DrawItemEventArgs also provides the selection state so that you can draw selected items differently (as our example does). DrawItemEventArgs also gives you a couple of helper methods for drawing the background and the focus rectangle if necessary. You'll usually use the latter to bracket your own custom drawing.

When you set DrawMode to OwnerDrawFixed, each item's size is set for you. If you'd like to influence the size, too, you can set DrawMode to OwnerDrawVariable, and, in addition to doing the drawing in the DrawItem handler, you can specify the height in the MeasureItem handler:

 
 Sub InitializeComponent()   ...   Me.listBox2.DrawMode = OwnerDrawVariable   ... End Sub Sub listBox2_MeasureItem(sender As Object, e As MeasureItemEventArgs)   ' Make every even item twice as high   If ( e.Index Mod 2 = 0 ) Then e.ItemHeight = e.ItemHeight * 2 End Sub 

The MeasureItem event provides an instance of the MessageItemEvent-Args class, which gives you useful properties for getting and setting each item's height:

 
 Class MeasureItemEventArgs   Inherits EventArgs   ' Properties   Property Graphics() As Graphics   Property Index() As Integer   Property ItemHeight() As Integer   Property ItemWidth() As Integer End Class 

Figure 8.8 shows the effects of doubling the heights of the even items (as well as continuing to show the selection in italics).

Figure 8.8. An Owner-Draw List Box Using Variable Height

Unlike the DrawItem event, the MeasureItem event is called only once for every item in the control, so things such as selection state can't be a factor when you decide how big to make the space for the item.

ControlPaint

Often, owner drawing is used to draw a control that looks just like an existing Windows control but has one minor addition, such as an image added to a menu item. In those cases, you'd like to avoid spending any time duplicating the way every version of Windows draws its controls, and you can use the ControlPaint helper class for that purpose. The ControlPaint class has static members for drawing common controls, lines, grids, and types of text:

 
 NotInheritable Class ControlPaint   ' Properties   Shared Property ContrastControlDark() As Color   ' Methods   Shared Function CreateHBitmap16Bit(...) As IntPtr   Shared Function CreateHBitmapColorMask(...) As IntPtr   Shared Function CreateHBitmapTransparencyMask(...) As IntPtr   Shared Function Dark(...) As Color   Shared Function DarkDark(...) As Color   Shared Sub DrawBorder(...)   Shared Sub DrawBorder3D(...)   Shared Sub DrawButton(...)   Shared Sub DrawCaptionButton(...)   Shared Sub DrawCheckBox(...)   Shared Sub DrawComboButton(...)   Shared Sub DrawContainerGrabHandle(...)   Shared Sub DrawFocusRectangle(...)   Shared Sub DrawGrabHandle(...)   Shared Sub DrawGrid(...)   Shared Sub DrawImageDisabled(...)   Shared Sub DrawLockedFrame(...)   Shared Sub DrawMenuGlyph(...)   Shared Sub DrawMixedCheckBox(...)   Shared Sub DrawRadioButton(...)   Shared Sub DrawReversibleFrame(...)   Shared Sub DrawReversibleLine(...)   Shared Sub DrawScrollButton(...)   Shared Sub DrawSelectionFrame(...)   Shared Sub DrawSizeGrip(...)   Shared Sub DrawStringDisabled(...)   Shared Sub FillReversibleRectangle(...)   Shared Function Light(...) As Color   Shared Function LightLight(...) As Color End Class 

For example, you can use ControlPaint to draw disabled text in an owner-draw status bar panel:

 
 Sub statusBar1_DrawItem(sender As Object, e As StatusBarDrawItemEventArgs)   ' Panels don't draw with their BackColor   ' so it's not set to something reasonable and   ' therefore e.DrawBackground() isn't helpful.   ' Instead, use the BackColor of the StatusBar, which is the sender   Dim statusBar As StatusBar = CType(sender, StatusBar)   Dim mybrush As Brush = New SolidBrush(statusBar.BackColor)   e.Graphics.FillRectangle(SystemBrushes.Control, e.bounds)   ' Draw text as disabled   Dim format As StringFormat = New StringFormat()   format.LineAlignment = StringAlignment.Center   format.Alignment = StringAlignment.Center   ControlPaint.DrawStringDisabled( _       e.Graphics, "Hi!', Me.Font, Me.ForeColor, e.Bounds, format) End Sub 

What makes the ControlPaint class handy is that it takes into account the conventions between versions of the operating system about the latest way to draw whatever it is you're trying to draw. So, instead of manually trying to duplicate how Windows draws disabled text this time, we can let ControlPaint do it for us, as shown in Figure 8.9.

Figure 8.9. An Owner-Drawn Status Bar Panel Using ControlPaint

As nifty as ControlPaint is, as of .NET 1.1 it doesn't take theming into account. If you are using a themed operating system (such as Windows XP or Windows 2003 Server), the artifacts drawn by ControlPaint will not be themed. But even though ControlPaint doesn't support themed drawing, WinForms has some support for it in the standard controls, as discussed in Chapter 2: Forms.



Windows Forms Programming in Visual Basic .NET
Windows Forms Programming in Visual Basic .NET
ISBN: 0321125193
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net