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 I didn't have the heart to remove them. The details of these two components can be found in Chapter 2: Forms.

  void button1_Click(object sender, EventArgs e) {  MessageBox.Show("Ouch!");  }  

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:

 void exitMenuItem_Click(object sender, EventArgs e) {   this.Close(); } 

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:

 void toolBar1_ButtonClick(   object sender, ToolBarButtonClickEventArgs e) {  if( e.Button == fileExitToolBarButton ) {  this.Close();  }   else if( e.Button == helpAboutToolBarButton ) {  MessageBox.Show("The standard controls are cool");  }  } 

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:

 void FileExit() {...} void HelpAbout() {...} void fileExitMenuItem_Click(object sender, EventArgs e) {   FileExit(); } void helpAboutMenuItem_Click(object sender, EventArgs e) {   HelpAbout(); } void toolBar1_ButtonClick(   object sender, ToolBarButtonClickEventArgs e) {   if( e.Button == fileExitToolBarButton ) {     FileExit();   }   else if( e.Button == helpAboutToolBarButton ) {     HelpAbout();   } } 

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 ValueChanged 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:

 void Form1_Load(object sender, EventArgs e) {  listBox1.Items.Add("an item");  } 

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

 void Form1_Load(object sender, EventArgs e) {  DateTime bday = DateTime.Parse("1995-08-30 6:02pm");  listBox1.Items.Add(  bday  ); } 

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 {   string name;   int age;   public Person(string name, int age) {     this.name = name;     this.age = age;   }   public string Name {     get { return name; }     set { name = value; }   }   public int Age {     get { return age; }     set { age = value; }   }   public override string ToString() {     return string.Format("{0} is {1} years old", Name, Age);   } } void Form1_Load(object sender, EventArgs e) {   Person[] boys = { new Person("Tom", 7), new Person("John", 8) };   foreach( Person boy in boys ) {     listBox1.Items.Add(boy);   } } 

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 ListViewItem 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:

 void Form1_Load(object sender, EventArgs e) {   Person[] boys = { new Person("Tom", 7), new Person("John", 8) };   foreach( Person boy in boys ) {     // NOTE: Assumes Columns collection already has 2 columns  ListViewItem item = new ListViewItem();   item.Text = boy.Name;   item.SubItems.Add(boy.Age.ToString());   listView1.Items.Add(item);  } } 

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:

 void Form1_Load(object sender, EventArgs e) {   TreeNode parentNode = new TreeNode();   parentNode.Text = "Chris";  // Add a node to the root of the tree   treeView1.Nodes.Add(parentNode);  TreeNode childNode = new TreeNode();   childNode.Text = "John";  // Add a node under an existing node   parentNode.Nodes.Add(childNode);  } 

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:

 void Form1_Load(object sender, EventArgs e) {   Person[] boys = { new Person("Tom", 7), new Person("John", 8) };  dataGrid1.DataSource = boys;  } 

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:

 void listBox1_SelectedIndexChanged(object sender, EventArgs e) {   // Get the selected object   object selection = listBox1.Items[listBox1.SelectedIndex];   MessageBox.Show(selection.ToString());   // The object is still the same type as when we added it   Person boy = (Person)selection;   MessageBox.Show(boy.ToString()); } 

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:

 void Form1_Load(object sender, EventArgs e) {   TreeNode parentNode = new TreeNode();   parentNode.Text = "Chris";  parentNode.Tag = "000-00-0000"; // Put in extra info  treeView1.Nodes.Add(parentNode); } void treeView1_AfterSelect(object sender, TreeViewEventArgs e) {   TreeNode selection = treeView1.SelectedNode;  object tag = selection.Tag; // Pull out extra info  MessageBox.Show(tag.ToString()); } 

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 {   public object Item;   public object Tag;   public TaggedItem(object item, object tag) {     this.Item = item;     this.Tag = tag;   }   public override string ToString() {     return Item.ToString();   } } void Form1_Load(object sender, EventArgs e) {   // Add two tagged strings   comboBox1.Items.Add(new TaggedItem("Tom", "000-00-0000));   comboBox1.Items.Add(new TaggedItem("John", "000-00-0000")); } void comboBox1_SelectedIndexChanged(object sender, EventArgs e) {   TaggedItem selection =     (TaggedItem)comboBox1.Items[comboBox1.SelectedIndex];   object tag = selection.Tag;   MessageBox.Show(tag.ToString()); } 

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:

 void InitializeComponent() {   ...  // groupBox1   this.groupBox1.Controls.AddRange(   new System.Windows.Forms.Control[] {   this.listBox1});  ...  // Form1   this.Controls.AddRange(   new System.Windows.Forms.Control[] {   this.tabControl1,   this.splitter1,   this.groupBox1});  ... } 

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:

 void InitializeComponent() {   ...  this.treeView1 = new TreeView();   this.imageList1 = new ImageList(this.components);  ...  // ImageList associated with the TreeView   this.treeView1.ImageList = this.imageList1;  ...  // Images read from Form's resources   this.imageList1.ImageStream = ...;  ... } void Form1_Load(object sender, EventArgs e) {   TreeNode parentNode = new TreeNode();   parentNode.Text = "Chris";  parentNode.ImageIndex = 0; // Dad image   parentNode.SelectedImageIndex = 0;  treeView1.Nodes.Add(parentNode);   TreeNode childNode = new TreeNode();   childNode.Text = "John";  childNode.ImageIndex = 1; // Son image   childNode.SelectedImageIndex = 1;  parentNode.Nodes.Add(childNode); } 

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 } 

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:

 void InitializeComponent() {   ...  this.listBox1.DrawMode = DrawMode.OwnerDrawFixed;  ... } void listBox1_DrawItem(object sender, DrawItemEventArgs e) {  // Draw the background   e.DrawBackground();   // Get the default font   Font drawFont = e.Font;  bool ourFont = false;   // Draw in italics if selected  if( (e.State & DrawItemState.Selected) == DrawItemState.Selected ) {  ourFont = true;     drawFont = new Font(drawFont, FontStyle.Italic);  }  using( Brush brush = new SolidBrush(e.ForeColor) ) {  // Draw the list box item   e.Graphics.DrawString(listBox1.Items[e.Index].ToString(),   drawFont,   new SolidBrush(e.ForeColor),   e.Bounds);  if( ourFont ) drawFont.Dispose();   }  // Draw the focus rectangle   e.DrawFocusRectangle();  } 

The DrawItem method comes with the DrawItemEventArgs object:

 class DrawItemEventArgs : EventArgs {   // Properties   public Color BackColor { get; }   public Rectangle Bounds { get; }   public Font Font { get; }   public Color ForeColor { get; }   public Graphics Graphics { get; }   public int Index { get; }   public DrawItemState State { get; }   // Methods   public virtual void DrawBackground();   public virtual void DrawFocusRectangle(); } 

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:

 void InitializeComponent() {   ...  this.listBox2.DrawMode = OwnerDrawVariable;  ... } void listBox2_MeasureItem(object sender, MeasureItemEventArgs e) {   // Make every even item twice as high   if(  e.Index  % 2 == 0 )  e.ItemHeight *= 2  ; } 

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

 class MeasureItemEventArgs : EventArgs {   // Properties   public Graphics Graphics { get; }  public int Index { get; }   public int ItemHeight { get; set; }   public int ItemWidth { get; set; }  } 

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-Drawn 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:

 sealed class ControlPaint {   // Properties   public static Color ContrastControlDark { get; }   // Methods   public static IntPtr CreateHBitmap16Bit(...);   public static IntPtr CreateHBitmapColorMask(...);   public static IntPtr CreateHBitmapTransparencyMask(...);   public static Color Dark(...);   public static Color DarkDark(...);   public static void DrawBorder(...);   public static void DrawBorder3D(...);   public static void DrawButton(...);   public static void DrawCaptionButton(...);   public static void DrawCheckBox(...);   public static void DrawComboButton(...);   public static void DrawContainerGrabHandle(...);   public static void DrawFocusRectangle(...);   public static void DrawGrabHandle(...);   public static void DrawGrid(...);   public static void DrawImageDisabled(...);   public static void DrawLockedFrame(...);   public static void DrawMenuGlyph(...);   public static void DrawMixedCheckBox(...);   public static void DrawRadioButton(...);   public static void DrawReversibleFrame(...);   public static void DrawReversibleLine(...);   public static void DrawScrollButton(...);   public static void DrawSelectionFrame(...);   public static void DrawSizeGrip(...);   public static void DrawStringDisabled(...);   public static void FillReversibleRectangle(...);   public static Color Light(...;   public static Color LightLight(...); } 

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

 void statusBar1_DrawItem(object sender, StatusBarDrawItemEventArgs e) {   // 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   StatusBar statusBar = (StatusBar)sender;   using( Brush brush = new SolidBrush(statusBar.BackColor) ) {     e.Graphics.FillRectangle(SystemBrushes.Control, e.Bounds);   }   // Draw text as disabled   StringFormat format = new StringFormat();   format.LineAlignment = StringAlignment.Center;   format.Alignment = StringAlignment.Center;  ControlPaint.DrawStringDisabled(   e.Graphics, "Hi!", this.Font, this.ForeColor, e.Bounds, format);  } 

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 C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

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