Control Class

All controls are derived from the System.Windows.Forms.Control base class, which provides an extensive set of properties, methods, and events for all controls, giving the controls their basic functionality. Many of the most common and useful members will be described in this and subsequent chapters.

7.1.1 Class Hierarchy

The Control class is not instantiated directly; only derived classes are instantiated. Figure 7-1 shows the class hierarchy for the Control classes.

System.Object is the ultimate base class of all controls (as well as all classes in the .NET Framework). The Object class provides low-level services to all classes through methods such as ToString (which returns a String that represents the Object in a culture-sensitive, human-readable format). It is very common for classes to override the Object methods.

The next level in the class hierarchy is System.MarshalByRefObject, which provides services to applications that support remoting.

Remoting, the process of moving objects across application domain or machine boundaries, is beyond the scope of this book but is discussed in Jesse Liberty's Programming C# (O'Reilly).

The System.ComponentModel.Component class provides an implementation of the IComponent interface, which enables the logical containment of components in a container. It provides the Container, Events, and other properties, as well as the Dispose method (which releases resources used by a component), all of which are important to the implementation of controls. In addition to controls, the Component class is also the base class for such objects as the Timer, the HelpProvider, and the ToolTip. Specific components will be discussed throughout the book, where relevant.

Finally we get to the System.Windows.Forms.Control class. Components that have a visual representation are derived from the Control class. As such, they have properties such as BackColor, Location, Size, and Text; methods such as Hide, Show, and Focus; and events such as Click, GotFocus, and Paint.

Note that the Form itself is a Control, derived from the Control class via the ScrollableControl and ContainerControl classes.

 

Figure 7-1. Control class hierarchy

figs/pnwa_0701a.gif figs/pnwa_0701b.gif

7.1.2 Common Features

Since all controls are derived from a common class, it stands to reason that they will have a common set of features and functionality. The rest of this chapter will explore the aspects common to all controls.

7.1.2.1 Parent/child relationship

All controls can take part in parent/child relationships. A parent control can contain one or more child controls. Those child controls are contained within the Controls collection of the parent. Every control, except the top-level form, has a Parent property, of type Control, which is used to get or set the parent control, which contains the control. If the Parent property is null (Nothing in VB.NET)i.e., the control does not have a parent controlthen the control will not be visible or accessible unless it is the top-level control. These relationships are defined by the values of the Control properties listed in Table 7-1.

Table 7-1. Control properties relevant to parent/child relationships

Property

Value type

Description

Container

IContainer

Read-only. An object that implements the IContainer interface.

ContainsFocus

Boolean

Read-only. Returns true if the control or one of its children has focus.

Controls

Control.ControlCollection

Read-only. Returns a collection of all the child controls.

HasChildren

Boolean

Read-only. Returns true if the control has one or more child controlsi.e., if the Count property of the Controls collection is greater than zero.

Parent

Control

Read/write. The control object that contains this control. If null (Nothing in VB.NET), then this control is removed from the Controls collection and will not be displayed or accessible.

TopLevelControl

Control

Read-only. Returns the control at the very top of the control hierarchyi.e., the control with no parent.

The TopLevelControl will be the Form. In the case of MDI child forms and their children, the TopLevelControl will be the containing MDI parent form.

