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.
|
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.
|
Figure 7-1. Control class hierarchy
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.
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)
using System;
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)
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
To compile the code, use the appropriate following command:
csc /out:ControlParent.exe /t:winexe ControlParent.cs
vbc /out:ControlParent.exe /t:winexe /r:system.dll,system.drawing.dll,system.windows.forms.dll, system.data.dll /imports:Microsoft.VisualBasic ControlParent.vb
|
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:
// Controls.Add(btn); btn.Parent = this;
' 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:
this.Controls.AddRange(new Control[ ] {btn1, btn2, btn3})
me.Controls.AddRange(new Control( ) {btn1, btn2, btn3})
|
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.
|
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
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
|
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.
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)
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)
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 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.
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)
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)
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;
|
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.
|
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
The event handler for the Change button (btnChange_Change) changes the size of the form to 800 pixels wide x 200 pixels high:
this.Size = new Size(800,200);
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)
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)
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.
|
The line of code that calculates the width of the button is as follows:
xButtonSize = (int)( Font.Height * .75) * btnShow.Text.Length;
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.
|
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:
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); }
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
|
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:
base.OnResize(e);
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 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)
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:
// // 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);
' '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:
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.
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)
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)
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
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:
int xButtonSize, yButtonSize; yButtonSize = Font.Height * 2;
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:
int xMargin, yMargin; xMargin = yMargin = Font.Height * 2;
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:
// 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;
' 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:
AnchorStyles.Top | AnchorStyles.Left
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:
xButtonSize = me.ClientSize.Width - (2 * xMargin)
with this line:
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:
xButtonSize = me.ClientSize.Width - (2 * xMargin)
and then it is anchored to both the left and right sides of the container:
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.
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)
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)
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
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.
|
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)
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)
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:
FontStyle theEnum = new FontStyle( ); FontStyle[] theStyles = (FontStyle[ ])Enum.GetValues(theEnum.GetType( ));
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.
|
Next comes the foreach loop (for each in VB.NET), which iterates through the array of each FontStyle, creating a Button for each one:
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++; }
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
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.
private void btn_Click(object sender, EventArgs e) { Button btn = (Button)sender; FontStyle fs = (FontStyle)btn.Tag; lbl.Font = new Font(lbl.Font, fs); }
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:
FontStyle fs = (FontStyle)btn.Text;
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.
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. |
|
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.
|
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).
|
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.
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. |
Member |
---|
Depth4Bit |
Depth8Bit |
Depth16Bit |
Depth24Bit |
Depth32Bit |
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. |
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.
|
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
Example 7-15. ImageList in C# (ImageLists.cs)
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)
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:
imgList = new ImageList( ); Image img;
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:
for (int i = 0; i < arFiles.Length; i++) { img = Image.FromFile(arFiles[i]); imgList.Images.Add(img); }
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):
img = Image.FromFile( "C:\Program Files\Microsoft Visual Studio .NET\Common7\" + "Graphics\icons\industry\wrench.ico"); imgList.Images(imgList.Images.Count - 1] = img;
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.
|
You could also get an image from the image list, with a line of code such as this:
img = imgList.Images[2];
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:
nmbrUpDown.ValueChanged += new System.EventHandler(nmbrUpDown_ValueChanged);
AddHandler nmbrUpDown.ValueChanged, _ AddressOf nmbrUpDown_ValueChanged
The event handler method, nmbrUpDown_ValueChanged, is reproduced here:
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; }
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
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:
this.imageList1.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject( "imageList1.ImageStream")));
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.