Button Classes
If labels are the most common control in Windows applications, then buttons must be a close second. A command button tells the application to take some action. In the .NET Framework, the Button control is an instance of the System.Windows.Forms.Button class. It, along with the CheckBox and RadioButton controls, are derived from the ButtonBase class, as shown in Figure 11-4. The CheckBox and RadioButton controls are covered in the next section.
Figure 11-4. Button class hierarchy
The ButtonBase class has several commonly used properties listed in Table 11-9, in addition to those inherited from Control. The ImageList and ImageIndex properties were demonstrated in Section 7.1.2.15. Other common ButtonBase properties will be demonstrated with a Button control in Example 11-5 and Example 11-6.
Property |
Value type |
Description |
---|---|---|
FlatStyle |
FlatStyle |
Read/write. The flat-style appearance of the control. Valid values must be a member of the FlatStyle enumeration, listed in Table 11-10. The default value is FlatStyle.Standard. |
Image |
Image |
Read/write. The image displayed on the control. Cannot be used for the same control at the same time as the ImageList or ImageIndex properties. |
ImageAlign |
ContentAlignment |
Read/write. Aligns image displayed in the label. Values are members of the ContentAlignment enumeration, listed in Table 11-3. The default value is ContentAlignment.MiddleCenter. |
ImageIndex |
Integer |
Read/write. Zero-based index value of the Image object contained in the ImageList. |
ImageList |
ImageList |
Read/write. The image list containing the images displayed in the control. One image is displayed at a time, selected by the ImageIndex property. The ImageList stores a collection of Image objects. The ImageList component is described fully in Chapter 7. |
TextAlign |
ContentAlignment |
Aligns text displayed in the control. Values are members of the ContentAlignment enumeration, listed in Table 11-3. The default value is ContentAlignment.MiddleCenter. |
Value |
Description |
---|---|
Flat |
Control appears flat. |
Popup |
Control appears 3-D when the mouse cursor is over it, flat otherwise. The 3D appearance is the same as Standard. |
Standard |
Control appears 3-D. |
System |
Control appearance controlled by user's operating system. All image and alignment related properties of the control are ignored, as well as Button.BackColor. Use this value to force the control to conform to the system's theme. |
A Button may be clicked with the mouse or an access key (discussed below). If a Button has focus, it may also be clicked by pressing Enter or the spacebar. When a Button is clicked, a Click event is raised. This event sends the event handler an argument of type EventArgs. The EventArgs event argument is essentially a placeholder, since it exposes no properties or information about the event.
In the programs listed in Example 11-5 (in C#) and Example 11-6 (in VB.NET), a form is created with a single button on it. The Text property of the button displays the value of the button's FlatStyle property. Each time the button is clicked, a new FlatStyle property is applied to the button, the Text property is changed to the current FlatStyle, and the size of the button is adjusted to the reflect the length of the displayed string. A happy face image is also displayed on the button for good measure. The resulting form looks like Figure 11-5.
Figure 11-5. ButtonFlatStyle program
|
Example 11-5. Button demonstration in C# (ButtonFlatStyle.cs)
using System; using System.Drawing; using System.Windows.Forms; namespace ProgrammingWinApps { public class ButtonFlatStyle : Form { Button btn; int i = 1; FlatStyle[ ] theStyles; Image img; public ButtonFlatStyle( ) { Text = "Button Properties"; Size = new Size(300,200); img = Image.FromFile( @"C:Program FilesMicrosoft Visual Studio .NET 2003" + @"Common7Graphicsitmapsassortedhappy.bmp"); btn = new Button( ); btn.Parent = this; btn.Text = btn.FlatStyle.ToString( ); btn.Location = new Point(10,10); btn.BackColor = Color.LightGreen; btn.Click += new System.EventHandler(btn_Click); btn.Image = img; btn.ImageAlign = ContentAlignment.MiddleRight; btn.TextAlign = ContentAlignment.MiddleLeft; ButtonSize(btn); // get the FlatStyle values into an array FlatStyle theEnum = new FlatStyle( ); theStyles = (FlatStyle[ ])Enum.GetValues(theEnum.GetType( )); } static void Main( ) { Application.Run(new ButtonFlatStyle( )); } private void btn_Click(object sender, EventArgs e) { Button btn = (Button)sender; btn.FlatStyle = theStyles[i]; btn.Text = btn.FlatStyle.ToString( ); ButtonSize(btn); if (i < theStyles.Length - 1) i++; else i = 0; } private void ButtonSize(Button btn) { int xSize = ((int)(Font.Height * .75) * btn.Text.Length) + (img.Width * 2); int ySize = img.Height * 2; btn.Size = new Size(xSize, ySize); } } }
Example 11-6. Button demonstration in VB.NET (ButtonFlatStyle.vb)
Option Strict On imports System imports System.Drawing imports System.Windows.Forms namespace ProgrammingWinApps public class ButtonFlatStyle : inherits Form dim btn as Button dim i as integer dim theStyles as FlatStyle( ) dim img as Image public sub New( ) Text = "Button Properties" Size = new Size(300,200) img = Image.FromFile( _ "C:\Program Files\Microsoft Visual Studio .NET 2003\" + _ "Common7\Graphics\bitmaps\assorted\happy.bmp") btn = new Button( ) btn.Parent = me btn.Text = btn.FlatStyle.ToString( ) btn.Location = new Point(10,10) btn.BackColor = Color.LightGreen AddHandler btn.Click, AddressOf btn_Click btn.Image = img btn.ImageAlign = ContentAlignment.MiddleRight btn.TextAlign = ContentAlignment.MiddleLeft ButtonSize(btn) ' get the FlatStyle values into an array dim theEnum as new FlatStyle( ) theStyles = CType([Enum].GetValues( _ theEnum.GetType( )), FlatStyle( )) end sub public shared sub Main( ) Application.Run(new ButtonFlatStyle( )) end sub private sub btn_Click(ByVal sender as object, _ ByVal e as EventArgs) Dim btn as Button = CType(sender, Button) btn.FlatStyle = theStyles(i) btn.Text = btn.FlatStyle.ToString( ) ButtonSize(btn) if (i < theStyles.Length - 1) then i = i + 1 else i = 0 end if end sub private sub ButtonSize(btn as Button) dim xSize as integer = (CType(Font.Height * .75, integer) * _ btn.Text.Length) + (img.Width * 2) dim ySize as integer = img.Height * 2 btn.Size = new Size(xSize, ySize) end sub end class end namespace
The programs in Example 11-5 and Example 11-6 start off by declaring several class members, including a Button, an Image, an integer, and an array of FlatStyle objects. All of these members will be used throughout the class.
In the constructor, the image is instantiated to the bitmap contained in happy.bmp, a file included with the typical Visual Studio .NET installation. This is done by calling the static FromFile method of the Image class:
img = Image.FromFile( @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"Graphicsitmapsassortedhappy.bmp");
img = Image.FromFile( _ "C:\Program Files\Microsoft Visual Studio .NET 2003\" + _ "Common7\Graphics\bitmaps\assorted\happy.bmp")
The Button control was previously declared a member variable. Here it is instantiated, and its Parent property is set to the form:
btn = new Button( ); btn.Parent = this;
btn = new Button( ) btn.Parent = me
The Text property of the button is set to a string representation of the current value of the button's FlatStyle property.
btn.Text = btn.FlatStyle.ToString( );
Since the FlatStyle property for this button has not yet been set, it will default to a value of Standard.
The Location of the button is set so the upper-left corner of the control will be 10 pixels in from the left edge of the form's client area and 10 pixels down from the top of the form's client area.
The background color of the button is set to LightGreen, which will not be obvious in this book, but will be visible on the screen.
The most commonly used event raised by the Button control is the Click event, although it supports all events common to the Control class other than DoubleClick. The Click event handler method is added to the event delegate with the appropriate following line of code:
btn.Click += new System.EventHandler(btn_Click);
AddHandler btn.Click, AddressOf btn_Click
In either case, the name of the Click event handler method is btn_Click. It will be analyzed shortly.
|
The next three statements sets the Image property to the Image object previously instantiated with the happy face. The image is aligned to the middle of the right edge of the button, and the text is aligned to the middle of the button's left edge.
btn.Image = img; btn.ImageAlign = ContentAlignment.MiddleRight; btn.TextAlign = ContentAlignment.MiddleLeft;
Normally the button would be sized in the constructor. As you will soon see, though, you should also resize the button in its Click event handler. Therefore it makes sense to factor out the common code to a ButtonSize helper method, and call that method from both the constructor and the Click event handler.
The ButtonSize method takes an object of type Button as its argument. The passed-in Button object is the button that will be resized. The method is reproduced here:
private void ButtonSize(Button btn) { int xSize = ((int)(Font.Height * .75) * btn.Text.Length) + (img.Width * 2); int ySize = img.Height * 2; btn.Size = new Size(xSize, ySize); }
private sub ButtonSize(btn as Button) dim xSize as integer = (CType(Font.Height * .75, integer) * _ btn.Text.Length) + (img.Width * 2) dim ySize as integer = img.Height * 2 btn.Size = new Size(xSize, ySize) end sub
The first statement in the method calculates the width of the button, based on multiplying the height of the form's current Font by a factor of 0.75 and adding twice the width of the Image object displayed on the button. The height of the button is calculated as twice the height of the Image object. (All of these factors were determined through trial and error to yield a desirable size.) Once the height and width of the button is calculated, the Size property of btn is set by declaring a new Size structure.
|
The Click event handler accomplishes its task of cycling through the values of the FlatStyle enumeration by indexing into an array containing those enumeration values. It is accomplished with the following two statements:
FlatStyle theEnum = new FlatStyle( ); theStyles = (FlatStyle[ ])Enum.GetValues(theEnum.GetType( ));
dim theEnum as new FlatStyle( ) theStyles = CType([Enum].GetValues(theEnum.GetType( )), FlatStyle( ))
You have seen similar statements in other examples in this book, wherever it is necessary to iterate or otherwise treat an enumeration as an indexed collection.
Once the array is in hand, the btn_Click event handler method can use the integer counter i to index into the array, setting the style of the button to the desired value of FlatStyle. The event handler method is reproduced here:
private void btn_Click(object sender, EventArgs e) { Button btn = (Button)sender; btn.FlatStyle = theStyles[i]; btn.Text = btn.FlatStyle.ToString( ); ButtonSize(btn); if (i < theStyles.Length - 1) i++; else i = 0; }
private sub btn_Click(ByVal sender as object, _ ByVal e as EventArgs) Dim btn as Button = CType(sender, Button) btn.FlatStyle = theStyles(i) btn.Text = btn.FlatStyle.ToString( ) ButtonSize(btn) if (i < theStyles.Length - 1) then i = i + 1 else i = 0 end if end sub
The first statement in the method casts the object that raised the event as a Button and assigns it to a variable, btn, of type Button. Although a button raised the event that invoked the method, the compiler has no way of knowing this, so the explicit cast is necessary. The variable btn used in this method is not the same btn used in the constructor. Although they look the same, they are scoped independently. You could have called the Button variable in this method fred, and the method would still work exactly the same, although it would have been confusing.
The next two statements in the method assign the indexed value of the array of FlatStyles to the FlatStyle property of the button and assign the string representation of the FlatStyle property to the button's Text property. Then the ButtonSize method is called, as it was in the constructor, to resize the button.
Finally, the method checks the value of the counter and either increments it or resets it to zero.
When the FlatStyle is set to System, the Image and BackColor properties are ignored and the operating system controls the appearance of the button.
11.2.1 Button
The previous section covered features common to all the classes derived from the ButtonBase class, using a Button object as the test case. In this section, each of the derived classes, including Button, will be covered in detail.
11.2.1.1 DialogResult property
The Button control has one property, DialogResult, that is not derived from either ButtonBase or Control and not shared with CheckBox or RadioButton. The DialogResult property, which was covered in Chapter 6, is used with buttons on dialog boxes, such as a modal child form. The DialogResult property of a button returns a specific value from a dialog box. If a button on a dialog box has its DialogResult property set, the dialog box will be closed when the button is clicked. The value of the button's DialogResult property will be returned to the parent form as the dialog box's DialogResult property. This is all done without the programmer having to hook up events or write extra code.
The valid values of the DialogResult property, for either the button or the dialog box, must be members of the DialogResult enumeration, listed in Table 11-11. The default value is DialogResult.None.
Value |
Return value |
---|---|
Abort |
Abort. |
Cancel |
Cancel. |
Ignore |
Ignore. |
No |
No. |
None |
Returns nothing. Modal dialog is not terminated. |
OK |
OK. |
Retry |
Retry. |
Yes |
Yes. |
Refer to Chapter 6 for examples showing the usage of the DialogResult property.
11.2.1.2 PerformClick method
The PerformClick method generates a click event for the button. This method, which is not derived from Control but is shared with the RadioButton class, can be useful when you want the functionality contained in a button that will be invoked, without requiring the user to actually click the button.
Consider a form with two buttons, btn1 and btn2. Each has a Click event handler, shown here (the VB.NET versions are nearly identical):
private void btn1_Click(object sender, EventArgs e) { MessageBox.Show("Button1 clicked."); btn2.PerformClick( ); } private void btn2_Click(object sender, EventArgs e) { MessageBox.Show("Button2 clicked."); }
If the user clicks on btn2, a message box will display saying Button2 clicked. If the user clicks on btn1, the user will see two message boxes: Button1 clicked and Button2 clicked.
11.2.1.3 Access keys
In addition to clicking with the mouse, a button can be clicked by an access key. An access key is specified by inserting an ampersand in front of the desired character in the button's Text property. That character will be underlined on the button's face. The button can be clicked, even if it does not have focus, by pressing the access key simultaneously with the Alt key.
If the Text property needs to show an ampersand character, escape it by doubling the ampersand.
The following code snippet specifies a button:
btn1 = new Button( ); btn1.Parent = this; btn1.Text = "&Cut && Paste"; btn1.Location = new Point(10,10); btn1.Click += new System.EventHandler(btn1_Click);
This would create a button displaying the legend Cut & Paste. Pressing Alt-C would click the button, regardless of which control on the form had focus.
11.2.1.4 Form Button properties
The Form class provides two properties that allow specified buttons to be clicked using specific keys on the keyboard, even if those buttons do not have focus.
The form AcceptButton property lets you get or set a button that will be clicked if the user presses Enter, even if that button does not have focus. The only time Enter will not click the designated button would be if the currently selected control intercepts the Enter key. For example, a multiline text box will process the Enter key as part of its text, preventing it from triggering AcceptButton.
The form CancelButton property lets you get or set a button that will be clicked if the user presses Escape, even if that button does not have focus.
Consider a form called frmMain with two buttons, btnOK and btnCancel. The following code snippet (in C#) from the constructor of the form sets the AcceptButton and CancelButton properties of the form:
frmMain.AcceptButton = btnOK; frmMain.CancelButton = btnCancel;
If the user presses Enter, btnOK will be clicked, and if she presses Escape, btnCancel will be clicked.
11.2.2 CheckBox
The CheckBox control is derived from the ButtonBase class, as are the Button and RadioButton controls. It is typically used to indicate a Boolean such as Yes/No or True/False.
A CheckBox normally consists of a small square with a text string next to it. Clicking on either the square or the associated text string toggles the state of the control. If the square is checked, it becomes unchecked, and vice versa. If the control has focus, the state can also be toggled by pressing the spacebar.
The normal appearance of the CheckBox can be changed to resemble a button by setting the Appearance property. The button appears depressed when checked and raised when unchecked. Unlike a true button control, it retains the depressed appearance until it is clicked again, which causes it to toggle to the raised position.
The most important property of the Checkbox control is the Checked property. It either sets the state of the control or determines whether the control is checked or unchecked. If the AutoCheck property is true (the default value), then the Checked property will be changed automatically when the CheckBox is clicked. If you set the AutoCheck property to false, then you must explicitly set the Checked property in a Click event handler.
The commonly used properties of the CheckBox control are listed in Table 11-12.
Property |
Value type |
Description |
---|---|---|
Appearance |
Appearance |
Read/write. If the value is Appearance.Normal (the default), then the checkbox will have a normal appearance. If the value is Appearance.Button, the checkbox will look like a button that toggles, (either in an up or down state). |
AutoCheck |
Boolean |
Read/write. Value indicating if the Checked or CheckState properties are automatically changed when the Click event is raised. If true (the default), those properties will be automatically changed when the control is clicked. |
CheckAlign |
ContentAlignment |
Read/write. Controls both the horizontal and vertical alignment of the checkbox in a CheckBox control. Valid values must come from the ContentAlignment enumeration, listed in Table 11-3. The default is ContentAlignment.MiddleLeft. |
Checked |
Boolean |
Read/write. Value indicating the state of the checkbox. If true, the checkbox is checked, otherwise false (the default). |
CheckState |
CheckState |
Read/write. The state of the checkbox. The valid values must be members of the CheckState enumeration, listed in Table 11-13. The default value is CheckState.Unchecked. |
TextAlign |
ContentAlignment |
Read/write. Aligns text displayed next to the checkbox. Values are members of the ContentAlignment enumeration, listed in Table 11-3. The default value is ContentAlignment.MiddleLeft. |
ThreeState |
Boolean |
Read/write. Value indicating if the control can display three states or two. If true, the checkbox can display three states, otherwise false (the default). |
The CheckBox control has a ThreeState property that, if true, allows it to indicate an indeterminate state in addition to the Boolean values. If the ThreeState property of the control is true, then each time it is clicked, its state cycles through the three values of the CheckState enumeration: Checked, Unchecked, and Indeterminate.
A good example of a CheckBox making use of the ThreeState property can be found in the Font dialog box used in Microsoft Word. This dialog box has several checkboxes, one each for effects such as Strikethrough, Superscript, or Subscript. If you highlight a word with no effects applied to and then open the Font dialog box, none of the checkboxes will be checked. Check the Strikethrough checkbox, and then close the dialog box, and the word will have a line through it. Leaving the word highlighted, open the dialog box again and now the Strikethrough checkbox will be checked. This is typical two-state behaviorthe text selection is unambiguously struck through. However, if you highlight the entire line of text with both normal words and struck-through words and then open the Font dialog box, the Strikethrough checkbox will have a checkmark, but the square and the checkmark will be gray. This is the indeterminate state, since the selection has both struck-through and non-struck-through characters.
If the ThreeState property is true, then use the CheckState property rather than the Checked property to get or set the state of the checkbox. The valid values of the CheckState property are members of the CheckState enumeration, listed in Table 11-13.
Value |
Appearance.Normal |
Appearance.Button |
---|---|---|
Checked |
Displays checkmark |
Button looks depressed |
Unchecked |
No checkmark |
Button looks raised |
Indeterminate |
Displays checkmark in shaded checkbox |
Button looks flat |
The CheckBox control has many events, most of which are inherited from the Control class. Table 11-14 lists the most commonly used events. Of those, only the Click event is inherited from Control. If the AutoCheck property is set to its default value of true, then there is rarely any need to handle the Click event.
Property |
Event argument |
Description |
---|---|---|
AppearanceChanged |
EventArgs |
Raised when the Appearance property of the control changes |
CheckedChanged |
EventArgs |
Raised when the Checked property of the control changes |
CheckStateChanged |
EventArgs |
Raised when the CheckState property of the control changes |
Click |
EventArgs |
Raised when the control is clicked. |
The programs listed in Example 11-7 and Example 11-8 demonstrate the basic features of using CheckBox controls. The Appearance, CheckAlign, and TextAlign properties will be demonstrated in Example 11-9 and Example 11-10 in the next section, where RadioButton controls will be used to change these CheckBox properties. In the next example, a checkbox is created for each member of the FontStyle enumeration. Clicking on one or more of the checkboxes applies that font style to the text contained in a label control. The checkboxes are contained in a Panel control to allow easy iteration through the checkboxes in the CheckedChanged event handler. When the examples are compiled and run, the program will look like the screenshot shown in Figure 11-6.
Figure 11-6. CheckBoxes
Example 11-7. CheckBoxes using C# (CheckBoxes.cs)
using System; using System.Drawing; using System.Windows.Forms; namespace ProgrammingWinApps { public class CheckBoxes : Form { Label lbl; Panel pnl; FontStyle[ ] theStyles; public CheckBoxes( ) { Text = "CheckBoxes"; Size = new Size(300,250); lbl = new Label( ); lbl.Parent = this; lbl.Text = "The quick brown fox..."; lbl.Location = new Point(0,0); lbl.AutoSize = true; lbl.BorderStyle = BorderStyle.Fixed3D; int yDelta = lbl.Height + 10; FontStyle theEnum = new FontStyle( ); theStyles = (FontStyle[ ])Enum.GetValues(theEnum.GetType( )); pnl = new Panel( ); pnl.Parent = this; pnl.Location = new Point(0, yDelta ); pnl.Size = new Size(150, (theStyles.Length + 1) * yDelta); pnl.BorderStyle = BorderStyle.FixedSingle; int i = 1; CheckBox cb; foreach (FontStyle style in theStyles) { cb = new CheckBox( ); cb.Parent = pnl; cb.Location = new Point(25, (yDelta * (i - 1)) + 10); cb.Size = new Size(75,20); cb.Text = style.ToString( ); cb.Tag = style; cb.CheckedChanged += new System.EventHandler(cb_CheckedChanged); if (cb.Text = = "Regular") cb.Checked = true; i++; } } static void Main( ) { Application.Run(new CheckBoxes( )); } private void cb_CheckedChanged(object sender, EventArgs e) { FontStyle fs = 0; for (int i = 0; i < pnl.Controls.Count; i++) { CheckBox cb = (CheckBox)pnl.Controls[i]; if (cb.Checked) fs |= (FontStyle)cb.Tag; // The following lines accomplish the same task in a more condensed way. // if (((CheckBox)pnl.Controls[i]).Checked) // fs |= (FontStyle)((CheckBox)pnl.Controls[i]).Tag; } lbl.Font = new Font(lbl.Font, fs); } } }
Example 11-8. CheckBoxes using VB.NET (CheckBoxes.vb)
Option Strict On imports System imports System.Drawing imports System.Windows.Forms namespace ProgrammingWinApps public class CheckBoxes : inherits Form dim lbl as Label dim pnl as Panel dim theStyles as FontStyle( ) public sub New( ) Text = "Button Properties" Size = new Size(300,250) lbl = new Label( ) lbl.Parent = me lbl.Text = "The quick brown fox..." lbl.Location = new Point(0,0) lbl.AutoSize = true lbl.BorderStyle = BorderStyle.Fixed3D dim yDelta as integer = lbl.Height + 10 ' get the FontStyle values into an array dim theEnum as new FontStyle( ) theStyles = CType([Enum].GetValues( _ theEnum.GetType( )), FontStyle( )) pnl = new Panel( ) pnl.Parent = me pnl.Location = new Point(0, yDelta ) pnl.Size = new Size(150, (theStyles.Length + 1) * yDelta) pnl.BorderStyle = BorderStyle.FixedSingle dim i as integer = 1 dim style as FontStyle dim cb as CheckBox for each style in theStyles cb = new CheckBox( ) cb.Parent = pnl cb.Location = new Point(25, (yDelta * (i - 1)) + 10) cb.Size = new Size(75,20) cb.Text = style.ToString( ) cb.Tag = style AddHandler cb.CheckedChanged, AddressOf cb_CheckedChanged if cb.Text = "Regular" then cb.Checked = true end if i = i + 1 next end sub public shared sub Main( ) Application.Run(new CheckBoxes( )) end sub private sub cb_CheckedChanged(ByVal sender as object, _ ByVal e as EventArgs) dim fs as FontStyle = 0 dim i as integer for i = 0 to pnl.Controls.Count - 1 dim cb as CheckBox = CType(pnl.Controls(i), CheckBox) if cb.Checked then fs = fs or CType(cb.Tag, FontStyle) end if ' The following lines accomplish the same task in a more condensed way. ' if (CType(pnl.Controls(i), CheckBox)).Checked then ' fs = fs or CType(CType(pnl.Controls(i), _ ' CheckBox).Tag, FontStyle) ' end if next lbl.Font = new Font(lbl.Font, fs) end sub end class end namespace
Three member variables are declared, so they will be available to all methodstwo controls and an array of FontStyle objects:
Label lbl; Panel pnl; FontStyle[ ] theStyles;
dim lbl as Label dim pnl as Panel dim theStyles as FontStyle( )
In the constructor, the Label control is instantiated and specified with several properties, including a Text property that will be displayed on the form. An integer yDelta is calculated, and will be used soon for positioning other controls.
The next two statements put the contents of the FontStyle enumeration into an array. This is necessary because you cannot iterate through an enumeration directly, and you will need to iterate before creating one checkbox for each member of the enumeration. You have seen similar code in previous examples.
FontStyle theEnum = new FontStyle( ); theStyles = (FontStyle[ ])Enum.GetValues(theEnum.GetType( ));
dim theEnum as new FontStyle( ) theStyles = CType([Enum].GetValues( _ theEnum.GetType( )), FontStyle( ))
This array is then looped through using a foreach loop, creating a CheckBox for each member of the enumeration. The Parent property of each CheckBox is set as the Panel control. This setting facilitates iterating through all the checkbox controls in the event handler, since it cleanly encapsulates the collection of checkbox controls within a container control. (The Panel control will be covered in detail in Chapter 13.)
The Tag property of each checkbox control is set to its associated FontStyle. This allows the Tag property to be used in the event handler to set the FontStyle directly without having to cast a text string to a FontStyle.
An event handler method, cb_CheckedChanged, is added to the delegate for the CheckedChanged event. When the CheckBox is changed, the CheckedChanged event is fired, which is then handled by that method:
cb.CheckedChanged += new System.EventHandler(cb_CheckedChanged); AddHandler cb.CheckedChanged, AddressOf cb_CheckedChanged
Finally, the Text property of the checkbox is tested. If it is Regular, then the Checked property of that checkbox is set to true so the checkbox will appear checked. Regular is the default value at program initialization.
The cb_CheckedChanged event handler is called every time one of the checkboxes has a changed value. It is not necessary for all checkbox controls in a group to use the same event handler, but in this case it serves the goal of the program, which is to apply one or more FontStyles to the Label control's Text property.
FontStyles can be added together bitwise using the logical OR operator. This is done by iterating through each of the checkbox controls, testing to see if the checkbox is checked, and if it is checked, OR'ing its Tag property to the built-up FontStyle variable.
The method starts by declaring, instantiating, and initializing the FontStyle variable to 0:
FontStyle fs = 0;
dim fs as FontStyle = 0
Then the Controls collection of the Panel control is iterated. Within the for loop, each member of the Panel's Controls collection is cast to a CheckBox:
CheckBox cb = (CheckBox)pnl.Controls[i];
dim cb as CheckBox = CType(pnl.Controls(i), CheckBox)
Then the Checked property of that checkbox is tested. If it is checked, the value of its Tag property is OR'ed to the pre-existing FontStyle variable:
if (cb.Checked) fs |= (FontStyle)cb.Tag;
if cb.Checked then fs = fs or CType(cb.Tag, FontStyle) end if
You can condense the casting of the control into the testing for a gain in code density but a loss of readability. The necessary lines are commented out in the example. It would be:
if (((CheckBox)pnl.Controls[i]).Checked) fs |= (FontStyle)((CheckBox)pnl.Controls[i]).Tag;
if (CType(pnl.Controls(i), CheckBox)).Checked then fs = fs or CType(CType(pnl.Controls(i), _ CheckBox).Tag, FontStyle) end if
Finally, the FontStyle is applied to the Label control by creating a new Font object that uses the current Font property as a template.
lbl.Font = new Font(lbl.Font, fs);
11.2.3 RadioButton
The RadioButton control, the last of the ButtonBase derived controls, is similar to the CheckBox. The main difference between the RadioButton and the CheckBox is that RadioButton controls are typically grouped with other RadioButtons and only one of the controls in the group can be checked at one time. In other words, if an unchecked radio button in a group is clicked by the user, the currently checked radio button will become unchecked automatically (assuming that the AutoClick property is set to the default value of true).
RadioButtons are typically grouped by a container control. These controls include Panels and GroupBoxes, both of which are described in Chapter 13. If one or more RadioButtons are on the form but not in a container control, then those "freestanding" radio buttons are grouped together.
The essential difference between a Panel control and a GroupBox control is that a Panel control can have scrollbars but no caption, while a GroupBox control can have a Text property that will appear as a caption, but no scrollbars.
If two or more groups of radio buttons are on a form, they will be totally independent of each other. There can be at most one checked radio button in each group, but changing the Checked state of the radio buttons in one group will have no effect on the other group.
The RadioButton control has the same common properties as the CheckBox control, listed in Table 11-12, with the omission of the ThreeState related properties: CheckState and ThreeState. Likewise, it has the same events as the CheckBox events listed in Table 11-14, with the omission of CheckStateChanged.
The RadioButton control has one method not derived from Control (or some other base class), but shared with the Button control: the PerformClick method. It behaves the same as it does with the Button control, described previously in this chapter.
Example 11-9 and Example 11-10 build on the examples used to demonstrate CheckBoxes and showing how to use radio buttons to change properties of a CheckBox control. The finished program is shown in Figure 11-7.
Figure 11-7. RadioButtons
The example programs add three GroupBox controls, each of which contains a group of RadioButton controls. Clicking on any of the radio buttons changes the appearance of the checkboxes. The Appearance group lets you toggle between Normal and Button values for the Appearance property. The CheckAlign group changes the values of the CheckAlign property of the CheckBox control, and the TextAlign group changes the TextAlign property.
Although this program changes the appearance of the CheckBox control, RadioButton controls behave and appear much like the CheckBox control.
When there are only two choices in a radio button group, you can use a single CheckBox instead. For example, you could replace the Appearance radio buttons with a single CheckBox labeled Button. When checked, it would set the Appearance property to Appearance.Button, and when unchecked it would set the value to Appearance.Normal. The main advantage of using a radio button in this case is that both available options are explicitly displayed to the user.
The changes in Example 11-9 and Example 11-10 from Example 11-7 and Example 11-8 are highlighted. These code listings are long, but there is a fair amount of repetitious code that you can cut and paste with minor changes. If you are developing with Visual Studio .NET, much of this code will be generated for you automatically.
|
Example 11-9. RadioButton controls using C# (RadioButtons.cs)
using System; using System.Drawing; using System.Windows.Forms; namespace ProgrammingWinApps { public class RadioButtons : Form { Label lbl; Panel pnl; int yDelta; RadioButton rdoAppearanceNormal; RadioButton rdoAppearanceButton; RadioButton rdoCheckAlignMiddleLeft; RadioButton rdoCheckAlignMiddleRight; RadioButton rdoTextAlignMiddleLeft; RadioButton rdoTextAlignMiddleRight; RadioButton rdoTextAlignMiddleCenter; FontStyle[ ] theStyles; public RadioButtons( ) { Text = "RadioButtons"; Size = new Size(350,375); lbl = new Label( ); lbl.Parent = this; lbl.Text = "The quick brown fox..."; lbl.Location = new Point(0,0); lbl.AutoSize = true; lbl.BorderStyle = BorderStyle.Fixed3D; yDelta = lbl.Height + 10; // Get the FontStyles into an array FontStyle theEnum = new FontStyle( ); theStyles = (FontStyle[ ])Enum.GetValues(theEnum.GetType( )); pnl = new Panel( ); pnl.Parent = this; pnl.Location = new Point(0, yDelta ); pnl.Size = new Size(150, (theStyles.Length + 1) * yDelta); pnl.BorderStyle = BorderStyle.None; int i = 1; CheckBox cb; foreach (FontStyle style in theStyles) { cb = new CheckBox( ); cb.Parent = pnl; cb.Location = new Point(25, (yDelta * (i - 1)) + 10); cb.Size = new Size(75,20); cb.Text = style.ToString( ); cb.Tag = style; cb.CheckedChanged += new System.EventHandler(cb_CheckedChanged); if (cb.Text = = "Regular") cb.Checked = true; i++; } GroupBox grpAppearance = new GroupBox( ); grpAppearance.Parent = this; grpAppearance.Text = "Appearance"; grpAppearance.Location = new Point(175,yDelta); grpAppearance.Size = new Size(150, yDelta * 3); rdoAppearanceNormal = new RadioButton( ); rdoAppearanceNormal.Parent = grpAppearance; rdoAppearanceNormal.Text = "Normal"; rdoAppearanceNormal.Location = new Point(10, 15); rdoAppearanceNormal.Checked = true; rdoAppearanceNormal.CheckedChanged += new System.EventHandler(rdoAppearance_CheckedChanged); rdoAppearanceButton = new RadioButton( ); rdoAppearanceButton.Parent = grpAppearance; rdoAppearanceButton.Text = "Button"; rdoAppearanceButton.Location = new Point(10, 15 + yDelta); rdoAppearanceButton.CheckedChanged += new System.EventHandler(rdoAppearance_CheckedChanged); GroupBox grpCheckAlign = new GroupBox( ); grpCheckAlign.Parent = this; grpCheckAlign.Text = "CheckAlign"; grpCheckAlign.Location = new Point(175, grpAppearance.Bottom + 25); grpCheckAlign.Size = new Size(150, yDelta * 3); rdoCheckAlignMiddleLeft = new RadioButton( ); rdoCheckAlignMiddleLeft.Parent = grpCheckAlign; rdoCheckAlignMiddleLeft.Text = "Middle Left"; rdoCheckAlignMiddleLeft.Tag = ContentAlignment.MiddleLeft; rdoCheckAlignMiddleLeft.Location = new Point(10, 15); rdoCheckAlignMiddleLeft.Checked = true; rdoCheckAlignMiddleLeft.CheckedChanged += new System.EventHandler(rdoCheckAlign_CheckedChanged); rdoCheckAlignMiddleRight = new RadioButton( ); rdoCheckAlignMiddleRight.Parent = grpCheckAlign; rdoCheckAlignMiddleRight.Text = "Middle Right"; rdoCheckAlignMiddleRight.Tag = ContentAlignment.MiddleRight; rdoCheckAlignMiddleRight.Location = new Point(10, 15 + yDelta); rdoCheckAlignMiddleRight.CheckedChanged += new System.EventHandler(rdoCheckAlign_CheckedChanged); GroupBox grpTextAlign = new GroupBox( ); grpTextAlign.Parent = this; grpTextAlign.Text = "TextAlign"; grpTextAlign.Location = new Point(175, grpCheckAlign.Bottom + 25); grpTextAlign.Size = new Size(150, yDelta * 4); rdoTextAlignMiddleLeft = new RadioButton( ); rdoTextAlignMiddleLeft.Parent = grpTextAlign; rdoTextAlignMiddleLeft.Text = "Middle Left"; rdoTextAlignMiddleLeft.Tag = ContentAlignment.MiddleLeft; rdoTextAlignMiddleLeft.Location = new Point(10, 15); rdoTextAlignMiddleLeft.Checked = true; rdoTextAlignMiddleLeft.CheckedChanged += new System.EventHandler(rdoTextAlign_CheckedChanged); rdoTextAlignMiddleRight = new RadioButton( ); rdoTextAlignMiddleRight.Parent = grpTextAlign; rdoTextAlignMiddleRight.Text = "Middle Right"; rdoTextAlignMiddleRight.Tag = ContentAlignment.MiddleRight; rdoTextAlignMiddleRight.Location = new Point(10, 15 + yDelta); rdoTextAlignMiddleRight.CheckedChanged += new System.EventHandler(rdoTextAlign_CheckedChanged); rdoTextAlignMiddleCenter = new RadioButton( ); rdoTextAlignMiddleCenter.Parent = grpTextAlign; rdoTextAlignMiddleCenter.Text = "Middle Center"; rdoTextAlignMiddleCenter.Tag = ContentAlignment.MiddleCenter; rdoTextAlignMiddleCenter.Location = new Point(10, 15 + (2 * yDelta)); rdoTextAlignMiddleCenter.CheckedChanged += new System.EventHandler(rdoTextAlign_CheckedChanged); } // close for constructor static void Main( ) { Application.Run(new RadioButtons( )); } private void cb_CheckedChanged(object sender, EventArgs e) { FontStyle fs = 0; for (int i = 0; i < pnl.Controls.Count; i++) { CheckBox cb = (CheckBox)pnl.Controls[i]; if (cb.Checked) fs |= (FontStyle)cb.Tag; } lbl.Font = new Font(lbl.Font, fs); } private void rdoAppearance_CheckedChanged(object sender, EventArgs e) { if (rdoAppearanceNormal.Checked) { for (int i = 0; i < pnl.Controls.Count; i++) { CheckBox cb = (CheckBox)pnl.Controls[i]; cb.Appearance = Appearance.Normal; } } else { for (int i = 0; i < pnl.Controls.Count; i++) { CheckBox cb = (CheckBox)pnl.Controls[i]; cb.Appearance = Appearance.Button; } } } private void rdoCheckAlign_CheckedChanged(object sender, EventArgs e) { RadioButton rdo = (RadioButton)sender; for (int i = 0; i < pnl.Controls.Count; i++) { CheckBox cb = (CheckBox)pnl.Controls[i]; cb.CheckAlign = (ContentAlignment)rdo.Tag; } } private void rdoTextAlign_CheckedChanged(object sender, EventArgs e) { RadioButton rdo = (RadioButton)sender; for (int i = 0; i < pnl.Controls.Count; i++) { CheckBox cb = (CheckBox)pnl.Controls[i]; switch ((int)rdo.Tag) { case (int)ContentAlignment.MiddleLeft : cb.TextAlign = ContentAlignment.MiddleLeft; break; case (int)ContentAlignment.MiddleRight : cb.TextAlign = ContentAlignment.MiddleRight; break; case (int)ContentAlignment.MiddleCenter : cb.TextAlign = ContentAlignment.MiddleCenter; break; } } } // close for rdoTextAlign_CheckedChanged } // close for form class } // close form namespace
Example 11-10. RadioButton controls using VB.NET (RadioButtons.vb)
Option Strict On imports System imports System.Drawing imports System.Windows.Forms namespace ProgrammingWinApps public class RadioButtons : inherits Form dim lbl as Label dim pnl as Panel dim yDelta as integer dim rdoAppearanceNormal as RadioButton dim rdoAppearanceButton as RadioButton dim rdoCheckAlignMiddleLeft as RadioButton dim rdoCheckAlignMiddleRight as RadioButton dim rdoTextAlignMiddleLeft as RadioButton dim rdoTextAlignMiddleRight as RadioButton dim rdoTextAlignMiddleCenter as RadioButton dim theStyles as FontStyle( ) public sub New( ) Text = "RadioButtons" Size = new Size(350,375) lbl = new Label( ) lbl.Parent = me lbl.Text = "The quick brown fox..." lbl.Location = new Point(0,0) lbl.AutoSize = true lbl.BorderStyle = BorderStyle.Fixed3D yDelta = lbl.Height + 10 ' get the FontStyle values into an array dim theEnum as new FontStyle( ) theStyles = CType([Enum].GetValues( _ theEnum.GetType( )), FontStyle( )) pnl = new Panel( ) pnl.Parent = me pnl.Location = new Point(0, yDelta ) pnl.Size = new Size(150, (theStyles.Length + 1) * yDelta) pnl.BorderStyle = BorderStyle.None dim i as integer = 1 dim style as FontStyle dim cb as CheckBox for each style in theStyles cb = new CheckBox( ) cb.Parent = pnl cb.Location = new Point(25, (yDelta * (i - 1)) + 10) cb.Size = new Size(75,20) cb.Text = style.ToString( ) cb.Tag = style AddHandler cb.CheckedChanged, AddressOf cb_CheckedChanged if cb.Text = "Regular" then cb.Checked = true end if i = i + 1 next dim grpAppearance as GroupBox = new GroupBox( ) grpAppearance.Parent = me grpAppearance.Text = "Appearance" grpAppearance.Location = new Point(175,yDelta) grpAppearance.Size = new Size(150, yDelta * 3) rdoAppearanceNormal = new RadioButton( ) rdoAppearanceNormal.Parent = grpAppearance rdoAppearanceNormal.Text = "Normal" rdoAppearanceNormal.Location = new Point(10, 15) rdoAppearanceNormal.Checked = true AddHandler rdoAppearanceNormal.CheckedChanged, _ AddressOf rdoAppearance_CheckedChanged rdoAppearanceButton = new RadioButton( ) rdoAppearanceButton.Parent = grpAppearance rdoAppearanceButton.Text = "Button" rdoAppearanceButton.Location = new Point(10, 15 + yDelta) AddHandler rdoAppearanceButton.CheckedChanged, _ AddressOf rdoAppearance_CheckedChanged dim grpCheckAlign as GroupBox = new GroupBox( ) grpCheckAlign.Parent = me grpCheckAlign.Text = "CheckAlign" grpCheckAlign.Location = new Point(175, _ grpAppearance.Bottom + 25) grpCheckAlign.Size = new Size(150, yDelta * 3) rdoCheckAlignMiddleLeft = new RadioButton( ) rdoCheckAlignMiddleLeft.Parent = grpCheckAlign rdoCheckAlignMiddleLeft.Text = "Middle Left" rdoCheckAlignMiddleLeft.Tag = ContentAlignment.MiddleLeft rdoCheckAlignMiddleLeft.Location = new Point(10, 15) rdoCheckAlignMiddleLeft.Checked = true AddHandler rdoCheckAlignMiddleLeft.CheckedChanged, _ AddressOf rdoCheckAlign_CheckedChanged rdoCheckAlignMiddleRight = new RadioButton( ) rdoCheckAlignMiddleRight.Parent = grpCheckAlign rdoCheckAlignMiddleRight.Text = "Middle Right" rdoCheckAlignMiddleRight.Tag = ContentAlignment.MiddleRight rdoCheckAlignMiddleRight.Location = new Point(10, 15 + yDelta) AddHandler rdoCheckAlignMiddleRight.CheckedChanged, _ AddressOf rdoCheckAlign_CheckedChanged dim grpTextAlign as GroupBox = new GroupBox( ) grpTextAlign.Parent = me grpTextAlign.Text = "TextAlign" grpTextAlign.Location = new Point(175, grpCheckAlign.Bottom + 25) grpTextAlign.Size = new Size(150, yDelta * 4) rdoTextAlignMiddleLeft = new RadioButton( ) rdoTextAlignMiddleLeft.Parent = grpTextAlign rdoTextAlignMiddleLeft.Text = "Middle Left" rdoTextAlignMiddleLeft.Tag = ContentAlignment.MiddleLeft rdoTextAlignMiddleLeft.Location = new Point(10, 15) rdoTextAlignMiddleLeft.Checked = true AddHandler rdoTextAlignMiddleLeft.CheckedChanged, _ AddressOf rdoTextAlign_CheckedChanged rdoTextAlignMiddleRight = new RadioButton( ) rdoTextAlignMiddleRight.Parent = grpTextAlign rdoTextAlignMiddleRight.Text = "Middle Right" rdoTextAlignMiddleRight.Tag = ContentAlignment.MiddleRight rdoTextAlignMiddleRight.Location = new Point(10, 15 + yDelta) AddHandler rdoTextAlignMiddleRight.CheckedChanged, _ AddressOf rdoTextAlign_CheckedChanged rdoTextAlignMiddleCenter = new RadioButton( ) rdoTextAlignMiddleCenter.Parent = grpTextAlign rdoTextAlignMiddleCenter.Text = "Middle Center" rdoTextAlignMiddleCenter.Tag = ContentAlignment.MiddleCenter rdoTextAlignMiddleCenter.Location = new Point(10, _ 15 + (2 * yDelta)) AddHandler rdoTextAlignMiddleCenter.CheckedChanged, _ AddressOf rdoTextAlign_CheckedChanged end sub ' close for constructor public shared sub Main( ) Application.Run(new RadioButtons( )) end sub private sub cb_CheckedChanged(ByVal sender as object, _ ByVal e as EventArgs) dim fs as FontStyle = 0 dim i as integer for i = 0 to pnl.Controls.Count - 1 dim cb as CheckBox = CType(pnl.Controls(i), CheckBox) if cb.Checked then fs = fs or CType(cb.Tag, FontStyle) end if next lbl.Font = new Font(lbl.Font, fs) end sub private sub rdoAppearance_CheckedChanged(ByVal sender as object, _ ByVal e as EventArgs) dim i as integer if rdoAppearanceNormal.Checked then for i = 0 to pnl.Controls.Count - 1 dim cb as CheckBox = CType(pnl.Controls(i), CheckBox) cb.Appearance = Appearance.Normal next else for i = 0 to pnl.Controls.Count - 1 dim cb as CheckBox = CType(pnl.Controls(i), CheckBox) cb.Appearance = Appearance.Button next end if end sub private sub rdoCheckAlign_CheckedChanged(ByVal sender as object, _ ByVal e as EventArgs) dim rdo as RadioButton = CType(sender, RadioButton) dim i as integer for i = 0 to pnl.Controls.Count - 1 dim cb as CheckBox = CType(pnl.Controls(i), CheckBox) cb.CheckAlign = CType(rdo.Tag, ContentAlignment) next end sub private sub rdoTextAlign_CheckedChanged(ByVal sender as object, _ ByVal e as EventArgs) dim rdo as RadioButton = CType(sender, RadioButton) dim i as integer for i = 0 to pnl.Controls.Count - 1 dim cb as CheckBox = CType(pnl.Controls(i), CheckBox) select case rdo.Tag case ContentAlignment.MiddleLeft cb.TextAlign = ContentAlignment.MiddleLeft case ContentAlignment.MiddleRight cb.TextAlign = ContentAlignment.MiddleRight case ContentAlignment.MiddleCenter cb.TextAlign = ContentAlignment.MiddleCenter end select next end sub end class end namespace
The first significant difference in these examples is that the yDelta variable is now a member of the class (and thus available to all the methods of the class).
There are a number of relatively minor changes in the constructor. The Text property and Size of the form has been modified and the instantiation of yDelta now reflects the fact that it is a member variable rather than a local variable. The BorderStyle of the Panel control has been set to BorderStyle.None so that it will not be visible on the form, but will still function as a container of the CheckBoxes.
Now comes the meat of the differences in the constructor. There are three new GroupBox controls, each containing several RadioButton controls. Each set of radio buttons within a group box are mutually exclusive: only one radio button in each group can be checked at the same time.
Within each group, the radio button corresponding to the default value has its Checked property set to true so that the form will initialize with the correctly checked radio button. yDelta is used for calculating the Location and Size properties of many of the controls. The Location property of the second and third group boxes are calculated based on the Bottom property of the previous group box. Here are the lines of code that set the Location property for each group box (the same in both languages except for the trailing semicolon):
grpAppearance.Location = new Point(175,yDelta); grpCheckAlign.Location = new Point(175, grpAppearance.Bottom + 25); grpTextAlign.Location = new Point(175, grpCheckAlign.Bottom + 25);
An event handler is added to the CheckedChanged delegate for each RadioButton control, as was done in the CheckBox control. One event handler is used for all the radio buttons in each group box, so there are a total of three event handler methods, one each for grpAppearance, grpCheckAlign, and grpTextAlign.
Each of the three event handlers uses a different technique for implementing the event handler, although in practice you will probably find the middle method, rdoCheckAlign_CheckedChanged, the most efficient.
The first event handler, rdoAppearance_CheckedChanged, capitalizes on the fact that there are only two radio buttons in the group, and therefore only two possible outcomes. This lends itself to a simple if-else (if...then...else) construct. Within each statement block, the Controls collection of the Panel control is iterated to apply the appropriate value of the Appearance property to the checkboxes.
The second event handler, rdoCheckAlign_CheckedChanged, uses the fact that the Tag property of the radio buttons is set to the appropriate value of the ContentAlignment enumeration. The object raising the CheckedChanged event (i.e., the radio button that has been changed) is cast to a variable of type RadioButton, and then in the iteration of the CheckBox controls, that Tag value is cast back to the ContentAlignment type and assigned to the CheckAlign property of each CheckBox control.
The third event handler, rdoTextAlign_CheckedChanged, uses a switch statement (select case in VB.NET) to apply the correct value of the ContentAlignment enumeration to the checkboxes. There is a slight difference here between the C# and VB.NET versions of the code. Since the switch construct in C# requires either an integer or string expression to switch on, it is not possible to switch directly on the Tag value, which is of type ContentAlignment, as is done in the VB.NET code. Instead the C# version must cast the ContentAlignment enumeration to its equivalent integer value and switch on that.