The source code shown in Example 7-1 (in C#) and in Example 7-2 (in VB.NET) demonstrate several of the properties listed in Table 7-1. In these examples, a button control is instantiated, has several properties set, and has a method added to the Click event delegate. The highlighted lines of code demonstrate two equivalent ways (one of which is commented out) to add the Button control to the ControlParent's controls collection.

Example 7-1. Control parent/child properties in C# (ControlParent.cs)

figs/csharpicon.gif

using System;

figs/csharpicon.gif

using System.Drawing;
using System.Windows.Forms;
 
namespace ProgrammingWinApps
{
 public class ControlParent : Form
 {
 
 private Button btn;
 
 public ControlParent( )
 {
 Text = "Control Parent";
 
 btn = new Button( );
 btn.Location = new Point(50,50);
 btn.Size = new Size(100,23);
 btn.Text = "Relationships";
 btn.Click += new System.EventHandler(btn_Click);
 
 // Use one of the following lines to add the Button control
 // to the Controls collection.
 // Controls.Add(btn);
 btn.Parent = this;
 }
 
 static void Main( ) 
 {
 Application.Run(new ControlParent( ));
 }
 
 private void btn_Click(object sender, EventArgs e)
 {
 MessageBox.Show("Button Parent: " + btn.Parent.ToString( ) + "
" +
 "Button HasChildren: " + btn.HasChildren.ToString( ) + "
" + 
 "TopLevelControl: " + btn.TopLevelControl.ToString( ) + "
" + 
 "Form HasChildren: " + this.HasChildren.ToString( ) + "
" + 
 "Form Controls Count: " + this.Controls.Count.ToString( ),
 "Button Relationships"); }
 }
}

Example 7-2. Control parent/child properties in VB.NET (ControlParent.vb)

figs/vbicon.gif

imports System
imports System.Drawing
imports System.Windows.Forms
 
namespace ProgrammingWinApps
 public class ControlParent : inherits Form
 
 Private WithEvents btn as Button
 
 public sub New( )
 Text = "Control Parent"
 
 btn = new Button( )
 btn.Location = new Point(50,50)
 btn.Size = new Size(100,23)
 btn.Text = "Relationships"
 
 ' Use one of the following lines to add the Button control
 ' to the Controls collection.
 ' Controls.Add(btn)
 btn.Parent = me
 end sub
 
 public shared sub Main( ) 
 Application.Run(new ControlParent( ))
 end sub
 
 private sub btn_Click(ByVal sender as object, _
 ByVal e as EventArgs) _
 Handles btn.Click
 MessageBox.Show("Button Parent: " + btn.Parent.ToString( ) + vbLf + _
 "Button HasChildren: " + btn.HasChildren.ToString( ) + vbLf + _
 "TopLevelControl: " + btn.TopLevelControl.ToString( ) + vbLf + _
 "Form HasChildren: " + me.HasChildren.ToString( ) + vbLf + _
 "Form Controls Count: " + me.Controls.Count.ToString( ), _
 "Button Relationships") 
 end sub
 end class
end namespace

The code in Example 7-1 and Example 7-2 is handcoded in a text editor, as will be many of the examples in the chapters covering controls, to avoid the clutter introduced by Visual Studio .NET. As with all source code files created outside Visual Studio .NET, they must be compiled from a command line to generate the executable file, as described in Chapter 2. Remember to use the command prompt found at Start Button Programs Microsoft Visual Studio .NET 2003 Visual Studio .NET Tools Visual Studio .NET 2003 Command Prompt.

To compile the code, use the appropriate following command:

figs/csharpicon.gif

csc /out:ControlParent.exe /t:winexe ControlParent.cs

figs/vbicon.gif

vbc /out:ControlParent.exe 
/t:winexe /r:system.dll,system.drawing.dll,system.windows.forms.dll,
system.data.dll /imports:Microsoft.VisualBasic ControlParent.vb

C# doesn't need the references in the command line because a file called csc.rsp contains "default" references for the C# compiler. VB.NET has no equivalent, so the references must be included in the command line.

In addition to the "default" references, this VB.NET example imports the Microsoft.VisualBasic namespace to enable use of the vbCrLf Visual Basic intrinsic constant. When VB.NET programs are compiled in Visual Studio .NET, that namespace is automatically and invisibly included.

In the code in Example 7-1 and Example 7-2, a Button control named btn is declared as a member variable. It is instantiated and has several properties set in the constructor. Of particular interest here are the two highlighted lines of code in each example:

figs/csharpicon.gif

// Controls.Add(btn);
btn.Parent = this;

figs/vbicon.gif

' Controls.Add(btn)
btn.Parent = me

These two lines of code accomplish the same task: they define the parent control for the button, and they add the button to the parent's Controls collection.

The first line (commented out) explicitly adds the button to the Controls collection and implicitly makes the current object the parent of the button. The second line explicitly assigns the parent/child relation and implicitly adds the button to the Controls collection. Which way you accomplish this task is entirely up to you; the end result is the same.

You can add multiple controls to the Controls collection using the AddRange method of the Control.ControlCollection class. This method takes an array of Controls as an argument. So, for example, suppose you had a form with three buttons named btn1, btn2, and btn3. Rather than having three individual statements declaring the Parent property for each control, you could use a single AddRange statement. This would look like:

figs/csharpicon.gif

this.Controls.AddRange(new Control[ ] {btn1, btn2, btn3})

figs/vbicon.gif

me.Controls.AddRange(new Control( ) {btn1, btn2, btn3})

Visual Studio .NET always adds controls to the Controls collection using the AddRange method, even if only a single control is being added.

The Button Click event handler puts up a MessageBox that concatenates and displays several Control properties. Notice that the C# version of the program uses the NewLine escape sequence ( ) directly, while the VB.NET version uses the equivalent VB.NET intrinsic constant vbLf.

Common C# escape sequences and the equivalent VB.NET constants are listed in Table 4-9 in Chapter 4.

When the program is compiled, run, and the button is clicked, the MessageBox shown in Figure 7-2 is displayed.

Figure 7-2. ControlParent MessageBox

figs/pnwa_0702.gif

7.1.2.2 Z-order

It is possible for two or more controls to occupy the same piece of screen real estate. In fact, by definition, every child control overlays part of its parent control. Controls can partly obscure underlying controls, even without a parent/child relationship. In these situations, one of the controls is "on top" and one of the controls is "on the bottom." Other controls are in between, one above, or below the other. This vertical relationship is quantified in the z-order.

The z-order is named after the Z-axis in a three-dimensional Cartesian coordinate system. The X and Y axes represent the horizontal and vertical directions in the plane of the video screen. The Z-axis represents the direction perpendicular to the screeni.e., the depth of the image.

The z-order of a control can only be set programmatically or at design time. It cannot be changed by the user at runtime, other than forms in an MDI application, except under program control. Clicking on a control or otherwise giving it focus does not change its z-order (unless you write code to make it change).

The z-order is initially determined by the order in which a control is assigned to the Parent's Controls collection. The first control added to the collection (index 0) is at the top of the z-order for that Parent. Each subsequent control added to the Controls collection is placed just below the controls previously added. The child control with an index of Controls.Count -1 is at the bottom of the z-order.

When you click the mouse or otherwise raise a mouse event, the event is directed to the control under the mouse. If there is more than one control directly under the mouse, the event is directed to the control at the top of the z-orderi.e., the visible control. (For a complete discussion of mouse events, see Chapter 8.) So if the mouse is clicked when the mouse cursor is over the parent Form, the Form handles the mouseclick. If the mouse cursor moves over a Panel control on the Form, the Panel handles the mouseclick. If the mouse cursor is over a Button on the Panel which is on the Form, the Button handles the mouseclick.

If multiple controls are docked against the same edge of a container, the docked control with the higher z-order is located closer to the center of the client area. The docked control with the lowest z-order is immediately adjacent to the edge of the container. (See Section 7.1.2.10 later in this chapter.)

7.1.2.3 Changing the z-order

In Visual Studio .NET you can easily change the z-order of a control by selecting the control in design view, and then using the Format Order menu command to select either Bring to Front or Send To Back. You can also change it by expanding the region of code labeled Windows Form Designer generated code, finding the AddRange statement that adds all the controls to the Controls collection, and editing the order in which those controls are listed in the statement. The first control in the list will be at the top of the z-order, and the last control in the list will be at the bottom.

The boilerplate comment in the code warns against editing the code in the auto-generated region, although it does work if you are careful. Any code in this region not understood by VS.NET will be discarded.

Two methods of the Control class allow you to change the z-order programmatically: BringToFront and SendToBack, neither of which takes any argument or returns any value. So, for example, the following line of code will bring a button called Button1 to the top of the z-order (identical in both languages except for the semicolon):

Button1.BringToFront( );

while the following line will send it to the bottom of the z-order:

Button1.SendToBack( );

7.1.2.4 Ambient properties

If you do not set a property on a control, it might inherit the setting for that property from its container (parent control). Properties that can be inherited by contained controls are called ambient properties. Ambient properties allow a control to assume the appearance of its surrounding environment. Table 7-2 lists the ambient properties and their default values.

Table 7-2. Ambient properties

Property

Default value

BackColor

Color.Empty

Cursor

null in C#, Nothing in VB.NET

Font

null in C#, Nothing in VB.NET

ForeColor

Color.Empty

If the control does not have a parent and an ambient property is not set, then the control will use a default value for the ambient property.

The use of ambient properties can be illustrated by modifying the code from Figures Figure 7-1 and Figure 7-2 to add a Label control, setting the BackColor and ForeColor properties of the Form, and observing the effect on both the Button and Label controls. The C# code is shown in Example 7-3 and the VB.NET code is shown in Example 7-4. In both examples, the added lines of code are highlighted.

When the program is compiled and run, both the Button and the Label have a Green background color and a Yellow foreground color, matching the form.

Example 7-3. Ambient property in C# (ControlAmbientProperty.cs)

figs/csharpicon.gif

using System;
using System.Drawing;
using System.Windows.Forms;
 
namespace ProgrammingWinApps
{
 public class ControlAmbientProperties : Form
 {
 
 private Button btn;
 private Label lbl;
 public ControlAmbientProperties( )
 {
 Text = "Control Parent";
 BackColor = Color.Green;
 ForeColor = Color.Yellow;
 btn = new Button( );
 btn.Location = new Point(50,50);
 btn.Size = new Size(100,23);
 btn.Text = "Relationships";
 btn.Click += new System.EventHandler(btn_Click);
 btn.Parent = this;
 
 lbl = new Label( );
 lbl.Text = "Ambient Properties";
 lbl.Parent = this;
 }
 
 static void Main( ) 
 {
 Application.Run(new ControlAmbientProperties( ));
 }
 
 private void btn_Click(object sender, EventArgs e)
 {
 MessageBox.Show("Button Parent: " + btn.Parent.ToString( ) + "
" +
 "Button HasChildren: " + btn.HasChildren.ToString( ) + "
" + 
 "TopLevelControl: " + btn.TopLevelControl.ToString( ) + "
" + 
 "Form HasChildren: " + this.HasChildren.ToString( ) + "
" + 
 "Form Controls Count: " + this.Controls.Count.ToString( ),
 "Button Relationships");
 }
 }
}

Example 7-4. Ambient property in VB.NET (ControlAmbientProperty.vb)

figs/vbicon.gif

imports System
imports System.Drawing
imports System.Windows.Forms
 
namespace ProgrammingWinApps
 public class ControlParent : inherits Form
 
 Private WithEvents btn as Button
 private lbl as Label
 public sub New( )
 Text = "Control Parent"
 BackColor = Color.Green
 ForeColor = Color.Yellow
 btn = new Button( )
 btn.Location = new Point(50,50)
 btn.Size = new Size(100,23)
 btn.Text = "Relationships"
 btn.Parent = me
 
 lbl = new Label( )
 lbl.Text = "Ambient Properties"
 lbl.Parent = me
 end sub
 
 public shared sub Main( ) 
 Application.Run(new ControlParent( ))
 end sub
 
 private sub btn_Click(ByVal sender as object, _
 ByVal e as EventArgs) _
 Handles btn.Click
 MessageBox.Show("Button Parent: " + btn.Parent.ToString( ) + vbCrLf + _
 "Button HasChildren: " + btn.HasChildren.ToString( ) + vbCrLf + _
 "TopLevelControl: " + btn.TopLevelControl.ToString( ) + vbCrLf + _
 "Form HasChildren: " + me.HasChildren.ToString( ) + vbCrLf + _
 "Form Controls Count: " + me.Controls.Count.ToString( ), _
 "Button Relationships")
 end sub
 end class
end namespace

7.1.2.5 Font

All controls have a Font property, of type Font. The Font property specifies the typeface or design, size, and style, for any text displayed by the control. Fonts and their properties are covered in detail in Chapter 9.

7.1.2.6 Size and location

The size and location of controls can be set either interactively using Visual Studio .NET, programmatically at design time, or programmatically at runtime. The control properties that pertain to size and location are listed in Table 7-3.

You have already seen the size and location for a Button control set in Example 7-3 (in C#) and in Example 7-4 (in VB.NET):

btn.Location = new Point(50,75)
btn.Size = new Size(100,23)

The Location property, of type Point, specifies the top-left corner of the control relative to the top-left corner of its container control, expressed as an instance of Point. Point is a structure (struct in C#) that provides an ordered pair of integer X,Y coordinates. In the code snippet shown above, the btn control is located 50 pixels to the right and 75 pixels down from the upper-left corner of its container (the form).

The units can be pixels, inches, millimeters, etc., contained in the GraphicsUnits enumeration, listed in Table 10-3 in Chapter 10. For more on the scale units used by Location and Size, see Chapter 10.

The Label control does not have a set Location property; it defaults to a location of (0,0): the upper-left corner of the containing form.

The Size property of the Control class is of type Size. The Size value is a structure that contains an ordered pair of integers specifying the width and height of the control. In the code snippet above, btn is 100 pixels wide and 23 pixels high.

Both the Size and Point objects are value types, rather than reference types. This means that when these properties are returned, a copy of the original is returned, not a reference to the original value. If you change the value, it will not have any effect on the control. Instead, to change the size or location of a control, you must assign a new Point or Size object to the Location or Size properties, respectively, as shown in the code snippet above.

Alternatively, you can change the Location of a control by setting the Left or Top properties (the Right and Bottom properties are read-only) of the control and you can change the Size of a control by setting the Width or Height property of the control.

Several of the properties listed in Table 7-3 distinguish between the area and the client area of a control. For most controls, they are the same. For forms, however, the client area refers to the entire control minus the scrollbars, borders, titlebars and menus.

These properties, as well as several others listed in Table 7-3 are demonstrated in Example 7-5 in C# and in Example 7-6 in VB.NET.

Table 7-3. Control Size and location properties

Property

Value type

Description

Bottom

integer

Read-only. Returns the distance, in pixels, from control's bottom edge to the top edge of its container client area.

Bounds

Rectangle

Read/write. The size and location of the control.

ClientRectangle

Rectangle

Read-only. Returns the rectangle representing the client area of the control.

ClientSize

Size

Read/write. The size of the client area of a control. Requires the System.Drawing namespace.

DisplayRectangle

Rectangle

Read-only. Returns a rectangle representing the display area of a controli.e., the smallest rectangle that encloses the control. For most controls, this corresponds to the ClientRectangle. It is useful primarily for scrollable controls, since it includes the entire control, including any part of the control not currently scrolled into view.

Height

integer

Read/write. The height of the control, in pixels.

Left

integer

Read/write. The x-coordinate, in pixels, of the left edge of the control. It is equivalent to the Point.X property of the Location property value.

Location

Point

Read/write. The upper-left corner of a control relative to the upper-left corner of its container. If the control is a Form, it is the upper-left corner of the Form in screen coordinates. Requires the System.Drawing namespace.

Right

integer

Read-only. Returns the distance, in pixels, from the control's right edge to the left edge of its container.

Size

Size

Read/write. An ordered pair of width and height properties specifying the size of a rectangular region which contains the control. Requires the System.Drawing namespace.

Top

integer

Read/write. The y-coordinate, in pixels, of the top edge of the control. This is equivalent to the Point.Y property of the Location property value.

Width

integer

Read/write. The width of the control, in pixels.

Example 7-5. Control size and location in C# (ControlSizeLocation.cs)

figs/csharpicon.gif

using System;
using System.Drawing;
using System.Windows.Forms;
 
namespace ProgrammingWinApps
{
 public class ControlSizeLocation : Form
 {
 
 private Button btnShow;
 private Button btnChange;
 private Label lbl;
 
 public ControlSizeLocation( )
 {
 Text = "Control Parent";
 BackColor = Color.LightBlue;
 ForeColor = Color.DarkBlue;
 Size = new Size(350,200);
 
 btnShow = new Button( );
 btnShow.Location = new Point(50,50);
 btnShow.Size = new Size(100,23);
 btnShow.Text = "Show";
 btnShow.Click += new System.EventHandler(btnShow_Click);
 btnShow.Parent = this;
 
 btnChange = new Button( );
 btnChange.Location = new Point(200,50);
 btnChange.Size = new Size(100,23);
 btnChange.Text = "Change";
 btnChange.Click += new System.EventHandler(btnChange_Click);
 btnChange.Parent = this;
 
 lbl = new Label( );
 lbl.Text = "Control Size and Location";
 lbl.Size = new Size(400,25);
 lbl.Parent = this;
 }
 
 static void Main( ) 
 {
 Application.Run(new ControlSizeLocation( ));
 }
 
 private void btnShow_Click(object sender, EventArgs e)
 {
 MessageBox.Show("Button Bottom: 	" + 
 btnShow.Bottom.ToString( ) + "
" +
 "Button Top: 	" + btnShow.Top.ToString( ) + "
" + 
 "Button Left: 	" + btnShow.Left.ToString( ) + "
" + 
 "Button Right: 	" + btnShow.Right.ToString( ) + "
" + 
 "Button Location: 	" + btnShow.Location.ToString( ) + "
" + 
 "Button Width: 	" + btnShow.Width.ToString( ) + "
" + 
 "Button Height: 	" + btnShow.Height.ToString( ) + "
" + 
 "Button Size: 	" + btnShow.Size.ToString( ) + "
" + 
 "Button ClientSize: 	" + btnShow.ClientSize.ToString( ) + "
" + 
 "Form Size: 	" + this.Size.ToString( ) + "
" + 
 "Form ClientSize: 	 " + this.ClientSize.ToString( ),
 "Size & Location");
 }
 
 private void btnChange_Click(object sender, EventArgs e)
 {
 this.Size = new Size(800,200);
 }
 
 }
}

Example 7-6. Control size and location in VB.NET (ControlSizeLocation.vb)

figs/vbicon.gif

imports System
imports System.Drawing
imports System.Windows.Forms
 
namespace ProgrammingWinApps
 public class ControlSizeLocation : inherits Form
 
 Private WithEvents btnShow as Button
 Private WithEvents btnChange as Button
 private lbl as Label
 
 public sub New( )
 Text = "Control Parent"
 BackColor = Color.LightBlue
 ForeColor = Color.DarkBlue
 Size = new Size(350,200)
 
 btnShow = new Button( )
 btnShow.Location = new Point(50,50)
 btnShow.Size = new Size(100,23)
 btnShow.Text = "Show"
 btnShow.Parent = me
 
 btnChange = new Button( )
 btnChange.Location = new Point(200,50)
 btnChange.Size = new Size(100,23)
 btnChange.Text = "Change"
 btnChange.Parent = me
 
 lbl = new Label( )
 lbl.Text = "Control Size and Location"
 lbl.Size = new Size(400,25)
 lbl.Parent = me
 end sub
 
 public shared sub Main( ) 
 Application.Run(new ControlSizeLocation( ))
 end sub
 
 private sub btnShow_Click(ByVal sender as object, _
 ByVal e as EventArgs) _
 Handles btnShow.Click
 MessageBox.Show("Button Bottom: " + _
 vbTab + btnShow.Bottom.ToString( ) + vbLf + _
 "Button Top: " + vbTab + _
 btnShow.Top.ToString( ) + vbLf + _ 
 "Button Left: " + vbTab + _
 btnShow.Left.ToString( ) + vbLf + _ 
 "Button Right: " + vbTab + _
 btnShow.Right.ToString( ) + vbLf + _ 
 "Button Location: " + vbTab + _
 btnShow.Location.ToString( ) + vbLf + _ 
 "Button Width: " + vbTab + _
 btnShow.Width.ToString( ) + vbLf + _ 
 "Button Height: " + vbTab + _
 btnShow.Height.ToString( ) + vbLf + _ 
 "Button Size: " + vbTab + _
 btnShow.Size.ToString( ) + vbLf + _ 
 "Button ClientSize: " + vbTab + _
 btnShow.ClientSize.ToString( ) + vbLf + _ 
 "Form Size: " + vbTab + me.Size.ToString( ) + vbLf + _ 
 "Form ClientSize: " + vbTab + me.ClientSize.ToString( ), _
 "Size & Location") 
 end sub
 
 private sub btnChange_Click(ByVal sender as object, _
 ByVal e as EventArgs) _
 Handles btnChange.Click
 me.Size = new Size(800,200)
 end sub
 end class
end namespace

The program shown in Example 7-5 and Example 7-6 puts up a form 350 pixels wide and 200 pixels high, as indicated by the following line of code (identical in both languages except for the trailing semicolon):

Size = new Size(350,200);

Another way to accomplish this would be use these two lines:

Width = 350;
Height = 200;

The SDK documentation suggests that an application will have better performance if the Size property of a control is not set in its constructor. Instead, it recommends overriding the protected property DefaultSize. If this performance improvement is significant to your application, you can add the following property to the class in C#:

figs/csharpicon.gif

protected override Size DefaultSize
{
 get
 {
 return new Size(400,400);
 }
}

or this code in VB.NET:

figs/vbicon.gif

protected ReadOnly overrides Property DefaultSize as Size
 get
 return new Size(400,400)
 end get
end property

These code snippets will change the form size to 400 x 400 (intentionally different from inside the constructor so the difference is apparent). If you are overriding the DefaultSize property, do not set the Size, ClientSize, Height, or Width properties in the constructor.

There are two buttons on the form: one marked Show, named btnShow, and the other marked Change, named btnChange. Both buttons have their Location and Size properties set so that they will be aligned with each other and centered in the form, and their Text and Parent properties are set appropriately.

The event handler for the Show button Click event (btnShow_Click) displays the value of the Size and Location properties for the button and the form.

Note that this MessageBox uses both tab characters ( in C# and vbTab in VB.NET) and line feeds ( in C# and vbLf in VB.NET) to control the formatting of the displayed text.

When you run the program and click on the Show button, the message box will be displayed, as shown in Figure 7-3.

Figure 7-3. Control size and location

figs/pnwa_0703.gif

The event handler for the Change button (btnChange_Change) changes the size of the form to 800 pixels wide x 200 pixels high:

figs/csharpicon.gif

this.Size = new Size(800,200);

figs/vbicon.gif

me.Size = new Size(800,200)

7.1.2.7 Dynamically setting size and location

The programs shown in Example 7-5 and Example 7-6 work fine, but they have several shortcomings. First of all, the size of the buttons are hardcoded. If the Button Text property changes, you may have to change the width of the button, and if the system running the program uses a different font size, the button may be sized incorrectly. The second shortcoming has to do with the location. The button is centered when the form first displays, but if the user resizes the form, then the button no longer will be centered.

The programs shown in Example 7-7 (in C#) and in Example 7-8 (in VB.NET) address these issues. The lines of code that differ from Example 7-5 and Example 7-6 are highlighted. An analysis follows the code listings.

Example 7-7. Dynamic control size and location in C#(ControlDynamicSizeLocation.cs)

figs/csharpicon.gif

using System;
using System.Drawing;
using System.Windows.Forms;
 
namespace ProgrammingWinApps
{
 public class ControlDynamicSizeLocation : Form
 {
 private Button btnShow;
 private Label lbl;
 int xButtonSize, yButtonSize;
 
 public ControlDynamicSizeLocation( )
 {
 Text = "Control Parent";
 
 btnShow = new Button( );
 btnShow.Parent = this;
 btnShow.Text = "Show Button Properties";
 // move Size to after instantiation of btnShow so OnResize will work.
 Size = new Size(350,400);
 
 xButtonSize = (int)(Font.Height * .75) * btnShow.Text.Length;
 yButtonSize = Font.Height * 2;
 btnShow.Size = new Size(xButtonSize, yButtonSize);
 btnShow.Click += new System.EventHandler(btnShow_Click);
 
 lbl = new Label( );
 lbl.Text = "Control Size and Location - Dynamic";
 lbl.AutoSize = true;
 lbl.Parent = this;
 
 OnResize(EventArgs.Empty);
 }
 
 protected override void OnResize(EventArgs e)
 {
 base.OnResize(e);
 
 // Reposition btnShow based on new form size.
 int xPosition = (int)(this.ClientSize.Width / 2) - 
 (int)(xButtonSize / 2);
 int yPosition = (int)(this.ClientSize.Height / 2) - 
 (int)(yButtonSize / 2);
 btnShow.Location = new Point(xPosition, yPosition);
 }
 
 static void Main( ) 
 {
 Application.Run(new ControlDynamicSizeLocation( ));
 }
 
 private void btnShow_Click(object sender, EventArgs e)
 {
 MessageBox.Show("Button Bottom: 	" + 
 btnShow.Bottom.ToString( ) + "
" +
 "Button Top: 	" + btnShow.Top.ToString( ) + "
" + 
 "Button Left: 	" + btnShow.Left.ToString( ) + "
" + 
 "Button Right: 	" + btnShow.Right.ToString( ) + "
" + 
 "Button Location: 	" + btnShow.Location.ToString( ) + "
" + 
 "Button Width: 	" + btnShow.Width.ToString( ) + "
" + 
 "Button Height: 	" + btnShow.Height.ToString( ) + "
" + 
 "Button Size: 	" + btnShow.Size.ToString( ) + "
" + 
 "Button ClientSize: 	" + btnShow.ClientSize.ToString( ) + "
" + 
 "Font:	" + btnShow.Font.ToString( ) + "
" + 
 "Font Family:	" + btnShow.Font.FontFamily.ToString( ) + "
" + 
 "Font Style:	" + btnShow.Font.Style.ToString( ) + "
" + 
 "Font Unit:	" + btnShow.Font.Unit.ToString( ) + "
" +
 "Form ClientSize: 	 " + this.ClientSize.ToString( ),
 "Size & Location");
 }
 }
}

Example 7-8. Dynamic control size and location in VB.NET (ControlDynamicSizeLocation.vb)

figs/vbicon.gif

imports System
imports System.Drawing
imports System.Windows.Forms
 
namespace ProgrammingWinApps
 public class ControlDynamicSizeLocation : inherits Form
 
 Private WithEvents btnShow as Button
 private lbl as Label
 private xButtonSize as integer
 private ybuttonSize as integer
 
 public sub New( )
 Text = "Control Parent"
 
 btnShow = new Button( )
 btnShow.Parent = me
 btnShow.Text = "Show Button Properties"
 
 ' move Size to after instantiation of btnShow so OnResize will work.
 Size = new Size(350,400)
 
 xButtonSize = Cint(Font.Height * .75) * btnShow.Text.Length
 yButtonSize = Font.Height * 2
 btnShow.Size = new Size(xButtonSize, yButtonSize)
 
 lbl = new Label( )
 lbl.Text = "Control Size and Location - Dynamic"
 lbl.AutoSize = true
 lbl.Parent = me
 
 OnResize(EventArgs.Empty)
 end sub
 
 
 protected overrides sub OnResize(ByVal e as EventArgs)
 MyBase.OnResize(e)
 
 ' Reposition btnShow based on new form size.
 dim xPosition as integer = Cint(me.ClientSize.Width / 2) - _
 Cint(xButtonSize / 2)
 dim yPosition as integer = Cint(me.ClientSize.Height / 2) - _
 Cint(yButtonSize / 2)
 btnShow.Location = new Point(xPosition, yPosition)
 end sub
 
 public shared sub Main( ) 
 Application.Run(new ControlDynamicSizeLocation( ))
 end sub
 
 private sub btnShow_Click(ByVal sender as object, _
 ByVal e as EventArgs) _
 Handles btnShow.Click
 MessageBox.Show("Button Bottom: " + _
 vbTab + btnShow.Bottom.ToString( ) + vbLf + _
 "Button Top: " + vbTab + _
 btnShow.Top.ToString( ) + vbLf + _ 
 "Button Left: " + vbTab + _
 btnShow.Left.ToString( ) + vbLf + _ 
 "Button Right: " + vbTab + _
 btnShow.Right.ToString( ) + vbLf + _ 
 "Button Location: " + vbTab + _
 btnShow.Location.ToString( ) + vbLf + _ 
 "Button Width: " + vbTab + _
 btnShow.Width.ToString( ) + vbLf + _ 
 "Button Height: " + vbTab + _
 btnShow.Height.ToString( ) + vbLf + _ 
 "Button Size: " + vbTab + _
 btnShow.Size.ToString( ) + vbLf + _ 
 "Button ClientSize: " + vbTab + _
 btnShow.ClientSize.ToString( ) + vbLf + _ 
 "Font:" + vbTab + btnShow.Font.ToString( ), _ 
 "Size & Location")
 end sub
 end class
end namespace

Two main issues are addressed in Example 7-7 and Example 7-8: the size of the control and the location of the control.

Dynamically controlling size

The size of the Button control, btnShow, is determined by the number of characters in the btnShow.Text property and the size of the Font. Two member variables, xButtonSize and yButtonSize, are declared. They need to be scoped for the entire class because they will be referenced in two different methods (discussed later).

The btnShow.Text.Length property determines the number of characters. The width of the button is calculated by multiplying the number of characters in the Text property by a width factor for each character. Three-quarters of the Height of the Font is used for the width factor. This width factor works well for most fonts, including proportional (variable pitch) and fixed pitch fonts.

You might think you could use the Size property of the Font for the width factor. This property is actually the em-size of the font, the width of the letter M. However, when changing the default Windows display font from Small to Large, the em-size actually decreases, although the Font.Height does increase correctly. Windows applies a scaling factor to the font to display the desired size, even though the reported em-size is counterintuitive.

You could accomplish the same goal by measuring the size of the string using the Graphics class method MeasureString, as shown in Chapter 10.

The line of code that calculates the width of the button is as follows:

figs/csharpicon.gif

xButtonSize = (int)( Font.Height * .75) * btnShow.Text.Length;

figs/vbicon.gif

xButtonSize = Cint(Font.Height * .75) * btnShow.Text.Length

The product of the Font.Height property, of type float in C# (single in VB.NET), and a numeric constant must be cast to an integer, since xButtonSize is of type integer. The Length property is already an integer.

The Height of the Font determines the Height of the Button. This is done in the following line of code:

yButtonSize = Font.Height * 2

Multiplying the Font.Height by 2 just makes the button less visually crowded. It turns out that the button has an internal border of 4 pixels around each edge. If you made the Button height the same as Font.Height, then 8 pixels would be clipped from the characters. If you are really tight on form real estate, you could use the following line of code to set the button height, making the button equal to Font.Height plus 8 pixels:

yButtonSize = Font.Height + 8

These two dimensions come together when the button's Size property is set in the following line of code:

btnShow.Size = new Size(xButtonSize, yButtonSize)

Now no matter what font is used for the Form, the button will be sized correctly. (If you are setting the Font for the Form, do so in the constructor before the Button size is calculated.) Also, you will not have to rejigger the size of the button if you change the Text property.

Dynamically controlling location

There are two situations to consider when controlling the location of the Button control. The first is when the form initially loads, and the second is when the form is resized by the user. Considering the latter situation first, all members of the Control class, including the Form, raise a Resize event whenever they are resized, whether by the user or programmatically. This Resize event is handled by the .NET Framework using the OnResize event handler.

For a complete discussion of events, see Chapter 4.

In Example 7-7 and Example 7-8, the OnResize method is overridden to dynamically recalculate the correct button Location based on the new Form dimensions. The overridden OnResize event handler is reproduced here:

figs/csharpicon.gif

protected override void OnResize(EventArgs e)
{
 base.OnResize(e);
 
 // Reposition btnShow based on new form size.
 int xPosition = (int)(this.ClientSize.Width / 2) - 
 (int)(xButtonSize / 2);
 int yPosition = (int)(this.ClientSize.Height / 2) - 
 (int)(yButtonSize / 2);
 btnShow.Location = new Point(xPosition, yPosition);
}

figs/vbicon.gif

protected overrides sub OnResize(ByVal e as EventArgs)
 MyBase.OnResize(e)
 
 ' Reposition btnShow based on new form size.
 dim xPosition as integer = Cint(me.ClientSize.Width / 2) - _
 Cint(xButtonSize / 2)
 dim yPosition as integer = Cint(me.ClientSize.Height / 2) - _
 Cint(yButtonSize / 2)
 btnShow.Location = new Point(xPosition, yPosition)
end sub

You can accomplish the same effect as these examples with a lot less effort using the Anchor property, as discussed later in this chapter in Section 7.1.2.9. However, this technique can accomplish things not possible with Anchor, such as forcing columns of controls to dynamically reposition in response to user resizing the form.

The override keyword (in VB.NET it is overrides) indicates to the compiler that this method is overriding a virtual OnResize method (overridable in VB.NET). The OnResize method takes a single argument, of type EventArgs.

The first line in the method calls the OnResize method from the base class. This ensures that any functionality contained in the base method is not omitted and any other methods registered with the event delegate are notified. This chaining up to the base method is done with the following line of code:

figs/csharpicon.gif

base.OnResize(e);

figs/vbicon.gif

MyBase.OnResize(e)

Next, two integer variables are declared and instantiated to hold the X and Y coordinates of the button, xPosition and yPosition, respectively. These coordinates are calculated based on the ClientSize of the Form (the size of the entire Form window minus the titlebar, menu and toolbar, status bar, and any scrollbars) and the size of the Button. All size terms are cast as integers, since xPosition and yPosition are integers.

Finally, the Location property of btnShow is set using xPosition and yPosition.

Once the OnResize method is overridden, it can be called in the constructor with the following line of code (identical in both languages except for the semicolon):

OnResize(EventArgs.Empty)

The EventArgs.Empty argument is an empty placeholder, of type EventArgs.

Now the button is located correctly when the form is first opened, and upon any subsequent resizing.

When this program was originally coded, all the Form properties were grouped in the constructor, followed by the Button properties, as in:

Text = "Control Parent";
Size = new Size(350,200);
btnShow = new Button( );
btnShow.Parent = this;
btnShow.Text = "Show";

This caused the following runtime error: "Object reference not set to an instance of an object."

This was caused by the Form Size property raising the Resize event, which invoked OnResize. The overridden OnResize method references btnShow, but btnShow had not yet been instantiated.

Moving the Form Size statement to a location in the code after btnShow had been instantiated solved the problem.

When the program shown in Example 7-7 and Example 7-8 is run on a system with the default Windows font set to Small Font, the result of clicking the Show Button Properties button is shown in Figure 7-4.

Figure 7-4. Dynamic control size and location (small font)

figs/pnwa_0704.gif

7.1.2.8 AutoScale

In the ControlDynamicSizeLocation examples shown above (Example 7-7 and Example 7-8), an algorithm was handcoded to dynamically determine the size and location of the Button control based on the Font property of the Form.

When using Visual Studio .NET, however, the size and location of controls is hardcoded, like the ControlSizeLocation examples shown above (Example 7-5 and Example 7-6). As you have seen, this can lead to problems if the user changes fonts for Windows or the form, or resizes the form.

Visual Studio .NET uses a .NET Framework feature called AutoScale to handle this scaling without having to explicitly code for it.

To see AutoScale in action, create a new Windows Application project in Visual Studio .NET in the language of your choice. Drag a single Button control onto the form and resize the button by dragging on one of the resizing handles.

Right-click on the form and select View Code to see the source code for the form. Click on the plus sign next to the line in the code that says Windows Form Designer generated code to expand the autogenerated code. You will see a method named InitializeComponent, which is called from the constructor to initialize all the controls and components on the form. Within InitializeComponent are lines of code similar to the following:

figs/csharpicon.gif

//
// button1
// 
this.button1.Location = new System.Drawing.Point(88, 80);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(104, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
// 
// Form1
// 
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 272);

figs/vbicon.gif

'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(80, 64)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(128, 23)
Me.Button1.TabIndex = 0
Me.Button1.Text = "Button1"
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(292, 272)

The highlighted lines of code above include the seemingly hardcoded sizes and locations of the Button and Form controls.

The key to autoscaling is the AutoScaleBaseSize property of the form. This property, of type Size, gets and sets the base size used in autoscaling the form and its child controls. This size is compared with the size of the current system font at display time to calculate scaling factors in both the horizontal and vertical directions.

It turns out that when the system font size is Small Fonts (set by going to Control Panel and then Display, selecting Properties, and then the Settings tab), the size of the font is 5 x 13 pixels. Large Fonts have a size of 6 x 15 pixels.

If the form is designed on a machine set to Small Fonts and then run on a machine also set to Small Fonts, the scaling factor in the X direction is 5/5 or 1, so no scaling takes place. The Y scaling is similar. However, if the form is run on a machine set to Large Fonts, the form and its controls are scaled in the X direction by a factor of 5/6 and in the Y direction by a factor of 13/15. The result is that the form and all the controls are scaled in both size and location so that the appearance looks the same no matter which system font is used.

Since the AutoScaleBaseSize property is of type Size, it can also be used to obtain the width and height of the current system font with the following lines of code:

figs/csharpicon.gif

int x = AutoScaleBaseSize.Width;
int y = AutoScaleBaseSize.Height;

7.1.2.9 Anchoring

The programs coded in Example 7-7 and Example 7-8 dynamically positioned a control by overriding the OnResize event handler. The Anchor property provides an alternative, and much easier, technique to dynamically position a control.

The Anchor property positions a control relative to the edge (or edges) of its container. As the container, say a form, is resized, the anchored control retains the same position relative to the specified edge (or edges).

The property can have one or more of the values shown Table 7-4, combined in a bitwise fashion using the logical OR operator. The default value is a combination of Top and Left. If a value of None is used, then the anchored control will retain its position in the client area relative to the size of the client area.

Table 7-4. Anchor property values

Value

Bottom

Left

None

Right

Top

The programs listed in Example 7-9 and Example 7-10 demonstrate the use of anchoring controls. These programs each have five buttons (which don't really do anything except look pretty), one anchored to each of the corners of the form and one that spans the middle of the form. Each button has a margin between it and the edge of the form client area. As you resize the form, the four corner buttons retain the same relative positions in their corners and the middle button changes width to match the client area of the form. The end result is shown in Figure 7-5.

Example 7-9. Control anchoring in C# (ControlAnchor.cs)

figs/csharpicon.gif

using System;
using System.Drawing;
using System.Windows.Forms;
 
namespace ProgrammingWinApps
{
 public class ControlAnchor : Form
 {
 public ControlAnchor( )
 {
 Text = "Control Anchoring";
 Size = new Size(350,400);
 
 int xButtonSize, yButtonSize;
 
 // All the buttons will have the same height.
 yButtonSize = Font.Height * 2;
 
 // All the buttons will be the same distance from the 
 // edge of the form.
 int xMargin, yMargin;
 xMargin = yMargin = Font.Height * 2;
 
 // Upper Left Button
 Button btn = new Button( );
 btn.Parent = this;
 btn.Text = "Upper Left";
 xButtonSize = (int)(Font.Height * .75) * btn.Text.Length;
 btn.Size = new Size(xButtonSize, yButtonSize);
 btn.Location = new Point(xMargin, yMargin);
 
 // Lower Left Button
 btn = new Button( );
 btn.Parent = this;
 btn.Text = "Lower Left";
 xButtonSize = (int)(Font.Height * .75) * btn.Text.Length;
 btn.Size = new Size(xButtonSize, yButtonSize);
 btn.Location = new Point(xMargin, 
 this.ClientSize.Height - 
 yMargin - yButtonSize);
 btn.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
 
 // Upper Right Button
 btn = new Button( );
 btn.Parent = this;
 btn.Text = "Upper Right";
 xButtonSize = (int)(Font.Height * .75) * btn.Text.Length;
 btn.Size = new Size(xButtonSize, yButtonSize);
 btn.Location = new Point(this.ClientSize.Width - 
 xMargin - xButtonSize, yMargin);
 btn.Anchor = AnchorStyles.Top | AnchorStyles.Right;
 
 // Lower Right Button
 btn = new Button( );
 btn.Parent = this;
 btn.Text = "Lower Right";
 xButtonSize = (int)(Font.Height * .75) * btn.Text.Length;
 btn.Size = new Size(xButtonSize, yButtonSize);
 btn.Location = new Point(this.ClientSize.Width - xMargin - 
 xButtonSize, 
 this.ClientSize.Height - yMargin - 
 yButtonSize);
 btn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
 
 // Middle spanning Button
 btn = new Button( );
 btn.Parent = this;
 btn.Text = "Middle Span";
 xButtonSize = this.ClientSize.Width - (2 * xMargin);
 btn.Size = new Size(xButtonSize, yButtonSize);
 btn.Location = new Point(xMargin, 
 (int)(this.ClientSize.Height / 2) - 
 yButtonSize);
 btn.Anchor = AnchorStyles.Left | AnchorStyles.Right;
 }
 
 static void Main( ) 
 {
 Application.Run(new ControlAnchor( ));
 }
 }
}

Example 7-10. Control anchoring in VB.NET (ControlAnchor.vb)

figs/vbicon.gif

imports System
imports System.Drawing
imports System.Windows.Forms
 
namespace ProgrammingWinApps
 public class ControlAnchor : inherits Form
 
 public sub New( )
 Text = "Control Anchoring"
 Size = new Size(350,400)
 
 dim xButtonSize, yButtonSize as integer
 
 ' All the buttons will have the same height.
 yButtonSize = Font.Height * 2
 
 ' All the buttons will be the same distance from the 
 ' edge of the form.
 dim xMargin, yMargin as integer
 xMargin = Font.Height * 2
 yMargin = Font.Height * 2
 
 ' Upper Left Button
 dim btn as new Button( )
 btn.Parent = me
 btn.Text = "Upper Left"
 xButtonSize = Cint(Font.Height * .75) * btn.Text.Length
 btn.Size = new Size(xButtonSize, yButtonSize)
 btn.Location = new Point(xMargin, yMargin)
 
 ' Lower Left Button
 btn = new Button( )
 btn.Parent = me
 btn.Text = "Lower Left"
 xButtonSize = Cint(Font.Height * .75) * btn.Text.Length
 btn.Size = new Size(xButtonSize, yButtonSize)
 btn.Location = new Point(xMargin, _
 me.ClientSize.Height - yMargin - _
 yButtonSize)
 btn.Anchor = AnchorStyles.Bottom Or AnchorStyles.Left
 
 ' Upper Right Button
 btn = new Button( )
 btn.Parent = me
 btn.Text = "Upper Right"
 xButtonSize = Cint(Font.Height * .75) * btn.Text.Length
 btn.Size = new Size(xButtonSize, yButtonSize)
 btn.Location = new Point(me.ClientSize.Width - xMargin - _
 xButtonSize, _
 yMargin)
 btn.Anchor = AnchorStyles.Top Or AnchorStyles.Right
 
 ' Lower Right Button
 btn = new Button( )
 btn.Parent = me
 btn.Text = "Lower Right"
 xButtonSize = Cint(Font.Height * .75) * btn.Text.Length
 btn.Size = new Size(xButtonSize, yButtonSize)
 btn.Location = new Point(me.ClientSize.Width - xMargin - _
 xButtonSize, _ 
 me.ClientSize.Height - yMargin - _
 yButtonSize)
 btn.Anchor = AnchorStyles.Bottom Or AnchorStyles.Right
 
 ' Middle spanning Button
 btn = new Button( )
 btn.Parent = me
 btn.Text = "Middle Span"
 xButtonSize = me.ClientSize.Width - (2 * xMargin)
 btn.Size = new Size(xButtonSize, yButtonSize)
 btn.Location = new Point(xMargin, _ 
 Cint(me.ClientSize.Height / 2) - _
 yButtonSize)
 btn.Anchor = AnchorStyles.Left Or AnchorStyles.Right
 end sub
 
 public shared sub Main( ) 
 Application.Run(new ControlAnchor( ))
 end sub
 end class
end namespace

Figure 7-5. Control anchoring

figs/pnwa_0705.gif

In this example, there are no member variables; all the variables and controls are declared and instantiated inside the constructor of the Form. A pair of integers are declared to hold the dimensions of each Button, and then the common Button height is calculated based on the Height property of the current Font:

figs/csharpicon.gif

int xButtonSize, yButtonSize;
yButtonSize = Font.Height * 2;

figs/vbicon.gif

dim xButtonSize, yButtonSize as integer
yButtonSize = Font.Height * 2

The margin between each of the buttons and the edge of the form is also calculated based on the Height property of the current Font. All the buttons will have the same margin:

figs/csharpicon.gif

int xMargin, yMargin;
xMargin = yMargin = Font.Height * 2;

figs/vbicon.gif

dim xMargin, yMargin as integer
xMargin = Font.Height * 2
yMargin = Font.Height * 2

Each button is instantiated and specified in a manner similar to each other. A typical corner button looks like the following:

figs/csharpicon.gif

// Lower Left Button
btn = new Button( );
btn.Parent = this;
btn.Text = "Lower Left";
xButtonSize = (int)(Font.Height * .75) * btn.Text.Length;
btn.Size = new Size(xButtonSize, yButtonSize);
btn.Location = new Point(xMargin, 
 this.ClientSize.Height - 
 yMargin - yButtonSize);
btn.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;

figs/vbicon.gif

' Lower Left Button
btn = new Button( )
btn.Parent = me
btn.Text = "Lower Left"
xButtonSize = Cint(Font.Height * .75) * btn.Text.Length
btn.Size = new Size(xButtonSize, yButtonSize)
btn.Location = new Point(xMargin, _
 me.ClientSize.Height - yMargin - _
 yButtonSize)
btn.Anchor = AnchorStyles.Bottom Or AnchorStyles.Left

The width of each button, contained in the variable xButtonSize, is based on the current Font Height and the number of characters in the Font Text property. The height of each button, contained in the variable yButtonSize, was defined previously for all buttons.

The Location property for each button is calculated by the xMargin and yMargin, defined above, and the size of the client area of the form.

Finally, the Anchor property is set for each button except for the upper-left button. The default Anchor property is equivalent to:

figs/csharpicon.gif

AnchorStyles.Top | AnchorStyles.Left

figs/vbicon.gif

AnchorStyles.Top Or AnchorStyles.Left

where the logical OR operator is used to combine more than one Anchor properties.

The important thing to note about this example is that there is no OnResize method. The size and location of each of the five buttons is not recalculated outside of the constructor. You can verify this for yourself by modifying the line of code that specifies the width of the middle button, replacing the reference to the ClientSize.Width with a hardcoded number, and observing the behavior. For example, replace this line:

figs/vbicon.gif

xButtonSize = me.ClientSize.Width - (2 * xMargin)

with this line:

figs/vbicon.gif

xButtonSize = 300

When you resize the resulting form, the middle button will continue to dynamically resize itself because it is anchored to both the left and right edges of the container.

The middle button has its width calculated based on the client size of the form minus the margin on either side:

figs/vbicon.gif

xButtonSize = me.ClientSize.Width - (2 * xMargin)

and then it is anchored to both the left and right sides of the container:

figs/vbicon.gif

btnMid.Anchor = AnchorStyles.Left Or AnchorStyles.Right

This causes the button to automatically resize itself as the form width is changed.

7.1.2.10 Docking

The Dock property is similar to the Anchor property because it associates a control with an edge of its container. However, rather than maintain a relative position near the edge, it butts right up against the edge, extending from one side of the container to the other. For example, if a control is docked along the top edge, it will extend from the left edge to the right.

Docking is often used for toolbars (docked along the top edge) or status bars (docked along the bottom edge). It is also used with TreeView controls to create Explorer type interfaces, as described in Chapter 5.

The Dock property can have any of the values contained in the DockStyle enumeration, listed in Table 7-5. Unlike the Anchor property values, the DockStyle values cannot be combined; a control can have only a single DockStyle.

If the Fill value is used, the control will dock to all four edges of its container and totally fill it. If a value of None is used, the control will not be docked.

Docking and anchoring are mutually exclusive. A control cannot be both docked and anchoredi.e., the value of one of those properties must be set to None.

Table 7-5. DockStyle values

Value

Bottom

Fill

Left

None

Right

Top

The programs shown in Example 7-11 and Example 7-12 demonstrate two docked buttons, one to the top of the form client area and one to the bottom. You will see that as you resize the form, the buttons resize to fill the edge automatically. When the program is compiled and run, it will look something like that shown in Figure 7-6.

Notice in these examples that although a Height property is defined for the buttons, there is no Width or Size property. Also, there is no Location property defined. The Dock property makes all of those properties superfluous.

If multiple controls are docked against the same edge of a container, the docked control with the higher z-order is located closer to the center of the client area. The docked control with the lowest z-order is immediately adjacent to the edge of the container. (Z-order was discussed earlier in this chapter.) You can verify this by modifying the code in Example 7-11 or Example 7-12 to make both buttons dock against the same edge.

Example 7-11. Docking in C# (ControlDock.cs)

figs/csharpicon.gif

using System;
using System.Drawing;
using System.Windows.Forms;
 
namespace ProgrammingWinApps
{
 public class ControlDock : Form
 {
 public ControlDock( )
 {
 Text = "Control Docking";
 Size = new Size(350,400);
 
 // Both buttons will have the same height.
 int yButtonSize = Font.Height * 2;
 
 // First Button
 Button btn = new Button( );
 btn.Parent = this;
 btn.Text = "First Button";
 btn.Height = yButtonSize;
 btn.Dock = DockStyle.Top;
 
 // Second Button
 btn = new Button( );
 btn.Parent = this;
 btn.Text = "Second Button";
 btn.Height = yButtonSize;
 btn.Dock = DockStyle.Bottom;
 }
 
 static void Main( ) 
 {
 Application.Run(new ControlDock( ));
 }
 }
}

Example 7-12. Docking in VB.NET (ControlDock.vb)

figs/vbicon.gif

imports System
imports System.Drawing
imports System.Windows.Forms
 
namespace ProgrammingWinApps
 public class ControlDock : inherits Form
 
 public sub New( )
 Text = "Control Docking"
 Size = new Size(350,400)
 
 ' Both buttons will have the same height.
 dim yButtonSize as integer = Font.Height * 2
 
 ' First Button
 dim btnFirst as new Button( )
 btnFirst.Parent = me
 btnFirst.Text = "First Button"
 btnFirst.Height = yButtonSize
 btnFirst.Dock = DockStyle.Top
 ' Second Button
 dim btnSecond as new Button( )
 btnSecond.Parent = me
 btnSecond.Text = "Second Button"
 btnSecond.Height = yButtonSize
 btnSecond.Dock = DockStyle.Bottom
 end sub
 
 public shared sub Main( ) 
 Application.Run(new ControlDock( ))
 end sub
 end class
end namespace

Figure 7-6. Control docking

figs/pnwa_0706.gif

7.1.2.11 Painting the control

All controls have a Paint event, which is raised when the control is about to be drawn. You can add an event handler to the Paint event delegate to dictate how the control gets drawn. This allows you to draw text and graphics directly on the client area of a form, for example, or change the appearance of a button.

For a complete discussion of delegates and events, see Chapter 4.

The Paint event, drawing, and graphics will be covered in detail in Chapter 10.

7.1.2.12 Tag property

It is often useful to be able to associate some data with a control. This can be done using the Tag property.

This property can be of any type, not just a string. In the programs shown in Example 7-13 and Example 7-14, the Tag property is of type FontStyle. But it could also be of type Color, Location, or Size, to name a few, or of a type defined in your program, or it can even be a DataSet, so that the data associated with a control will be readily available.

The programs shown in Example 7-13 and Example 7-14 present several buttons, one for each member of the FontStyle enumeration. The contents of the FontStyle enumeration are put into an array of FontStyle's, which is then iterated through using a foreach loop. On each iteration, a new button is created. Clicking on one of these buttons applies that FontStyle to the Label control on the form.

Example 7-13. Control Tag property in C# (Tags.cs)

figs/csharpicon.gif

using System;
using System.Drawing;
using System.Windows.Forms;
 
namespace ProgrammingWinApps
{
 public class Tags : Form
 {
 Label lbl;
 
 public Tags( )
 {
 Text = "Control Tag Property";
 Size = new Size(300,200);
 
 lbl = new Label( );
 lbl.Text = "The quick brown fox...";
 lbl.AutoSize = true;
 lbl.Parent = this;
 lbl.Location = new Point(0,0);
 
 FontStyle theEnum = new FontStyle( );
 FontStyle[ ] theStyles = 
 (FontStyle[ ])Enum.GetValues(theEnum.GetType( ));
 
 int i = 1;
 foreach (FontStyle style in theStyles)
 {
 Button btn = new Button( );
 btn.Parent = this;
 btn.Location = new Point(25,25 * i);
 btn.Size = new Size(75,20);
 btn.Text = style.ToString( );
 btn.Tag = style;
 btn.Click += new System.EventHandler(btn_Click);
 i++;
 }
 }
 
 static void Main( ) 
 {
 Application.Run(new Tags( ));
 }
 
 private void btn_Click(object sender, EventArgs e)
 {
 Button btn = (Button)sender;
 FontStyle fs = (FontStyle)btn.Tag;
 lbl.Font = new Font(lbl.Font, fs);
 }
 }
}

Example 7-14. Control Tag property in VB.NET (Tags.vb)

figs/vbicon.gif

imports System
imports System.Drawing
imports System.Windows.Forms
 
namespace ProgrammingWinApps
 public class Tags : inherits Form
 
 private lbl as Label
 
 public sub New( )
 Text = "Control Tag Property"
 Size = new Size(300,200)
 
 lbl = new Label( )
 lbl.Text = "The quick brown fox..."
 lbl.AutoSize = true
 lbl.Parent = me
 lbl.Location = new Point(0,0)
 
 dim theEnum as new FontStyle( )
 dim theStyles as FontStyle( ) = CType([Enum].GetValues( _
 theEnum.GetType( )), FontStyle( ))
 
 dim i as integer = 1
 dim style as FontStyle
 for each style in theStyles
 dim btn as new Button( )
 btn.Parent = me
 btn.Location = new Point(25,25 * i)
 btn.Size = new Size(75,20)
 btn.Text = style.ToString( )
 btn.Tag = style
 AddHandler btn.Click, AddressOf btn_Click
 i += 1
 next
 end sub
 
 public shared sub Main( ) 
 Application.Run(new Tags( ))
 end sub
 
 private sub btn_Click(ByVal sender as object, _
 ByVal e as EventArgs)
 dim btn as Button = CType(sender, Button)
 dim fs as FontStyle = CType(btn.Tag, FontStyle)
 lbl.Font = new Font(lbl.Font, fs)
 end sub
 
 end class
end namespace

In the constructor for the Form, the Text and Size properties of the Form are set and the Label control is instantiated and specified.

The next two lines in the code are both crucial and dense. They get the contents of the FontStyle enumeration and, using reflection, put the values into an array of FontStyles:

figs/csharpicon.gif

FontStyle theEnum = new FontStyle( );
FontStyle[] theStyles = (FontStyle[ ])Enum.GetValues(theEnum.GetType( ));

figs/vbicon.gif

dim theEnum as new FontStyle( )
dim theStyles as FontStyle( ) = CType((Enum].GetValues( _
 theEnum.GetType( )), FontStyle( ))

This convolution is necessary because the foreach loop used further on in the program can loop through an array, but cannot loop through an enumeration since an enumeration is not a collection.

The Enum class provides a number of methods for handling enumerations. Since the goal is to get the contents of the enumeration into an array, the static method Enum.GetValues serves the purpose. It takes the type of the enumeration as a parameter.

The type of the enumeration can be obtained from the GetType method, but this is not staticit requires an instance of the enumeration in question. Hence, the first line in this code snippet instantiates a new instance of the FontStyle enumeration, called theEnum. This instance is then used to call GetType, which returns the type of the enumeration to GetValues, which returns an array of values. The array of values is cast to an array of FontStyle's called theStyles.

The C# version can be simplified down to a single line of code using the typeof operator, which returns the type of a class. There is no need to create an instance of type FontStyle. Thus, the following line will return an array of FontStyles:

figs/csharpicon.gif

FontStyle[ ] theStyles = (FontStyle[ ])Enum.GetValues(typeof(FontStyle));

There is no corresponding operator in VB.NET.

For a complete discussion of reflection, see Programming C# by Jesse Liberty (O'Reilly).

Next comes the foreach loop (for each in VB.NET), which iterates through the array of each FontStyle, creating a Button for each one:

figs/csharpicon.gif

int i = 1;
foreach (FontStyle style in theStyles)
{
 Button btn = new Button( );
 btn.Parent = this;
 btn.Location = new Point(25,25 * i);
 btn.Size = new Size(75,20);
 btn.Text = style.ToString( );
 btn.Tag = style;
 btn.Click += new System.EventHandler(btn_Click);
 i++;
}

figs/vbicon.gif

dim i as integer = 1
dim style as FontStyle
for each style in theStyles
 dim btn as new Button( )
 btn.Parent = me
 btn.Location = new Point(25,25 * i)
 btn.Size = new Size(75,20)
 btn.Text = style.ToString( )
 btn.Tag = style
 AddHandler btn.Click, AddressOf btn_Click
 i += 1
next

The integer counter i is declared and initialized to increment the Location property of each button. For each member of the theStyles array, a new Button control is declared and instantiated and a number of properties set. Notice that the Text property is set to the string representation of the current FontStyle. The advantage of using the Tag property is that it retains the type of the array. This will be useful shortly. Also notice that all the Buttons use the same event-handler method to respond to the Click event.

The resulting form is shown in Figure 7-7.

Figure 7-7. Control tags

figs/pnwa_0707.gif

The Click event handler does the work of determining which Button has been clicked, what FontStyle is associated with that button, and applying that FontStyle to the Label control.

figs/csharpicon.gif

private void btn_Click(object sender, EventArgs e)
{
 Button btn = (Button)sender;
 FontStyle fs = (FontStyle)btn.Tag;
 lbl.Font = new Font(lbl.Font, fs);
}

figs/vbicon.gif

private sub btn_Click(ByVal sender as object, _
 ByVal e as EventArgs)
 dim btn as Button = CType(sender, Button)
 dim fs as FontStyle = CType(btn.Tag, FontStyle)
 lbl.Font = new Font(lbl.Font, fs)
end sub

The first line in the method handles the job of determining which Button raised the Click event. The sender argument is cast to a Button type and assigned to a local variable btn. This btn variable is independent from the btn variable referred to in the foreach loop above; they just happen to have the same name, but a different scope.

The next line in the method gets the Tag property from the Button and casts it to an object of type FontStyle. Although it may seem that you could just as easily use the Text property and cast it to type FontStyle, this will not work. If you try substituting this line:

figs/csharpicon.gif

FontStyle fs = (FontStyle)btn.Text;

figs/vbicon.gif

dim fs as FontStyle = CType(btn.Text, FontStyle)

you will get the following compile error:

Cannot convert type 'string' to 'System.Drawing.FontStyle'

Finally, the FontStyle is applied to the Label control. This too is not as simple as you might think, since the Font.Style property of a control's font is read-only. In fact, since the Font object is immutable, i.e., all of its properties are read-only, the only way to change a Font property is to assign a new Font object based on the existing Font.

The Font object has over a dozen different overloaded constructors, covered in Chapter 9. The one used here takes an existing Font object as a prototype from which to create the new Font object, and a member of the FontStyle enumeration to apply to the new Font. (Multiple FontStyle enumeration values could be combined using the OR operator.)

7.1.2.13 Tabbing

The user can shift focus from one control to the next on a form by pressing the Tab key. (The arrow keys also shift focus until the focus arrives at a control such as a TextBox, where the arrow keys have functionality within the control.) When tabbing among controls, the order in which the controls get focus is known as the tab order. You can traverse the tab order in reverse by pressing Shift-Tab.

Two properties of Control that affect tabbing are listed in Table 7-6.

Table 7-6. Tabbing properties

Property

Type

Description

TabStop

Boolean

If true, the default, the control can get focus while participating in the tab order.

TabIndex

integer

The index of a control in the tab order. Lower values are earlier in the tab order. If two or more controls have the same TabIndex, the control with the lower z-order will get focus first.

Some controls, such as the Label, participate in the tab order, but their TabStop property is false, so they cannot receive focus. Focus instead goes to the next control in the tab order. Later chapters will cover this topic.

When developing forms in VS.NET, the TabIndex initially tracks the control's z-order. However, you can subsequently change either the TabIndex property or the z-order independently of each other.

7.1.2.14 Keyboard interaction

Although the keyboard is probably the primary means of user interaction with your application, the Control class provides such seamless keyboard support that you can usually ignore it. That said, a few issues bear further discussion.

A slew of events associated with keyboard interaction allow you to determine which key has been pressed, if any modifier keys (such as Shift, Alt, or Ctrl) have been pressed, and so on. They also allow you to trap key presses, disallow them, or send an alternative key press instead. All key events are covered thoroughly in Chapter 4.

Input focus

When a key is pressed on the keyboard attached to a computer running Windows, the keyboard events are received by the object with input focus. This focus may be a specific window or form, or it may be a child control on a form. In any event, Windows handles all the details to ensure that the correct object receives the keyboard input.

The focus can be shifted by the user by clicking the mouse on an object or traversing the tab order with the Tab key, Shift Tab, or one of the arrow keys. You can also set the input focus to a specific control programmatically using the Focus( ) method.

For a control to be able to receive focus, its ControlStyles.Selectable style bit must be set to true, it must be contained by another control, and all its parent controls must be both visible and enabled. Some controls, including Panel, GroupBox, PictureBox, ProgressBar, Splitter, Label, and LinkLabel (when there is no link in the control) cannot receive focus under any circumstances.

Navigation

One way to use the keyboard to navigate around an application is by tabbing. Tabbing and the tab order were discussed previously in this chapter.

Another way to navigate an application is with the shortcut and access keys, which will be covered more thoroughly in Chapter 18. In brief, access keys allow keyboard navigation of menu items by pressing Alt in combination with a character underlined in the menu item. Shortcut keys are single keys, typically function keys (F1, F2, etc.), that are associated with a menu item and invoke that menu item.

7.1.2.15 ImageLists

The ImageList is not really a control, but rather a component that contains an indexed collection of Image objects. These images can then be displayed by any control that has an ImageList property, including Label, LinkLabel, Button, CheckBox, RadioButton, ListView (which actually has two variants of the ImageList property: a SmallImageList and a LargeImageList), TabControl, ToolBar, and TreeView.

The Image objects contained in the Images collection can be members of, or descended from, one of the following classes:

Bitmap

Contains pixel data for an image and its attributes.

Icon

A small, transparent bitmap used to represent an object.

The documentation released with Versions 1 and 1.1 of the .NET Framework SDK states that metafiles, which are used to describe a sequence of graphics operations, may also be added to an image list. However, this is incorrect. If you attempt to add a metafile to an image list, a compiler error will result.

 

Setting the ImageList property of a control associates an image list with that control. Your code can then dynamically set the image to be displayed on the control at runtime by referencing the zero-based index of the desired image In the Images collection. This will be demonstrated shortly.

Some of the controls (Label, LinkLabel, Button, CheckBox, and RadioButton) that can use the ImageList component also have an Image property, which allows a single image to be associated with the control. If a control has both Image and ImageList properties, they can not both be set for the same control at the same time. If the Image property is set, then the ImageList property will be set to a null reference (Nothing in VB.NET) and the ImageIndex property will be set to the default value of -1. If the ImageList property is set, then the Image property will be set to a null reference (Nothing in VB.NET).

It is also possible to use an ImageList to draw images directly on the client area of the form, independently of any controls, using the overloaded Draw method of the ImageList class.

A form can have multiple image lists defined. For example, two buttons on a form can each have a different ImageList properties set, each displaying the images from a different collection of images. Conversely, the same image list can be used by multiple controls on a form. So, for example, both buttons could reference the same image list, and the code that specifies the ImageIndex for each button might select the same image for each button or different images from the same collection for each button. However, each control can reference images only from a single image list.

All images in an image list must be the same size. The size of the images displayed by the image list can be set using the ImageSize property. If an image is added to the Images collection that is not the correct size, it will be scaled to the correct size when it is displayed.

The commonly used properties of the ImageList component are listed in Table 7-7.

Table 7-7. ImageList properties

Property

Value type

Description

ColorDepth

ColorDepth

Read/write. The number of colors available for the image. Values come from the ColorDepth enumeration, which are listed in Table 7-8. The default value is Depth8Bit.

Images

ImageList.ImageCollection

Read/write. A collection of images. The ImageList.ImageCollection class has a number of properties and methods, with the common ones listed in Table 7-9, and Table 7-10.

ImageSize

Size

Read/write. The Height and Width of the images in the image list. The default size is 16 x 16 pixels, and the maximum size is 256 x 256.

TransparentColor

Color

Read/write. The color to treat as transparent. The default is Color.Transparent.

Table 7-8. ColorDepth enumeration values

Member

Depth4Bit

Depth8Bit

Depth16Bit

Depth24Bit

Depth32Bit

Table 7-9. ImageList.ImageCollection properties

Property

Value type

Description

Count

Integer

Returns number of images in the collection. The default is zero.

Empty

Boolean

Returns true if there are no images in the collection. The default is false.

Item

Image

Read/write. The image at the specified index.

Table 7-10. ImageList.ImageCollection methods

Method

Description

Add

Overloaded. Adds a specified icon or image to the ImageList.

AddStrip

Adds one or more images contained in a bitmap to the ImageList.

Clear

Removes all images and masks from the ImageList.

RemoveAt

Removes the image at the specified index from the ImageList.

Only image objects are added to the Images collection, not files or the names of files containing those images. Once an image is added to an image list, the source filename for that image is no longer available from the image list. It may, of course, still be available elsewhere, such as from an array of filenames, as demonstrated in the following example.

If you are developing your application in Visual Studio .NET and you add the images to the ImageList using the Image Collection Editor, described later in this section, there will be no persistence of the filenames associated with the images.

In Example 7-15 (in C#) and Example 7-16 (in VB.NET), an array is filled with a number of image filenames. (These images are included with a standard Visual Studio .NET installation.) The array is iterated to add all the images to an ImageList. Three controls are added which display an image from the ImageList: a Label, a LinkLabel, and a Button. A NumericUpDown control is provided to allow the user to select the index of the image to display on the controls. The Button control displays a different image from the Label and LinkLabel controls. When either program is compiled and run and the NumericUpDown value is changed, the result is shown in Figure 7-8.

Figure 7-8. Image lists

figs/pnwa_0708.gif

Example 7-15. ImageList in C# (ImageLists.cs)

figs/csharpicon.gif

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
 
namespace ProgrammingWinApps
{
 public class ImageLists : Form
 {
 ImageList imgList;
 Label lbl;
 LinkLabel lnk;
 Button btn;
 NumericUpDown nmbrUpDown;
 
 public ImageLists( )
 {
 Text = "ImageLists";
 Size = new Size(300,300);
 
 imgList = new ImageList( );
 Image img;
 
 // Use an array to add filenames to the ImageList
 String[ ] arFiles = {
 @"C:Program FilesMicrosoft Visual Studio .NETCommon7" + 
 @"Graphicsitmapsassortedhappy.bmp",
 @"C:Program FilesMicrosoft Visual Studio .NETCommon7" + 
 @"Graphicsitmapsassortedhand.bmp",
 @"C:Program FilesMicrosoft Visual Studio .NETCommon7" + 
 @"Graphicsitmapsassortedphone.bmp",
 @"C:Program FilesMicrosoft Visual Studio .NETCommon7" + 
 @"Graphicsiconsindustryicycle.ico",
 @"C:Program FilesMicrosoft Visual Studio .NETCommon7" + 
 @"Graphicsiconsindustryhammer.ico",
 @"C:Program FilesMicrosoft Visual Studio .NETCommon7" + 
 @"Graphicsiconsindustry
ocket.ico"
 };
 
 for (int i = 0; i < arFiles.Length; i++)
 {
 img = Image.FromFile(arFiles[i]);
 imgList.Images.Add(img);
 }
 
 // Change the size
 imgList.ImageSize = new Size(32, 32);
 
 // Replace an image
 img = Image.FromFile(
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + 
 "Graphics\icons\industry\wrench.ico");
 imgList.Images[imgList.Images.Count - 1] = img;
 
 lbl = new Label( );
 lbl.Parent = this;
 lbl.Text = "The quick brown fox...";
 lbl.Location = new Point(0,0);
 lbl.Size = new Size(lbl.PreferredWidth + imgList.ImageSize.Width, 
 imgList.ImageSize.Height + 10);
 lbl.BorderStyle = BorderStyle.Fixed3D;
 lbl.ImageList = imgList;
 lbl.ImageIndex = 0;
 lbl.ImageAlign = ContentAlignment.MiddleRight;
 
 int yDelta = lbl.Height + 10;
 
 lnk = new LinkLabel( );
 lnk.Parent = this;
 lnk.Text = "The slow red dog...";
 lnk.Size = new Size(lnk.PreferredWidth + imgList.ImageSize.Width, 
 imgList.ImageSize.Height + 10);
 lnk.Location = new Point(0, yDelta);
 lnk.ImageList = imgList;
 lnk.ImageIndex = 0;
 lnk.ImageAlign = ContentAlignment.MiddleRight;
 
 btn = new Button( );
 btn.Parent = this;
 btn.ImageList = imgList;
 btn.ImageIndex = imgList.Images.Count - 1;
 btn.Location = new Point(0, 2 * yDelta);
 btn.Size = new Size(3 * imgList.ImageSize.Width, 
 2 * imgList.ImageSize.Height);
 
 // Create numeric updown to select the image
 nmbrUpDown = new NumericUpDown( );
 nmbrUpDown.Parent = this;
 nmbrUpDown.Location = new Point(0, 4 * yDelta);
 nmbrUpDown.Value = 0;
 nmbrUpDown.Minimum = 0;
 nmbrUpDown.Maximum = imgList.Images.Count - 1;
 nmbrUpDown.Width = 50;
 nmbrUpDown.ReadOnly = true;
 nmbrUpDown.ValueChanged += 
 new System.EventHandler(nmbrUpDown_ValueChanged);
 
 } // close ImageLists constructor
 
 static void Main( ) 
 {
 Application.Run(new ImageLists( ));
 }
 
 private void nmbrUpDown_ValueChanged(object sender, EventArgs e)
 {
 NumericUpDown n = (NumericUpDown)sender;
 lbl.ImageIndex = (int)n.Value;
 lnk.ImageIndex = (int)n.Value;
 btn.ImageIndex = (imgList.Images.Count - 1) - (int)n.Value;
 }
 }
}

Example 7-16. ImageList in VB.NET (ImageLists.vb)

figs/vbicon.gif

imports System
imports System.Drawing
imports System.Windows.Forms
 
namespace ProgrammingWinApps
 public class ImageLists : inherits Form
 
 dim imgList as ImageList
 dim lbl as Label 
 dim lnk as LinkLabel
 dim btn as Button
 dim nmbrUpDown as NumericUpDown
 
 public sub New( )
 Text = "ImageLists"
 Size = new Size(300,300)
 
 imgList = new ImageList( )
 dim img as Image
 dim i as integer
 
 dim arFiles as string( ) = { _
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + _
 "Graphics\bitmaps\assorted\happy.bmp", _
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + _
 "Graphics\bitmaps\assorted\hand.bmp", _
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + _
 "Graphicsitmapsassortedphone.bmp", _
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + _
 "Graphics\icons\industry\bicycle.ico", _
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + _
 "Graphicsiconsindustryhammer.ico", _
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + _
 "Graphics\icons\industry\rocket.ico" _
 }
 
 for i = 0 to arFiles.Length - 1
 img = Image.FromFile(arFiles(i))
 imgList.Images.Add(img)
 next
 
 ' Change the size
 imgList.ImageSize = new Size(32, 32)
 
 ' Replace an image
 img = Image.FromFile( _
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + _
 "Graphics\icons\industry\wrench.ico")
 imgList.Images(imgList.Images.Count - 1) = img
 
 
 lbl = new Label( )
 lbl.Parent = me
 lbl.Text = "The quick brown fox..."
 lbl.Location = new Point(0,0)
 lbl.Size = new Size (lbl.PreferredWidth + imgList.ImageSize.Width, _
 imgList.ImageSize.Height + 10)
 lbl.BorderStyle = BorderStyle.Fixed3D
 lbl.ImageList = imgList
 lbl.ImageIndex = 0
 lbl.ImageAlign = ContentAlignment.MiddleRight
 
 dim yDelta as integer = lbl.Height + 10
 
 lnk = new LinkLabel( )
 lnk.Parent = me
 lnk.Text = "The slow red dog..."
 lnk.Size = new Size( _
 lnk.PreferredWidth + imgList.ImageSize.Width, _
 imgList.ImageSize.Height + 10)
 lnk.Location = new Point(0, yDelta)
 lnk.ImageList = imgList
 lnk.ImageIndex = 0
 lnk.ImageAlign = ContentAlignment.MiddleRight
 
 btn = new Button( )
 btn.Parent = me
 btn.ImageList = imgList
 btn.ImageIndex = imgList.Images.Count - 1
 btn.Location = new Point(0, 2 * yDelta)
 btn.Size = new Size(3 * imgList.ImageSize.Width, _
 2 * imgList.ImageSize.Height)
 
 ' Create numeric updown to select the image
 nmbrUpDown = new NumericUpDown( )
 nmbrUpDown.Parent = me
 nmbrUpDown.Location = new Point(0, 4 * yDelta)
 nmbrUpDown.Value = 0
 nmbrUpDown.Minimum = 0
 nmbrUpDown.Maximum = imgList.Images.Count - 1
 nmbrUpDown.Width = 50
 nmbrUpDown.ReadOnly = true
 AddHandler nmbrUpDown.ValueChanged, _
 AddressOf nmbrUpDown_ValueChanged
 end sub
 
 public shared sub Main( ) 
 Application.Run(new ImageLists( ))
 end sub
 
 private sub nmbrUpDown_ValueChanged(ByVal sender as object, _
 ByVal e as EventArgs)
 dim n as NumericUpDown = CType(sender, NumericUpDown)
 lbl.ImageIndex = CType(n.Value, Integer)
 lnk.ImageIndex = CType(n.Value, Integer)
 btn.ImageIndex = (imgList.Images.Count - 1) - _
 CType(n.Value, Integer)
 end sub
 end class
end namespace

In Example 7-15 and Example 7-16, the ImageList and the controls on the form are declared as member variables so that they will be visible to all the methods of the form. There is a Label, a LinkLabel, a Button, and a NumericUpDown control. (These controls will be discussed in detail in subsequent chapters.)

In the constructor of the class, the ImageList component is instantiated and a variable of type Image is declared:

figs/csharpicon.gif

imgList = new ImageList( );
Image img;

figs/vbicon.gif

imgList = new ImageList( )
dim img as Image

Next a string array is declared, instantiated, and populated with several filenames of image files. (In a real application, you would not distribute image files this way, but embed them as resources in the executable.) The array is then iterated, instantiating an Image object from each filename using the static Images.FromFile method. That Image is then added to the ImageList:

figs/csharpicon.gif

for (int i = 0; i < arFiles.Length; i++)
{
 img = Image.FromFile(arFiles[i]);
 imgList.Images.Add(img);
}

figs/vbicon.gif

for i = 0 to arFiles.Length - 1
 img = Image.FromFile(arFiles(i))
 imgList.Images.Add(img)
next

Next the size of all the images in the ImageList is changed from the default of 16 x 16 to 32 x 32:

imgList.ImageSize = new Size(32, 32);

The next two statements demonstrate how the Images collection can be treated similarly to an array, with the ImageList.ImageCollection index used as in indexer into the collection. In this code snippet, an image is set (i.e., the last image (rocket.ico) is replaced with wrench.ico):

figs/csharpicon.gif

img = Image.FromFile(
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + 
 "Graphics\icons\industry\wrench.ico");
imgList.Images(imgList.Images.Count - 1] = img;

figs/vbicon.gif

img = Image.FromFile( _
 "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + _
 "Graphics\icons\industry\wrench.ico")
imgList.Images(imgList.Images.Count - 1) = img

If you try to reference an index higher than what exists in the image list, an exception will be thrown.

As of this writing (Version 1.1), there is some sort of bug at work here. The image contained in wrench.ico is 16 x 16 pixels, and the current size of the ImageList is 32 x 32. The replacement image is overlaid on the original rather than being properly scaled and replacing the original image. Hence, the rocket is visible under the wrench. If the ImageList is not resized, then the wrench image properly replaces the rocket image. If the ImageList is resized after the wrench image replaces the rocket, then all the images in the ImageList are lost (i.e., the Count property goes to 0).

You could also get an image from the image list, with a line of code such as this:

figs/csharpicon.gif

img = imgList.Images[2];

figs/vbicon.gif

img = imgList.Images(2)

Now that the image list is populated, it can be used by the different controls on the form. Each control is declared and specified. The Label control, lbl, has its Size property set based on its own PreferredWidth property (which automatically takes into account the current font and the length of its Text property) and the Width and Height of the ImageSize property of the image list:

lbl.Size = new Size (lbl.PreferredWidth + imgList.ImageSize.Width, 
 imgList.ImageSize.Height + 10);

The ImageList property is set to the image list created above, the ImageIndex property is set to zero so the control will display the first image in the collection, and the alignment of the image is set using the ImageAlign property:

lbl.ImageList = imgList;
lbl.ImageIndex = 0;
lbl.ImageAlign = ContentAlignment.MiddleRight;

At this point, now that the Height of lbl is known, an integer, yDelta, is declared based on that dimension, to be used by the other controls on the form to aid in setting the vertical position.

The LinkLabel control is declared and specified very similarly to the Label control.

The Button control is a bit different. Like the Label and LinkLabel on the form, its ImageList property is set:

btn.ImageList = imgList;

But rather than set the ImageIndex property to point to the first Image in the collection, it is set to point to the last image in the collection:

btn.ImageIndex = imgList.Images.Count - 1;

The Size property of the Button is derived entirely from the ImageSize property, multiplying both the ImageSize.Width and ImageSize.Height properties by integer factors to give a more pleasing appearance.

The final control on the form is a NumericUpDown control. This control allows the user to cycle through a range of integers. An event is raised every time the integer value of the control is changed. The event handler for this event can then change the image from the Images collection that is displayed on each of the other controls.

The initial value and the minimum value of the NumericUpDown is set to 0, and its maximum value is set to the highest index value of the collection, using the Count property of the collection. Remember that the index of the collection is zero based, so you must subtract 1 from the Count.

nmbrUpDown.Value = 0;
nmbrUpDown.Minimum = 0;
nmbrUpDown.Maximum = imgList.Images.Count - 1;

The event handler method for the ValueChanged event is added to the event delegate:

figs/csharpicon.gif

nmbrUpDown.ValueChanged +=
 new System.EventHandler(nmbrUpDown_ValueChanged);

figs/vbicon.gif

AddHandler nmbrUpDown.ValueChanged, _
 AddressOf nmbrUpDown_ValueChanged

The event handler method, nmbrUpDown_ValueChanged, is reproduced here:

figs/csharpicon.gif

private void nmbrUpDown_ValueChanged(object sender, EventArgs e)
{
 NumericUpDown n = (NumericUpDown)sender;
 lbl.ImageIndex = (int)n.Value;
 lnk.ImageIndex = (int)n.Value;
 btn.ImageIndex = (imgList.Images.Count - 1) - (int)n.Value;
}

figs/vbicon.gif

private sub nmbrUpDown_ValueChanged(ByVal sender as object, _
 ByVal e as EventArgs)
 dim n as NumericUpDown = CType(sender, NumericUpDown)
 lbl.ImageIndex = CType(n.Value, Integer)
 lnk.ImageIndex = CType(n.Value, Integer)
 btn.ImageIndex = (imgList.Images.Count - 1) - _
 CType(n.Value, Integer)
end sub

The first line of code in the method casts the sender object as a variable of type NumericUpDown. Then the Value property of that object, i.e., the new value, can be cast as an integer and used to set the ImageIndex property for each control, forcing each of the controls to display the appropriate image from the Images collection. Notice how the ImageIndex of the Button control is calculated to go backward through the collection.

As mentioned earlier, when creating an image list in Visual Studio .NET, you can use the Image Collection Editor to add images. To add an image list to a form, drag an ImageList component from the ToolBox anywhere onto the form. It will not be visible on the form itself, but will display at the bottom of the Design window. Click on the ImageList there, and the Properties window will show its properties. (If need be, right-click on the ImageList component on the Design window and select Properties.)

Click on the Images property and a build button will appear (...). Clicking on that button will present the Image Collection Editor, shown in Figure 7-9, after adding two images to the collection. Clicking on the Add button will bring up a standard File Open dialog box, allowing you to browse your system for files.

Notice that the properties displayed for each image on the right side of the dialog box do not include the source filename. The image is essentially copied to the ImageList. If you need to know the name of the image, you will need to use some means other than this collection editor, such as the array used in Example 7-15 and Example 7-16.

Figure 7-9. Image Collection Editor

figs/pnwa_0709.gif

If you examine the source code generated by Visual Studio .NET for the ImageList inside the region labeled Windows Form Designer generated code, you will see a line similar to this:

figs/csharpicon.gif

this.imageList1.ImageStream =
 ((System.Windows.Forms.ImageListStreamer)(resources.GetObject(
 "imageList1.ImageStream")));

figs/vbicon.gif

Me.ImageList1.ImageStream = CType(resources.GetObject( _
 "ImageList1.ImageStream"), System.Windows.Forms.ImageListStreamer)

The bitmaps for the images are serialized into a resource file, with an extension of .resx. This is an XML file, which you can examine in a text editor. There you will find tags similar to the following:

 
 
 AAEAAAD/////AQAAAAAAAAAMAgAAAFpTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVy
 MC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkz
 

All the image data from the image list is encoded within the tags.

Windows Forms and the .NET Framework

Getting Started

Visual Studio .NET

Events

Windows Forms

Dialog Boxes

Controls: The Base Class

Mouse Interaction

Text and Fonts

Drawing and GDI+

Labels and Buttons

Text Controls

Other Basic Controls

TreeView and ListView

List Controls

Date and Time Controls

Custom Controls

Menus and Bars

ADO.NET

Updating ADO.NET

Exceptions and Debugging

Configuration and Deployment



Programming. NET Windows Applications
Programming .Net Windows Applications
ISBN: 0596003218
EAN: 2147483647
Year: 2003
Pages: 148

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