Okay, now that you have covered the basics of a form and how to handle events from a form, you'll go ahead and make the form do something constructive. To do this, you need to add what the .NET Framework class library calls controls.
Controls provide you the ability to build an interface by breaking it down into smaller components. Each control provides a specific type of input and/or output functionality to your Win Form. For example, there are controls to place a label on the screen, display and input text data, select a data item from a list, and display and (if you want) update a tree of data. There is even a control to display a calendar.
All controls inherit from the Component and Control classes, with each class providing a number of standard methods and properties. Each control will have a few methods and properties of its own that make it unique. Also, all controls have events, for which you can create handlers. You can find all controls provided by the .NET Framework class library within the System::Windows::Forms namespace.
You can add controls to a Win Form in one of two ways, just like almost any other process when it comes to Win Forms. You can use Visual Studio .NET GUI tool to drop and drag the controls to the Win Form, or you can code the controls by hand using Visual Studio .NET's IDE editor.
Let's look at how to drag and drop controls onto a Win Form, as this is essentially what you're going to mimic when you code by hand. The steps are as follows:
Resize the form to the size you want by dragging the borders of the form in the design window. Make it a little bigger than you think you'll need. Don't worry—you can change the size later to enclose the controls better. I've learned from past experience that having the extra real estate makes things easier when designing.
Bring the cursor over the Toolbox tab (if you don't have it tacked open). This will expand the Toolbox.
Click, hold, and then drag the control you want from the Toolbox to the form. (If you don't have the Toolbox tacked open, you may need to drag the control to an open location on the form and release it there. This will cause the Toolbox to close so that you can click again and drag the control to the desired location on the form.)
Alter the properties of the controls as you wish by changing them within the Properties view. I recommend changing the Name property at a minimum, but there is nothing stopping you from using the default generated name for the control.
Add event handlers as desired. You might consider holding off on this step until you have the entire Win Form laid out.
Repeat steps 1 through 5 for all other required controls.
What these steps do behind the scenes is add a definition of the control to the class and then create an instance of it. Each property that is changed adds a line of code that updates one of the control's properties. Each event handler added adds a delegation statement and then creates an event handler.
As a developer, you can rely solely on the drag-and-drop functionality of Visual Studio .NET or you can do as I do and use the tool to build the basic design but then fine-tune it within the code itself. You could also be a glutton for punishment and do it all by hand. But why bother? The tool is there, so why not use it?
Okay, now that you know how to add a control to the Win Form, you'll take a look at an assortment of the more common controls provided by the .NET Framework class library, starting with one of the easiest: Label.
The name of this control is a little misleading. It gives you the impression that it is just good for displaying static text in the form. Nothing could be further from the truth. The Label control is also great for displaying dynamic text to the form. Heck, the Label control can even trigger an event when clicked.
In general, though, you'll normally use a Label control to statically label something else. The usual process of creating a label is simply to create the Label control and then set its properties so that the Label control looks the way you want it to. Here are some of the more common properties used by the Label control:
BackColor is a System::Drawing::Color that represents the background color of the label and defaults to the DefaultBackColor property.
Font is a System::Drawing::Font that represents the font used by the label and defaults to the DefaultFont property.
ForeColor is a System::Drawing::Color that represents the foreground color (or the actual color of the text) of the label and defaults to the DefaultForeColor property.
Image is a System::Drawing::Image that represents the image displayed within the label. The default is null, which signifies that no image is to be displayed.
ImageAlign is a ContentAlignment enum that represents the alignment of the image within the label. I like to visualize the different alignments by picturing a tic-tac-toe game in my head, with each box a possible alignment. The default alignment is the center box of the tic-tac-toe game or ContentAlignment::MiddleCenter.
Text is a String containing the actual text to be displayed.
TextAlign is a ContentAlignment enum that represents the alignment of the image within the label. The default is based on the culture of the computer. Because my computer has a culture of en-us, the default alignment is the top-left corner or ContentAlignment::TopLeft.
UseMnemonic is a Boolean that represents if the ampersand (&) character should be interpreted as an access-key prefix character. The default is true.
Now that you have seen the more common properties, for grins and giggles you'll implement a Label control using some of its less common properties (see Listing 9-5).
Listing 9-5: The MightyLabel, an Implementation of the Uncommon Properties
namespace MightyLabel { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { Boolean labelSwitch; public: Form1(void) { labelSwitch = true; InitializeComponent(); } protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::Label * MightyLabel; private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->MightyLabel = new System::Windows::Forms::Label(); this->SuspendLayout(); // // MightyLabel // this->MightyLabel->BorderStyle = System::Windows::Forms::BorderStyle::FixedSingle; this->MightyLabel->Cursor = System::Windows::Forms::Cursors::Hand; this->MightyLabel->Location = System::Drawing::Point(60, 90); this->MightyLabel->Name = S"MightyLabel"; this->MightyLabel->Size = System::Drawing::Size(180, 40); this->MightyLabel->TabIndex = 0; this->MightyLabel->Text = S"This is the mighty label! It will change when you click it"; this->MightyLabel->TextAlign = System::Drawing::ContentAlignment::MiddleCenter; this->MightyLabel->Click += new System::EventHandler(this, MightyLabel_Click); // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(300, 300); this->Controls->Add(this->MightyLabel); this->Name = S"Form1"; this->Text = S"The Mighty Label"; this->ResumeLayout(false); } private: System::Void MightyLabel_Click(System::Object * sender, System::EventArgs * e) { if (labelSwitch) MightyLabel->Text = S"Ouchie!!! That hurt."; else MightyLabel->Text = S"Ooo!!! That tickled."; labelSwitch = !labelSwitch; } }; }
As you can see, dragging and dropping can save you a lot of time when you're designing a form, even in such a simple case. But even this simple program shows that a programmer is still needed. A designer can drag and drop the label to where it's needed, and he or she can even change the control's properties, but a programmer is still needed to give the controls life or, in other words, to handle events.
Notice that a Form class is like any other Managed C++ class in that you can add your own member variables, methods, and properties. In this example, I added a Boolean member variable called labelSwitch to hold the current state of the label. I initialize it in the constructor just like I would in any other class and then use it within the Click event handler. Basically, as long as you don't code within the areas that the generated code says not to, you're safe to use the Form class as you see fit.
Figure 9-7 shows what MightyLabel.exe looks like when you execute it. Be sure to click the label a couple of times.
Figure 9-7: The MightyLabel example
Buttons are one of the most commonly used controls for getting user input found in any Win Forms application, basically because the average user finds buttons easy to use and understand. And yet they are quite versatile for the software developer.
The .NET Framework class library provides three different types of buttons: Button, CheckBox, and RadioButton. All three inherit from the abstract ButtonBase class, which provides common functionality across all three. Here are some of the common properties provided by ButtonBase:
FlatStyle is a FlatStyle enum that represents the appearance of the button. The default is FlatStyle::Standard, but other options are Flat and Popup.
Image is a System::Drawing:: Image that represents the image displayed on the button. The default is null, meaning no image is to be displayed.
IsDefault is a protected Boolean that specifies if the button is the default for the form. In other words, it indicates if the button's Click event gets triggered when the Enter key is pressed. The default is false.
Text is a String that represents the text that will be displayed on the button.
Remember, you also get all the properties of Control and Component. Thus, you have a plethora of properties and methods to work with.
The Button control does not give much functionality beyond what is defined by abstract ButtonBase class. You might think of the Button control as the lowest level implementation of the abstract base class.
Most people think of Button as a static control that you place on the Win Form at design time. As the following example in Listing 9-6 points out (over and over again), this is not the case. Yes, you can statically place a Button control, but you can also dynamically place it on the Win Form.
Listing 9-6: The Code for "Way Too Many Buttons!"
namespace TooManyButtons { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) //... protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::Button * TooMany; System::ComponentModel::Container * components; void InitializeComponent(void) { this->TooMany = new System::Windows::Forms::Button(); this->SuspendLayout(); // // TooMany // this->TooMany->Location = System::Drawing::Point(24, 16); this->TooMany->Name = S"TooMany"; this->TooMany->TabIndex = 0; this->TooMany->Text = S"Click Me!"; this->TooMany->Size = System::Drawing::Size(72, 24); this->TooMany->Click += new System::EventHandler(this, TooMany_Click); // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(292, 270); this->Controls->Add(this->TooMany); this->Name = S"Form1"; this->Text = S"Too Many Buttons"; this->ResumeLayout(false); } private: System::Void TooMany_Click(System::Object * sender, System::EventArgs * e) { // Grab the location of the button that was clicked Point p = dynamic_cast<Button*>(sender)->Location; // Create a dynamic button Button *Many = new Button(); Many->Location = Drawing::Point(p.X + 36, p.Y + 26); Many->Size = Drawing::Size(72, 24); Many->Text = S"Click Me!"; Many->Click += new System::EventHandler(this, TooMany_Click); // Add dynamic button to Form Controls->Add(Many); } }; }
There really isn't much difference between adding a Label control and a Button statically, as you can see in the InitializeComponent() method. The fun code in Listing 9-6 is in the TooMany_Click() event handler method. The first thing this method does is grab the location of the button that was clicked and place it into a Point struct so that you can manipulate it. You'll examine System::Drawing::Point in Chapter 10. You could have grabbed the whole button but you only need its location. Next, you build a button. There's nothing tricky here, except the button is declared within the event handler. Those of you from a traditional C++ background are probably jumping up and down screaming "Memory leak!" Sorry to disappoint you, but this is Managed C++ and the memory will be collected when it's no longer referenced, so this code is perfectly legal. And finally, the last step in placing the button dynamically on the Win Form is adding it.
Figure 9-8 shows what TooManyButtons.exe looks like when you execute it. Be sure to click a few of the newly created buttons.
Figure 9-8: Way too many buttons
The CheckBox control is also an extension of the ButtonBase class. It's similar to a normal Button control in many ways. The two major differences are that it looks different on the Win Form and that it retains its check state when clicked. Well, the first difference isn't always true—there's a property to make a CheckBox look like a Button.
The CheckBox control, if configured to do so, can have three states: checked, unchecked, and indeterminate. I'm sure you understand checked and unchecked states, but what is this indeterminate state? Visually, in this state, the check boxes are shaded. Most likely you saw this type of check box when you installed Visual Studio .NET on your machine. Remember when you set which parts to install and some of the check marks were gray? When you selected the gray box, you found that some of the subparts were not checked. Basically, the indeterminate state of the parent was because not all child boxes were checked.
In addition to supporting the properties provided by ButtonBase, the CheckBox control also supports some properties unique to itself:
Appearance is an Appearance enum that specifies whether the check box looks like a button or a standard check box. The default, Appearance::Normal, is a standard check box.
CheckAlign is a ContentAlignment enum that represents the alignment of the check box within the CheckBox control. The default alignment is centered and to the left: ContentAlignment::MiddleLeft.
Checked is a Boolean that represents whether the check box is checked or not. This property returns true if the check box is in an indeterminate state as well. The default is false.
CheckState is a CheckState enum that represents the current state of the check box, either Checked, Unchecked, or Indeterminate. The default is CheckState::Unchecked.
ThreeState is a Boolean that specifies if the check box can have an indeterminate state. The default is false.
In following example (see Listing 9-7) you'll have a little fun with the CheckBox control, in particular the Visibility property. Enter the following code and have some fun.
Listing 9-7: The Code for "You Can't Check Me!"
namespace CheckMe { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) //... protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::CheckBox * TopCheck; private: System::Windows::Forms::CheckBox * checkBox1; private: System::Windows::Forms::CheckBox * checkBox2; private: System::Windows::Forms::CheckBox * BottomCheck; private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->TopCheck = new System::Windows::Forms::CheckBox(); this->checkBox1 = new System::Windows::Forms::CheckBox(); this->checkBox2 = new System::Windows::Forms::CheckBox(); this->BottomCheck = new System::Windows::Forms::CheckBox(); this->SuspendLayout(); // // TopCheck // this->TopCheck->Location = System::Drawing::Point(50, 50); this->TopCheck->Name = S"TopCheck"; this->TopCheck->Size = System::Drawing::Size(160, 25); this->TopCheck->TabIndex = 2; this->TopCheck->TabStop = false; this->TopCheck->Text = S"You Can\'t Check Me!"; this->TopCheck->Enter += new System::EventHandler(this, TopCheck_Entered); this->TopCheck->MouseEnter += new System::EventHandler(this, TopCheck_Entered); // // checkBox1 // this->checkBox1->Checked = true; this->checkBox1->CheckState = System::Windows::Forms::CheckState::Indeterminate; this->checkBox1->Location = System::Drawing::Point(50, 100); this->checkBox1->Name = S"checkBox1"; this->checkBox1->Size = System::Drawing::Size(160, 25); this->checkBox1->TabIndex = 0; this->checkBox1->Text = S"Check Me! Check Me!"; this->checkBox1->ThreeState = true; // // checkBox2 // this->checkBox2->Location = System::Drawing::Point(50, 150); this->checkBox2->Name = S"checkBox2"; this->checkBox2->Size = System::Drawing::Size(160, 25); this->checkBox2->TabIndex = 1; this->checkBox2->Text = S"Don\'t Forget ME!"; // // BottomCheck // this->BottomCheck->Enabled = false; this->BottomCheck->Location = System::Drawing::Point(50, 200); this->BottomCheck->Name = S"BottomCheck"; this->BottomCheck->Size = System::Drawing::Size(160, 25); this->BottomCheck->TabIndex = 0; this->BottomCheck->TabStop = false; this->BottomCheck->Text = S"You Can\'t Check Me!"; this->BottomCheck->Visible = false; this->BottomCheck->Enter += new System::EventHandler(this, BottomCheck_Entered); this->BottomCheck->MouseEnter += new System::EventHandler(this, BottomCheck_Entered); // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(300, 300); this->Controls->Add(this->BottomCheck); this->Controls->Add(this->checkBox2); this->Controls->Add(this->checkBox1); this->Controls->Add(this->TopCheck); this->Name = S"Form1"; this->Text = S"Can\'t Check Me"; this->ResumeLayout(false); } private: System::Void TopCheck_Entered(System::Object * sender, System::EventArgs * e) { // Hide Top checkbox and display bottom TopCheck->Enabled = false; TopCheck->Visible = false; BottomCheck->Enabled = true; BottomCheck->Visible = true; } private: System::Void BottomCheck_Entered(System::Object * sender, System::EventArgs * e) { // Hide Bottom checkbox and display top BottomCheck->Enabled = false; BottomCheck->Visible = false; TopCheck->Enabled = true; TopCheck->Visible = true; } }; }
You may have noticed that I threw in the indeterminate state in the first/second/first...(whichever) check box, just so you can see what it looks like.
An important thing to take from this example is that it shows you can delegate the same event handler to more than one event. To do this in the Visual Studio .NET Properties view requires that you use the drop-down list to select the event handler that you want to redelegate.
The example also shows how to enable/disable and show/hide both in the Properties view and at runtime.
Figure 9-9 shows what CheckMe.exe looks like when you execute it. Who says programmers don't have a sense of humor!
Figure 9-9: You can't check me!
From a coding perspective, there isn't much to say about the RadioButton control other than you code it in exactly the same way you code a CheckBox control. The only differences between the RadioButton and CheckBox controls are that with the RadioButton you lose the CheckState property and its associated CheckStateChanged event.
The RadioButton control works a little differently than the CheckBox control. Only one RadioButton can be checked at a time within a given container, which at this point is the Win Form. (You will see that you can have multiple containers placed on a Win Form later in this chapter in the section "The GroupBox Control.") If you have ever played with a car radio, you should understand exactly how a RadioButton works.
Listing 9-8 shows a neat little trick that the C# GUI design tool can't do—it shows how to create an array of radio buttons. Having unique names for what amounts to a single entity with multiple values seems a little silly in most cases, and at worst the code goes on forever. I think developing a set of radio buttons, as shown in Listing 9-8, makes good sense.
Listing 9-8: The Code for an Array of Radio Buttons
namespace ArrayOfRadios { using namespace System; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { RadioButton *radios[]; Label *label; public: Form1(void) { String *rbText[] = {S"Can", S"You", S"Click", S"More", S"Than", S"One"}; radios = new RadioButton*[6]; label = new Label(); for (Int32 i = 0; i < radios->Count; i++) { Int32 j = 50*i; radios[i] = new RadioButton(); radios[i]->BackColor = Color::FromArgb(255, j+5, j+5, j+5); radios[i]->ForeColor = Color::FromArgb(255, 250-j, 250-j, 250-j); radios[i]->Location = Drawing::Point(90, 5+(40*i)); radios[i]->TabIndex = i; radios[i]->TabStop = true; radios[i]->Text = rbText[i]; radios[i]->CheckedChanged += new EventHandler(this,radioCheckedChanged); } Controls->AddRange(radios); label->Location = Drawing::Point(90, 5+(40*radios->Count)); Controls->Add(label); Text = S"An Array Of Radios"; } private: void radioCheckedChanged(Object *sender, EventArgs *e) { RadioButton *rb = dynamic_cast<RadioButton*>(sender); if (rb->Checked == true) label->Text = rb->Text; } }; }
The code in Listing 9-8 is pretty straightforward. (This example doesn't include the design tool-specific code as it was written by hand.) First, you create an array of RadioButton controls and then you populate the array. I also threw in a Label control to show how to extract the currently checked RadioButton control.
You should notice a couple of things going on in this listing. First, only one event handler method is needed, as the sender parameter will tell you which RadioButton sent the event. Second, you need to check for a true Checked value because the CheckedChanged event is also triggered on the unchecking event, which also always occurs when a different RadioButton is checked. And the final thing you might want to notice is that you can use the AddRange() method instead of the Add() method to add controls to the form because there is a readymade array using this method, as the array of RadioButtons is also an array of controls.
I also play with colors a bit, but you look at colors in detail in Chapter 11, so I will hold off the explanation until then.
Figure 9-10 shows what ArrayOfRadios.exe looks like when you execute it.
Figure 9-10: An array of radio buttons
The GroupBox control does basically what its name suggests: It groups controls into a box. Not only does the GroupBox group controls visually, but it also binds the controls so that they act as a group.
The GroupBox control is predominately used for RadioButton controls, but that isn't a requirement. The requirement is that everything it groups is a control. Grouping random control types is usually done just for cosmetic reasons. Grouping RadioButton controls, on the other hand, provides the RadioButton control with additional functionality. Instead of being able to select only a single RadioButton on the form, you now can select a unique RadioButton for each GroupBox.
The following example (see Listing 9-9) shows how it is now possible to select more than one RadioButton—in this case, one of the RadioButton controls attached to the form and one from each of the GroupBoxes. Notice I use three arrays of RadioButtons. If you were to create a unique RadioButton each time instead of the array, as is the case for the generated GUI-designed code, you would then be declaring and implementing 12 different RadioButtons. I think this is a good example of how knowing how to code Win Forms by hand improves the code.
Listing 9-9: The Code for Grouping RadioButtons
namespace GroupingRadios { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); BuildRadios(); } protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::GroupBox * groupBox1; System::Windows::Forms::GroupBox * groupBox2; System::ComponentModel::Container * components; System::Windows::Forms::RadioButton * radio1[]; System::Windows::Forms::RadioButton * radio2[]; System::Windows::Forms::RadioButton * radio3[]; void InitializeComponent(void) { this->groupBox1 = new System::Windows::Forms::GroupBox(); this->groupBox2 = new System::Windows::Forms::GroupBox(); this->SuspendLayout(); // // groupBox1 // this->groupBox1->Location = System::Drawing::Point(150, 15); this->groupBox1->Name = S"groupBox1"; this->groupBox1->Size = System::Drawing::Size(150, 130); this->groupBox1->TabIndex = 0; this->groupBox1->TabStop = false; this->groupBox1->Text = S"You"; // // groupBox2 // this->groupBox2->Location = System::Drawing::Point(150, 160); this->groupBox2->Name = S"groupBox2"; this->groupBox2->Size = System::Drawing::Size(150, 130); this->groupBox2->TabIndex = 1; this->groupBox2->TabStop = false; this->groupBox2->Text = S"Use"; // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(352, 330); this->Controls->Add(this->groupBox2); this->Controls->Add(this->groupBox1); this->Name = S"Form1"; this->Text = S"Using Group Boxes"; this->ResumeLayout(false); } void BuildRadios() { this->SuspendLayout(); // Text for RadioButton places on Form directly String *rbText1[]= {S"Can", S"You", S"Click", S"More", S"Than", S"One"}; // Build a RadioButton for each rbText1 radio1 = new RadioButton*[6]; for (Int32 i = 0; i < radio1->Count; i++) { radio1[i] = new RadioButton(); radio1[i]->Location = Drawing::Point(20, 20+(40*i)); radio1[i]->Text = rbText1[i]; } // Add RadioButtons to Form Controls->AddRange(radio1); // Text for RadioButton places in first GroupBox String *rbText2[] = {S"Can", S"If", S"You"}; // Build a RadioButton for each rbText2 radio2 = new RadioButton*[3]; for (Int32 i = 0; i < radio2->Count; i++) { radio2[i] = new RadioButton(); radio2[i]->Location = Drawing::Point(40, 30+(35*i)); radio2[i]->Text = rbText2[i]; } // Add RadioButtons to GroupBox groupBox1->Controls->AddRange(radio2); // Text for RadioButton places in second GroupBox String *rbText3[] = {S"Different", S"Group", S"Boxes"}; // Build a RadioButton for each rbText3 radio3 = new RadioButton*[3]; for (Int32 i = 0; i < radio3->Count; i++) { radio3[i] = new RadioButton(); radio3[i]->Location = Drawing::Point(40, 30+(35*i)); radio3[i]->Text = rbText3[i]; } // Add RadioButtons to GroupBox2 groupBox2->Controls->AddRange(radio3); this->ResumeLayout(false); } }; }
Only a couple of things are new here. First, notice now that you add the GroupBox to the form and then add the RadioButtons to the GroupBox, as opposed to adding the RadioButtons to the form. You can also add the RadioButtons to the GroupBox and then add the GroupBox to the form. Which of the previous methods you choose is not important, so long as the controls are defined and instantiated before being added.
The second new thing is the location where you put the RadioButtons. The location is relative to the GroupBox and not the form. Notice that the exact same code is used to specify the location of the RadioButtons for both GroupBoxes.
As you can see, you can combine the autogenerated GUI tool code and the hand-coded code together, but you have to be careful. You can't add your code within the InitializeComponent() method, because the GUI design tool will overwrite it any time you change the form using the design tool. Because this is the case, I had to create the BuildRadios() method to add my hand-designed code instead of embedding it directly within the InitializeComponent() method.
Figure 9-11 shows what GroupingRadios.exe looks like when you execute it. Try to click the radio buttons. Now you are able to select three different ones.
Figure 9-11: Groups of radio buttons
The Panel control is similar in many ways to the GroupBox control. It also groups controls visually into a box and binds them so that they act as a group. It differs in that you can enable it to support scrolling, thus letting the Panel control contain more controls than its area would normally allow.
A feature that both the Panel and GroupBox controls share is that when you disable the Panel, all the controls within the Panel are also disabled. You do this by setting the Enable property to false. Another feature I particularly like is that you can make the Panel invisible by setting the Visible property to false. Using this feature, you can make the form less cluttered by hiding Panels that are not currently relevant.
Listing 9-10 shows how it is now possible to enable, disable, and make Panels reappear. It also highlights how to enable autoscrolling within a Panel.
Listing 9-10: The Code for Disabling and Hiding Panels
namespace Panels { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) //... protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::Panel * Leftpanel; private: System::Windows::Forms::Panel * Rightpanel; private: System::Windows::Forms::Button * bnDisable; private: System::Windows::Forms::Button * bnHide; private: System::Windows::Forms::Button * button1; private: System::Windows::Forms::Button * button2; private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->Leftpanel = new System::Windows::Forms::Panel(); this->Rightpanel = new System::Windows::Forms::Panel(); this->bnDisable = new System::Windows::Forms::Button(); this->bnHide = new System::Windows::Forms::Button(); this->button1 = new System::Windows::Forms::Button(); this->button2 = new System::Windows::Forms::Button(); this->Leftpanel->SuspendLayout(); this->Rightpanel->SuspendLayout(); this->SuspendLayout(); // // Leftpanel // this->Leftpanel->BorderStyle = System::Windows::Forms::BorderStyle::FixedSingle; this->Leftpanel->Controls->Add(this->bnHide); this->Leftpanel->Controls->Add(this->bnDisable); this->Leftpanel->Location = System::Drawing::Point(32, 24); this->Leftpanel->Name = S"Leftpanel"; this->Leftpanel->Size = System::Drawing::Size(145, 110); this->Leftpanel->TabIndex = 0; // // Rightpanel // this->Rightpanel->AutoScroll = true; this->Rightpanel->BorderStyle = System::Windows::Forms::BorderStyle::Fixed3D; this->Rightpanel->Controls->Add(this->button2); this->Rightpanel->Controls->Add(this->button1); this->Rightpanel->Location = System::Drawing::Point(192, 24); this->Rightpanel->Name = S"Rightpanel"; this->Rightpanel->Size = System::Drawing::Size(145, 70); this->Rightpanel->TabIndex = 1; // // bnDisable // this->bnDisable->Location = System::Drawing::Point(20, 8); this->bnDisable->Name = S"bnDisable"; this->bnDisable->Size = System::Drawing::Size(100, 24); this->bnDisable->TabIndex = 0; this->bnDisable->Text = S"Disable Panel"; this->bnDisable->Click += new System::EventHandler(this, bnDisable_Click); // // bnHide // this->bnHide->Location = System::Drawing::Point(20, 72); this->bnHide->Name = S"bnHide"; this->bnHide->Size = System::Drawing::Size(100, 24); this->bnHide->TabIndex = 1; this->bnHide->Text = S"Hide Panel"; this->bnHide->Click += new System::EventHandler(this, bnHide_Click); // // button1 // this->button1->Location = System::Drawing::Point(24, 8); this->button1->Name = S"button1"; this->button1->TabIndex = 0; this->button1->Text = S"button1"; // // button2 // this->button2->Location = System::Drawing::Point(24, 72); this->button2->Name = S"button2"; this->button2->TabIndex = 1; this->button2->Text = S"button2"; // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(370, 160); this->Controls->Add(this->Rightpanel); this->Controls->Add(this->Leftpanel); this->Name = S"Form1"; this->Text = S"A hidden fourth button"; this->Leftpanel->ResumeLayout(false); this->Rightpanel->ResumeLayout(false); this->ResumeLayout(false); } private: System::Void bnDisable_Click(System::Object * sender, System::EventArgs * e) { Rightpanel->Enabled = !Rightpanel->Enabled; } private: System::Void bnHide_Click(System::Object * sender, System::EventArgs * e) { Rightpanel->Visible = !Rightpanel->Visible; } }; }
What's interesting in this form is the ability to use a button to disable and hide Panels. Another neat feature is that you can use the Enable and Visible properties as toggles:
Rightpanel->Enabled = !Rightpanel->Enabled; Rightpanel->Visible = !Rightpanel->Visible;
To get RightPanel to scroll, you have to set its client size smaller than the visual area needed to view all controls. Basically, because a control is going to be obscured, the Panel automatically creates the appropriate scroll bar (either vertical or horizontal) so that the control can be exposed.
Figure 9-12 shows what Panels.exe looks like when you execute it and click the Disable Panel button. I guess I could have also clicked the Hide Panel button, but then the RightPanel would have disappeared and you wouldn't be able to tell that it was disabled.
Figure 9-12: Disabling and hiding panels
There is obviously a need to enter text into most Win Forms applications. To handle this, the .NET Framework provides two highly configurable text controls: TextBox and RichTextBox. Both text controls are very powerful. In fact, the simpler of the two, the TextBox control, has so much functionality that you will probably use it most, if not all, of the time. A few possible exceptions are when you require font styles such as boldface, italic, or underline within the text being entered.
As is the common theme in the .NET Framework class library, the text controls derive from a common abstract base class, TextBoxBase. This class provides a common set of functionality that you can use for both text controls and it's also a great starting point for those programmers who need to write a text control to meet specific needs.
The abstract TextBoxBase class is composed of numerous properties and methods that can handle text input from the user. Being that TextBoxBase is an abstract class, you can't instantiate from it; instead, you need to use one of its child classes. Here are some common TextBoxBase-specific properties:
AcceptsTab is a Boolean that represents, in a multiline text control, whether the Tab key will be used as a control character or as a means to move to the next control. The default is false.
CanUndo is a Boolean that represents whether the control can undo the previous operation that occurred. The default is false.
MaxLength is an Int32 that represents the maximum number of characters allowed to be entered into the control. The default is 0, which means the allowable number of characters enterable is only restricted by the memory of the computer.
Modified is a Boolean that represents whether the content of the control has been modified since the control was created or the contents were set. The default is false.
Multiline is a Boolean that represents whether the control is made up of more than one line. The default is false.
ReadOnly is a Boolean that represents whether the control is read-only. The default is false.
SelectedText is a String containing selected text from the control. The default is a zero-length String (not null).
SelectionLength is an Int32 that represents the length of the selected text. If the SelectionLength property is set to a value larger than the length of text within the control, it's automatically set to the number of characters in the control minus the SelectionStart property.
SelectionStart is an Int32 that represents the starting location of the selected text within the control. If the SelectionStart property is set to a value larger than the number of characters within the control, it's automatically set to the value after the last character in the control.
Text is a String that represents the text of the control.
WordWrap is a Boolean that represents, in a multiline text control, whether a word wraps automatically to the beginning of a line when necessary. If the value is false, the control will scroll horizontally when text is entered beyond the width of the control. The default is true.
Here are some common TextBoxBase-specific methods:
AppendText() adds text to the end of the current text of the control.
Clear() sets the text in the control to be empty.
ClearUndo() removes the last undo operation from the undo buffer.
Copy () takes the selected text and places it on the Clipboard. The control is unaffected.
Cut() removes the selected text from the control and places it on the Clipboard.
Paste() copies the text in the Clipboard to the current location of the cursor in the control.
Select() selects text within the control using a start location and a length.
SelectAll() selects all the text within the control.
Undo() restores the contents in the text control back to the previous state before the last operation.
As stated earlier, you can configure the TextBox control in many ways ranging from long to short, normal to password hidden, and single to multilined. If you enable this control, you have a built-in undo buffer. You can cut and paste to it. The functionality this control has is simply amazing.
Along with the properties provided by TextBoxBase, the TextBox control adds a few properties of its own:
AcceptReturn is a Boolean that represents, in a multiline control, whether pressing the Enter key creates a new line of text or passes control to the default button of the form. If this property is set to false, then Ctrl-Enter must be pressed to create a new line of text. The default is true.
CharacterCasing is a CharacterCasing enum that notifies the control as characters are entered into the control that it should convert the character to uppercase, lowercase, or leave the character as typed. The default is CharacterCasing::Normal or leave the characters as they are typed.
PasswordChar is a Char that represents the character to be used to replace all the characters typed in, thus hiding the password from view. The default is the value 0, meaning do not use PasswordChar.
TextAlign is a HorizontalAlignment enum that represents whether the text should be right justified, left justified, or centered when entered. The default is HorizontalAlignment::Left or left justified.
The following example (see Listing 9-11) demonstrates some features of the TextBox control. First it creates a text box to handle input. When you press Enter or click the Submit button, the text gets inserted into the front of the read-only, multiline text box. This multiline text box can be made editable if you enter Editable in the bottom password text box.
Listing 9-11: Some TextBox Code
namespace TextEntry { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) //... protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::TextBox * Entry; private: System::Windows::Forms::TextBox * Result; private: System::Windows::Forms::Button * bnSubmit; private: System::Windows::Forms::TextBox * Password; private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->Entry = new System::Windows::Forms::TextBox(); this->Result = new System::Windows::Forms::TextBox(); this->Password = new System::Windows::Forms::TextBox(); this->bnSubmit = new System::Windows::Forms::Button(); this->SuspendLayout(); // // Entry // this->Entry->Location = System::Drawing::Point(25, 25); this->Entry->Name = S"Entry"; this->Entry->Size = System::Drawing::Size(260, 22); this->Entry->TabIndex = 0; // // Result // this->Result->AcceptsReturn = true; this->Result->AcceptsTab = true; this->Result->Location = System::Drawing::Point(25, 65); this->Result->Multiline = true; this->Result->Name = S"Result"; this->Result->ReadOnly = true; this->Result->ScrollBars = System::Windows::Forms::ScrollBars::Vertical; this->Result->Size = System::Drawing::Size(330, 240); this->Result->TabIndex = 1; // // Password // this->Password->Location = System::Drawing::Point(25, 320); this->Password->Name = S"Password"; this->Password->PasswordChar = '*'; this->Password->Size = System::Drawing::Size(330, 22); this->Password->TabIndex = 2; this->Password->Text = S"You cannot cut or copy but you can paste text"; this->Password->TextChanged += new System::EventHandler(this, Password_TextChanged); // // bnSubmit // this->bnSubmit->Location = System::Drawing::Point(290, 25); this->bnSubmit->Name = S"bnSubmit"; this->bnSubmit->Size = System::Drawing::Size(60, 25); this->bnSubmit->TabIndex = 3; this->bnSubmit->Text = S"Submit"; this->bnSubmit->Click += new System::EventHandler(this, bnSubmit_Click); // // Form1 // this->AcceptButton = this->bnSubmit; this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(370, 360); this->Controls->Add(this->bnSubmit); this->Controls->Add(this->Password); this->Controls->Add(this->Result); this->Controls->Add(this->Entry); this->Name = S"Form1"; this->Text = S"Simple Text Entry"; this->ResumeLayout(false); } private: System::Void Password_TextChanged(System::Object * sender, System::EventArgs * e) { // if the Password TextBox Text equals "Editable" then make // the multiline TextBox editable if (Password->Text->Equals(S"Editable")) Result->ReadOnly = false; else Result->ReadOnly = true; } private: System::Void bnSubmit_Click(System::Object * sender, System::EventArgs * e) { // Grab a StringBuilder from the Text of the Multiline Textbox Result->Text = String::Concat(Entry->Text, S"\r\n", Result->Text); Entry->Clear(); } }; }
Notice a few things about this example. Pressing the Tab key creates a tab character in the multiline text box (when editable, obviously), but jumps to the next control when any other control has the focus. Also, pressing the Enter key creates a new line of text in the multiline text box when it has the focus, but causes the AcceptButton to be triggered if any other control has focus.
There is nothing special about the code in Listing 9-11. In fact, I created it entirely using the GUI design tool, except (obviously) the event handler code.
Figure 9-13 shows what TextEntry.exe looks like when you execute it.
Figure 9-13: Assorted text boxes
Plain and simple, the RichTextBox control is overkill, for most cases, when you need text input. This control provides advanced formatting features, such as boldface, italics, underline, color, and different fonts. It is also possible to format paragraphs. You can assign text directly to the control using the Text property, or you can load it from a Rich Text Format (RTF) or plain text file using the LoadFile() method.
The RichTextBox control is a little tricky to use, as most of the added functionality over the TextBox control requires the handling of events or other controls, such as buttons, to implement. For example, implementing boldfacing of text within a RichTextBox requires implementing the SelectionFont property, which needs to be referenced somehow. In the following example I do this by pressing the F1 key, but you could do it any number of other ways.
The RichTextBox control provides a number of additional properties to handle the formatting features it provides. Here are some of the more common properties:
BulletIndent is an Int32 that represents the number of pixels inserted as the indentation after a bullet. The default is zero.
CanRedo is a Boolean that represents whether undone operations can be reapplied.
RedoActionName is a String that represents the name of the next redo action to be applied. If the return String is empty (a zero-length String, not a null), then there are no more actions that can be redone.
RightMargin is an Int32 that represents the number of pixels from the left side of the control where the nonvisible right margin is placed.
Rtf is a String that represents the RTF-formatted data in the control. The content of the Rtf property differs from that of the Text property in that the Rtf property is in Rich Text Format, whereas the Text property is in just plain text.
Scrollbars is a RichTextScrollbars enum that represents which (if any) scroll bars will be visible within the control. The default is RichTextScrollbars:: Both, which will display both vertical and horizontal scroll bars if needed. I prefer to use ForceVertical instead because it stops the control from having to readjust itself when the content extends beyond the vertical height of the control. It now simply enables the already visible vertical scroll bar.
SelectedRtf is a String containing selected RTF-formatted text from the control. The default is a zero-length String (not null).
SelectionBullet is a Boolean that represents whether the bullet style should be applied to the current selected text or insertion point. The default is false.
SelectionColor is a System::Drawing::Color that represents the color of the selected text. If more than one color falls within the selected text, then Color::Empty is returned.
Selection Font is a System::Drawing::Font that represents the font of the selected text. If more than one font falls within the selected text, then null is returned.
SelectionHangingIndent is an Int32 that represents the distance in pixels between the left edge of the first line of text in the selected paragraph and the left edge of subsequent lines in the same paragraph.
SelectionIndent is an Int32 that represents the distance in pixels between the left edge of the control window and the left edge of the current selected text or text added after the insertion point.
SelectionRightIndent is an Int32 that represents the distance in pixels between the right edge of the text and the right edge of the control.
SelectionTabs is an array of Int32 that represents a set of absolute tab locations in pixels.
ShowSelectionMargin is a Boolean that represents whether the selection margin on the left side of the control is expanded for easier access. Clicking the margin highlights the entire row. The default is false.
UndoActionName is a String that represents the name of the next undo action to be applied. If the return String is empty (a zero-length String, not a null), then there are no more actions that can be undone.
The RichTextBox control provides a number of additional methods as well:
Find () searches for the specified text within the control.
LoadFile() loads a text or RTF-formatted file into the control.
Redo() will redo the last undo operation done on the control.
SaveFile() saves a text or RTF-formatted file to specified path/file location.
Undo() will undo the last operation done on the control.
The following example (see Listing 9-12) is an extremely simple and limited use of the functionality of the RichTextBox. It lacks many of the features that are available, but it is a good starting point and gives you some ideas about how to implement your own RTF editor, if you are so inclined.
Listing 9-12: Implementing a Simple RTF Editor
namespace RichText { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) //... protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::RichTextBox * rtBox; private: System::Windows::Forms::Label *labels[]; private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->rtBox = new System::Windows::Forms::RichTextBox(); this->SuspendLayout(); // // rtBox // this->rtBox->Anchor = (System::Windows::Forms::AnchorStyles) (((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Bottom) | System::Windows::Forms::AnchorStyles::Left) | System::Windows::Forms::AnchorStyles::Right); this->rtBox->Location = System::Drawing::Point(0, 32); this->rtBox->Name = S"rtBox"; this->rtBox->RightMargin = 900; this->rtBox->ScrollBars = System::Windows::Forms::RichTextBoxScrollBars::ForcedVertical; this->rtBox->ShowSelectionMargin = true; this->rtBox->Size = System::Drawing::Size(950, 488); this->rtBox->TabIndex = 0; this->rtBox->Text = S""; this->rtBox->KeyDown += new System::Windows::Forms::KeyEventHandler(this, rtBox_KeyDown); // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(950, 520); this->Controls->Add(this->rtBox); this->Name = S"Form1"; this->Text = S"(Very Simple Rich Text Editor)"; this->ResumeLayout(false); } private: void BuildLabels() { String *rtLabel[] = {S"F1-Bold", S"F2-Italics", S"F3-Underline", S"F4-Normal", S"F5-Red", S"F6-Blue", S"F7-Green", S"F8-Black", S"F9-Load", S"F10-Save"}; labels = new Label*[10]; // Build the labels for (Int32 i = 0; i < labels->Count; i++) { labels[i] = new Label(); labels[i]->BackColor = SystemColors::ControlDark; labels[i]->BorderStyle = BorderStyle::FixedSingle; labels[i]->Location = Drawing::Point(5+(95*i), 8); labels[i]->Size = Drawing::Size(85, 16); labels[i]->Text = rtLabel[i]; labels[i]->TextAlign = ContentAlignment::MiddleCenter; } // Place labels on the Form Controls->AddRange(labels); } private: System::Void rtBox_KeyDown(System::Object * sender, System::Windows::Forms::KeyEventArgs * e) { try { if (rtBox->SelectionLength > 0) { // Change selected text style FontStyle fs; switch (e->KeyCode) { case Keys::F1: fs = FontStyle::Bold; break; case Keys::F2: fs = FontStyle::Italic; break; case Keys::F3: fs = FontStyle::Underline; break; case Keys::F4: fs = FontStyle::Regular; break; // Change selected text color case Keys::F5: rtBox->SelectionColor = Color::Red; break; case Keys::F6: rtBox->SelectionColor = Color::Blue; break; case Keys::F7: rtBox->SelectionColor = Color::Green; break; case Keys::F8: rtBox->SelectionColor = Color::Black; break; } // Do the actual change of the selected text style if (e->KeyCode >= Keys::F1 && e->KeyCode <= Keys::F4) { rtBox->SelectionFont = new Drawing::Font( rtBox->SelectionFont->FontFamily, rtBox->SelectionFont->Size, fs ); } } // Load hard coded Chapter01.rtf file else if (e->KeyCode == Keys::F9) { rtBox->LoadFile(S"Chapter01.rtf"); } // Save hard coded Chapter01.rtf file else if (e->KeyCode == Keys::F10) { rtBox->SaveFile(S"Chapter01.rtf", RichTextBoxStreamType::RichText); } } // Capture any blowups catch (Exception *e) { MessageBox::Show(String::Format(S"Error: {0}", e->Message)); } } }; }
In the example, pressing the F9 key loads a couple of pages from a novel I am writing. You can save the file back by pressing F10. To test out the special features of this RichTextBox, select some text with the mouse and then press one of the remaining function keys (F1-F8).
As you can see, implementing the functionality of the RichTextBox is done externally to the control itself. You need some way of updating the properties. I took the easy way out by capturing simple function keystroke events and updating the selected RichTextBox text as appropriate. You will probably want to use a combination of keystrokes, button clicks, and so on to make the editing process as easy as possible.
Another interesting bit of code in this example is the use of the Anchor property:
this->rtBox->Anchor = (System::Windows::Forms::AnchorStyles) (((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Bottom) | System::Windows::Forms::AnchorStyles::Left) | System::Windows::Forms::AnchorStyles::Right);
This property allows you to have a control anchor itself to any or all (as shown in the previous code) sides of the parent window. Thus, when the parent resizes so does the control.
Be careful when you run this program, as it is dependant on where it is executed. To make things easier, I hard-coded the program to load and save to the current working directory. When you run this program within Visual Studio .NET, the current working directory is located where your source code is. Thus, the Chapter01.rtf file is located in the same directory as the source code. If you run this program on its own out of Windows Explorer, for example, then it will not find the .rtf file. In this scenario, you need to copy the file to the same directory as the executable. Obviously, if you wanted to make the program more robust, you would allow a user to specify where the .rtf file is, so this dependency would not be an issue.
Figure 9-14 shows what RichText.exe looks like when you execute it.
Figure 9-14: The simple RTF editor in action
The three common selection controls, ListBox, ComboBox, and CheckedListBox, are the last of the more basic controls provided by the .NET Framework class library that you will cover in this chapter. Each of the controls represents a selectable scrolling list of items.
When you create a selection control, you provide it with a collection of values for it to display. Each value within the collection has a unique index. The control keeps track of these indices for you, along with their associated values. All you have to do is handle selection events sent by the selection controls or query the control for which a value or values have been selected, either by value or index.
Selection controls are helpful when you want to select from a list of items of a "reasonable" size. "Reasonable," though, is a very relative term and it depends on what the user is selecting and where the data is coming from (fetching 300 rows from a local hard disk is different than fetching them from a mainframe in another country). For example, a list of 50 items may seem excessive in a selection control if there is no rhyme or reason to it, but it is just right when you are looking for a state in America.
All of the selection controls inherit from the abstract class ListControl. This provides a common set of properties and methods from which to build upon. Selection controls have the capability to display lists originating from sources that implement the IList interface. The functionality is provided by the ListControl's property, DataSource. You will see an example of this when you cover ADO.NET in Chapter 12.
Here is a list of some of the most common properties found in the ListControl class and thus inherited by the ListBox, ComboBox, and CheckListBox controls:
DataSource is an Object that implements the IList interface, frequently an Array or DataSet, which represents the items that make up the control. The default is null, which means no DataSource is being used.
SelectedIndex is an Int32 that represents the zero-based index of the currently selected item. If no index is selected, then -1 will be returned.
SelectedValue is an Object that represents the value of the currently selected item as specified by the control data source's ValueMember. If the ValueMember is not specified, then the ToString() value is returned.
ValueMember is a String that represents the property of the control's data source to use as the value. The default is an empty String (and not null), meaning that it uses the ToString() value.
The ListBox is truly just a selection list, whereas the ComboBox is a combination of a ListBox and a TextBox. The CheckListBox, on the other hand, is a combination of a ListBox and a CheckBox. In fact, the CheckListBox inherits directly from ListBox and thus only indirectly from ListControl.
The ListBox control is a simple scrollable list of items from which a user can select one or more items, depending on the SelectionMode of the ListBox. Four modes are available:
SelectionMode::None: No items can be selected.
SelectionMode::One: Only one item can be selected at a time.
SelectionMode::MultiSimple: More than one item can be selected.
SelectionMode::MultiExtended: More than one item can be selected. The method of selecting the multiple items uses the Shift and Ctrl keys to allow for swifter selection of items.
The ListBox control provides a number of additional properties from the ListControl to configure the control and organize, find, and select the data within:
Items is a ListBox::ObjectCollection that represents the collection of items within the control. The ObjectCollection allows you to do things such as add and remove items from the ListBox. Note that this method of providing items to the ListBox is not the same as using a DataSource. If you use a DataSource, you cannot manipulate the items in the ListBox using the ObjectCollection.
MultiColumn is a Boolean that represents whether the control can be broken into multiple columns. The default is false.
SelectedIndices is a ListBox::SelectedIndexCollection that represents the collection of zero-based indices of currently selected items within the control.
SelectedItems is a ListBox::SelectedObjectCollection that represents the collection of currently selected items within the control.
Sorted is a Boolean that represents whether the control is automatically sorted. The default is false.
Text is a String that represents the value of the currently selected item. If you set the value of the Text property, then the ListBox searches itself for an item that matches the Text property and selects that item.
The ListBox control also provides a number of additional methods:
ClearSelected() deselects all selected items in the control.
FindString() finds the first item that starts with a given String.
FindStringExact() finds the first item that exactly matches a given String.
GetSelected() determines if a given item is currently selected.
SetSelected() selects the items at the given index.
Sort() sorts the items in the control.
Listing 9-13 shows how to transfer selected items between two different lists. The ListBox on the left is sorted and is a MultiExtended list, whereas the one on the right is not sorted and is a MultiSimple list.
Listing 9-13: Transferring Items Between ListBoxes
namespace ListTransfers { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) //... protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::Label * lOrg; private: System::Windows::Forms::Label * lDest; private: System::Windows::Forms::Button * bnL2R; private: System::Windows::Forms::Button * bnR2L; private: System::Windows::Forms::ListBox * LBOrg; private: System::Windows::Forms::ListBox * LBDest; private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->lOrg = new System::Windows::Forms::Label(); this->lDest = new System::Windows::Forms::Label(); this->bnL2R = new System::Windows::Forms::Button(); this->bnR2L = new System::Windows::Forms::Button(); this->LBOrg = new System::Windows::Forms::ListBox(); this->LBDest = new System::Windows::Forms::ListBox(); this->SuspendLayout(); // // lOrg // this- >lOrg->Location = System::Drawing::Point(24, 16); this->lOrg->Name = S"lOrg"; this->lOrg->Size = System::Drawing::Size(136, 23); this->lOrg->TabIndex = 0; this->lOrg->Text = S"Sorted Multiextended"; // // lDest // this->lDest->Location = System::Drawing::Point(256, 16); this->lDest->Name = S"lDest"; this->lDest->Size = System::Drawing::Size(136, 23); this->lDest->TabIndex = 1; this->lDest->Text = S"Unsorted Multisimple"; // // bnL2R // this->bnL2R->Location = System::Drawing::Point(200, 88); this->bnL2R->Name = S"bnL2R"; this->bnL2R->Size = System::Drawing::Size(40, 23); this->bnL2R->TabIndex = 2; this->bnL2R->Text = S"==>"; this->bnL2R->Click += new System::EventHandler(this, bnL2R_Click); // // bnR2L // this->bnR2L->Location = System::Drawing::Point(200, 120); this->bnR2L->Name = S"bnR2L"; this->bnR2L->Size = System::Drawing::Size(40, 23); this->bnR2L->TabIndex = 3; this->bnR2L->Text = S"<=="; this->bnR2L->Click += new System::EventHandler(this, bnR2L_Click); // // LBOrg // this->LBOrg->ItemHeight = 16; System::Object* __mcTemp__ 1[] = new System::Object*[10]; __mcTemp__1[0] = S"System"; __mcTemp__1[1] = S"System::Collections"; __mcTemp__1[2] = S"System::Data"; __mcTemp__1[3] = S"System::Drawing"; __mcTemp__1[4] = S"System::IO"; __mcTemp__1[5] = S"System::Net"; __mcTemp__1[6] = S"System::Threading"; __mcTemp__1[7] = S"System::Web"; __mcTemp__1[8] = S"System::Windows::Forms"; __mcTemp__1[9] = S"System::Xml"; this->LBOrg->Items->AddRange(__mcTemp__1); this->LBOrg->Location = System::Drawing::Point(24, 48); this->LBOrg->Name = S"LBOrg"; this->LBOrg->SelectionMode = System::Windows::Forms::SelectionMode::MultiExtended; this->LBOrg->Size = System::Drawing::Size(160, 164); this->LBOrg->Sorted = true; this->LBOrg->TabIndex = 0; this->LBOrg->DoubleClick += new System::EventHandler(this, LBOrg_DoubleClick); // // LBDest // this->LBDest->ItemHeight = 16; this->LBDest->Location = System::Drawing::Point(256, 48); this->LBDest->Name = S"LBDest"; this->LBDest->SelectionMode = System::Windows::Forms::SelectionMode::MultiSimple; this->LBDest->Size = System::Drawing::Size(160, 164); this->LBDest->TabIndex = 1; this->LBDest->DoubleClick += new System::EventHandler(this, LBDest_DoubleClick); // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(440, 231); this->Controls->Add(this->LBDest); this->Controls->Add(this->LBOrg); this->Controls->Add(this->bnR2L); this->Controls->Add(this->bnL2R); this->Controls->Add(this->lDest); this->Controls->Add(this->lOrg); this->Name = S"Form1"; this->Text = S"List Box Transfers"; this->ResumeLayout(false); } private: System::Void LBOrg_DoubleClick(System::Object * sender, System::EventArgs * e) { // Add Selected item to other ListBox // Then remove item from original if (LBOrg->SelectedItem != 0) { LBDest- >Items- >Add(LBOrg- >SelectedItem); LBOrg->Items->Remove(LBOrg->SelectedItem); } } private: System::Void LBDest_DoubleClick(System::Object * sender, System::EventArgs * e) { // Add Selected item to other ListBox // Then remove item from original if (LBDest->SelectedItem != 0) { LBOrg->Items->Add(LBDest->SelectedItem); LBDest->Items->Remove(LBDest->SelectedItem); } } private: System::Void bnL2R_Click(System::Object * sender, System::EventArgs * e) { // Add all Selected items to other ListBox // Then remove the all items from original Object *tmp[] = new Object*[LBOrg->SelectedItems->Count]; LBOrg->SelectedItems->CopyTo(tmp, 0); LBDest->Items->AddRange(tmp); for (Int32 i = 0; i < tmp->Count; i++) LBOrg->Items->Remove(tmp[i]); } private: System::Void bnR2L_Click(System::Object * sender, System::EventArgs * e) { // Add all Selected items to other ListBox // Then remove all the items from original Object *tmp[] = new Object*[LBDest->SelectedItems->Count]; LBDest->SelectedItems->CopyTo(tmp, 0); LBOrg->Items->AddRange(tmp); for (Int32 i = 0; i < tmp->Count; i++) LBDest->Items->Remove(tmp[i]); } }; }
The code is pretty straightforward. It creates two ListBoxes and configures them using their properties. Things you need to pay attention to in Listing 9-13 are that when handling the double-click event for a list, make sure that an item is actually selected by checking the SelectedItem for a non-zero (null) value before trying to work with the SelectedItem. The reason is double-clicking an area of the list that is not an item generates an event with no selection.
The second thing to watch out for is removing items from a list using the SelectedItems property. The SelectedItems property does not create a copy of the items selected; instead, it uses the original items. Thus, if you try to remove items from a list such as the following:
// This code DOES NOT work for (Int32 i = 0; i < LBDest->SelectedItems->Count; i++) { LBDest->Items->Remove(LBDest->SelectedItems->Item[i]); }
not all the selected items get removed—in fact, only half do. What is happening is that LBDest->SelectedItems->Count decreases when you call LBDest->Items->Remove() because the SelectedItems enumeration is decreasing in size at the same time as the ListBox entries are. The solution I came up with was to create a copy of the SelectedItems and then use that instead of SelectedItems directly:
// This DOES work Object *tmp[] = new Object*[LBDest->SelectedItems->Count]; LBDest->SelectedItems->CopyTo(tmp, 0); for (Int32 i = 0; i < tmp->Count; i++) LBDest->Items->Remove(tmp[i]);
On a personal note, the preceding code was generated by Visual Studio .NET. I would have defined __mcTemp__1 like this:
Object * __mcTemp__1[] = { S"System", S"System::Collections", S"System::Data", S"System::Drawing", S"System::IO", S"System::Net", S"System::Threading", S"System::Web", S"System::Windows::Forms", S"System::Xml" };
This way, I can add entries without having to change the array size each time. But hey, it's not my call.
Figure 9-15 shows what ListTransfers.exe looks like when you execute it.
Figure 9-15: Transferring items between list boxes
The ComboBox control is a combination of a ListBox control with a TextBox control attached to the top. The ListBox control provides a quick click response, and the TextBox control allows the user to type in an answer.
There are three different DropDownStyles of ComboBox:
ComboBoxStyle::Simple: The list is always expanded and the text field can be edited.
ComboBoxStyle::DropDown: The list starts collapsed but can be expanded and the text field can be edited.
ComboBoxStyle::DropDownList: The list starts collapsed but can be expanded and the text field only accepts strings that are part of the selection list. (This style of ComboBox does not allow responses that are not part of the list.)
Like all other controls, the ComboBox provides several properties and methods to support the functionality of the control. You will probably recognize that these members are half TextBox and half ListBox in nature. Some of the common members unique to the ComboBox are as follows:
DroppedDown is a Boolean that represents whether the list portion of the control has been expanded.
MaxDropDownItems is an Int32 that represents the maximum number of items that can be visually displayed in the list portion of the control. This number can range from 1 to 100. Note that this is not the same as the total items in the control, which is limited to the memory of the computer, though I doubt you will ever create a list that large (unless of course you accidentally create an infinite loop).
MaxLength is an Int32 that represents the maximum length of the text box portion of the control.
Select() is a method that selects a specified range of text within the text box portion of the control.
SelectAll() is a method that selects all the text in the text box portion of the control.
SelectionLength is an Int32 that represents the length of the selected text within the text box portion of the control.
SelectionStart is an Int32 that represents the zero-based starting position of the selected text within the text box portion of the control.
Listing 9-14 shows that you can keep all three ComboBox style controls in sync. Selecting an item in one control will automatically update the other two. If you type an entry in the text box area, the other two controls are updated appropriately. You should notice that if you type in a value that is not on the selection list, then the DropDownList style control does not update.
Listing 9-14: Synchronizing ComboBoxes
namespace SyncCombos { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public _gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); PopulateLists(); } protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::ComboBox * ddown; private: System::Windows::Forms::ComboBox * simple; private: System::Windows::Forms::ComboBox * ddlist; private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->ddown = new System::Windows::Forms::ComboBox(); this->simple = new System::Windows::Forms::ComboBox(); this->ddlist = new System::Windows::Forms::ComboBox(); this->SuspendLayout(); // // ddown // this->ddown->Location = System::Drawing::Point(16, 24); this->ddown->MaxDropDownItems = 3; this->ddown->MaxLength = 10; this->ddown->Name = S"ddown"; this->ddown->Size = System::Drawing::Size(121, 24); this->ddown->TabIndex = 0; this->ddown->TextChanged += new System::EventHandler(this, ddown_Change); this->ddown->SelectedIndexChanged += new System::EventHandler(this, ddown_Change); // // simple // this->simple->DropDownStyle = System::Windows::Forms::ComboBoxStyle::Simple; this->simple->Location = System::Drawing::Point(184, 24); this->simple->Name = S"simple"; this->simple->Size = System::Drawing::Size(121, 128); this->simple->Sorted = true; this->simple->TabIndex = 1; this->simple->TextChanged += new System::EventHandler(this, simple_Change); this->simple->SelectedIndexChanged += new System::EventHandler(this, simple_Change); // // // ddlist // this->ddlist->DropDownStyle = System::Windows::Forms::ComboBoxStyle::DropDownList; this->ddlist->Location = System::Drawing::Point(352, 24); this->ddlist->Name = S"ddlist"; this->ddlist->Size = System::Drawing::Size(121, 24); this->ddlist->TabIndex = 2; this->ddlist->SelectedIndexChanged += new System::EventHandler(this, ddlist_Change); // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(496, 167); this->Controls->Add(this->ddlist); this->Controls->Add(this->simple); this->Controls->Add(this->ddown); this->Name = S"Form1"; this->Text = S"Synchronized Combo boxing"; this->ResumeLayout(false); } private: void PopulateLists() { // Item to be placed in all ComboBoxes Object *ddItems[] = { S"oranges", S"cherries", S"apples", S"lemons", S"bananas", S"grapes" }; ddown->Items->AddRange(ddItems); simple->Items->AddRange(ddItems); ddlist->Items->AddRange(ddItems); } private: System::Void ddown_Change(System::Object * sender, System::EventArgs * e) { // Update simple and dropdownlist with dropdown text simple->Text = ddown->Text; ddlist->SelectedItem = ddown->Text; } private: System::Void simple_Change(System::Object * sender, System::EventArgs * e) { // Update dropdown and dropdownlist with simple text ddown->Text = simple->Text; ddlist->SelectedItem = simple->Text; } private: System::Void ddlist_Change(System::Object * sender, System::EventArgs * e) { // Update simple and dropdown with dropdownlist SelectedText ddown->SelectedItem = ddlist->SelectedItem; simple->SelectedItem = ddlist->SelectedItem; } }; }
When you are working with Simple or DropDown ComboBoxes, all you usually need to worry about is what is currently in the Text property. This property tells you what the current value is in the ComboBox and by placing the value in it automatically changes the SelectedItem property. On the other hand, when you are working with the DropDownList, it is better to work with the SelectedItem property, because it is more efficient for the control as the editing overhead of the text field goes unused.
Figure 9-16 shows what SyncCombos.exe looks like when you execute it.
Figure 9-16: Synchronized combo boxes
The CheckedListBox control provides you a way to group related check boxes in a scrollable and selectable ListBox control. In other words, this control provides the functionality of an array of check boxes and at the same time the functionality of a ListBox, allowing the selection of a checkable item without actually checking the item off.
The CheckedListBox control directly inherits from the ListBox control, so in addition to the functionality provided by the ListBox, the CheckedListBox provides numerous other properties. Some of the more common are as follows:
CheckedIndices is a CheckedListBox::CheckedIndexCollection that represents the collection of zero-based indices of currently checked or indeterminate state items within the control.
CheckedItems is a CheckedListBox::CheckedItemCollection that represents the collection of currently checked or indeterminate state items within the control.
CheckOnClick is a Boolean that represents whether the check box is toggled immediately on the selection of the check box item. The default is false.
ThreeDCheckBoxes is a Boolean that represents if 3D or flat check boxes are used. The default is false or a flat appearance.
Along with the preceding properties, the CheckListBox control provides several methods. The following methods get access to the checked status of the CheckListBox's items:
GetItemChecked() checks using a specified index whether an item is checked.
GetItemCheckState () checks using a specified index what the check state of the item is.
SetItemChecked() checks or unchecks an item at a specified index.
SetItemCheckState() sets the check status of an item at a specified index.
Working with the CheckedListBox can be a little confusing as selected and checked items are not the same thing. You can have an item that does not check or uncheck when selected.
To get the selected item (you can only have one, unless you select SelectionMode::None), you use the properties prefixed by "Selected". Even though there are properties that suggest more than one item can be selected, these properties return a collection of one item. Basically, the difference between SelectedIndex and SelectedIndices, and SelectedItem and SelectedItems, is that the first returns a single item and the second returns a collection of one item.
To get the checked items from the control, you need to use the properties and methods that contain "Check(ed)" within their name. One thing you should note is that there are two common ways of getting all the checked items in the CheckedListBox. The first method is to use the properties CheckIndices and CheckItems:
for (Int32 i = 0; i < checkedlistbox->CheckedItems->Count; i++) { //...do what you want with: // checkedlistbox->CheckedItems->Item[i]; }
The second method is to use the methods GetItemChecked() and GetItemCheckState():
for (Int32 i = 0; i < checkedlistbox->Items->Count; i++) { if (checkedlistbox->GetItemChecked(i)) { //...do what you want with: // checkedlistbox->Items[i]; } }
The main difference between the two is that the first method provides only a list of checked items, whereas the second requires an iteration through all the items and checks the check status of each.
The example in Listing 9-15 shows how closely the CheckListBox is to an array of Checkboxes and a ListBox. It does this by synchronizing input using these controls.
Listing 9-15: Splitting the CheckedListBox
namespace SplitCLB { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); Object *Items[] = { S"Appleman", S"Challa", S"Chand", S"Cornell", S"Fraser", S"Gunnerson", S"Harris", S"Rammer", S"Symmonds", S"Thomsen", S"Troelsen", S"Vaughn" }; clBox->Items->AddRange(Items); lBox->Items->AddRange(Items); // Create a Check box for each entry in Items array. cBox = new CheckBox*[Items->Count]; Int32 j = cBox->Count/2; for (Int32 i = 0; i < j; i++) { // Build Left Column cBox[i] = new CheckBox(); cBox[i]->Location = Drawing::Point(50, 160+(30*i)); cBox[i]->TabIndex = i+2; cBox[i]->Text = Items[i]->ToString(); cBox[i]->CheckStateChanged += new EventHandler(this, cBox_CheckStateChanged); // Build Right Column cBox[i+j] = new CheckBox(); cBox[i+j]->Location = Drawing::Point(180, 160+(30*i)); cBox[i+j]->TabIndex = i+j+2; cBox[i+j]->Text = Items[i+j]->ToString(); cBox[i+j]->CheckStateChanged += new EventHandler(this, cBox_CheckStateChanged); } // Add all CheckBoxes to Form Controls->AddRange(cBox); } protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::CheckedListBox * clBox; private: System::Windows::Forms::ListBox * lBox; private: System::ComponentModel::Container * components; private: CheckBox *cBox[]; void InitializeComponent(void) { this->clBox = new System::Windows::Forms::CheckedListBox(); this->lBox = new System::Windows::Forms::ListBox(); this->SuspendLayout(); // // clBox // this->clBox->Location = System::Drawing::Point(16, 16); this->clBox->MultiColumn = true; this->clBox->Name = S"clBox"; this->clBox->Size = System::Drawing::Size(304, 106); this->clBox->TabIndex = 0; this->clBox->ThreeDCheckBoxes = true; this->clBox->SelectedIndexChanged += new System::EventHandler(this, clBox_SelectedIndexChanged); this->clBox->ItemCheck += new System::Windows::Forms::ItemCheckEventHandler(this, clBox_ItemCheck); // // lBox // this->lBox->ItemHeight = 16; this->lBox->Location = System::Drawing::Point(360, 16); this->lBox->Name = S"lBox"; this->lBox->Size = System::Drawing::Size(136, 196); this->lBox->TabIndex = 1; this->lBox->SelectedIndexChanged += new System::EventHandler(this, lBox_SelectedIndexChanged); // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(528, 357); this->Controls->Add(this->lBox); this->Controls->Add(this->clBox); this->Name = S"Form1"; this->Text = S"Splitting The Check List Box"; this->ResumeLayout(false); } private: System::Void clBox_ItemCheck(System::Object * sender, System::Windows::Forms::ItemCheckEventArgs * e) { //update state of CheckBox with same index as checked CheckedListBox cBox[e->Index]->CheckState = e->NewValue; } private: System::Void clBox_SelectedIndexChanged(System::Object * sender, System::EventArgs * e) { //update ListBox with same selected item in the CheckedListBox lBox->SelectedItem = clBox->SelectedItem->ToString(); } private: System::Void lBox_SelectedIndexChanged(System::Object * sender, System::EventArgs * e) { //update CheckedListBox with same selected item in the ListBox clBox->SelectedItem = lBox->SelectedItem; } private: void cBox_CheckStateChanged(Object *sender, EventArgs *e) { //update state of CheckedListBox with same index as checked CheckBox CheckBox *cb = dynamic_cast<CheckBox*>(sender); clBox->SetItemCheckState(Array::IndexOf(cBox, cb), cb->CheckState); } }; }
The CheckedListBox provides an event to handle the checking of a box within the control. To handle this event, you need to create a method with the template:
ItemCheck(System::Object *sender, System::Windows::Forms::ItemCheckEventArgs *e)
Conveniently, the handler provides the parameter of type ItemCheckEventArgs, which among other things provides the index of the box being checked and the current and previous state of the box. I use this information to update the external array of check boxes.
cBox[e->Index]->CheckState = e->NewValue;
One other thing of note in the code is the trick I used to get the index of the CheckBox, which triggered the state change event out of the CheckBox array. The Array class has a neat little static method, Array::IndexOf(), which you pass as arguments to the array containing an entry and the entry itself, with the result being the index to that entry. I used this method by passing it the array of CheckBoxes along with the dynamically cast sender Object.
Figure 9-17 shows what SplitCLB.exe looks like when you execute it.
Figure 9-17: Splitting the checklist box
A few timers are sprinkled throughout the .NET Framework class library. One relevant to this chapter is found in the System::Windows::Forms namespace. Though not a GUI control, the Timer is an important component for scheduling events that occur at discrete user-defined intervals.
Notice I called Timer a "component" and not a "control," as it inherits from the Component class but not the Control class. This fact is apparent when you implement a Timer in Visual Studio .NET, because when you drag the component to the Win Form it does not get placed on the form. Instead, it gets placed in its own area at the bottom of the designer window. Even though it is placed there, you still work with the Timer the same way you do with a control. You use the Properties view to update the Timer's properties and events.
The Timer component is easy to use. Just instantiate it in your program:
Timer *timer = new Timer();
Create an event handler to accept Tick events:
void timer_Tick(Object *sender, System::EventArgs *e) { //...Process the Tick event }
And then delegate that event handler:
timer->Tick += new EventHandler(this, timer_Tick);
The Timer component provides a few properties to configure and methods to implement the functionality of the control:
Enabled is a Boolean that represents whether the Timer is enabled or disabled. When enabled, the Timer will trigger Tick events at an interval specified by the Interval property. The default is false or disabled.
Interval is an Int32 that represents the discrete interval in milliseconds between triggering Tick events. The default interval is zero, meaning no interval is set.
Start() is a method that does the same thing as the Enabled property being set to true.
Stop() is a method that does the same thing as the Enabled property being set to false.
The Timer is such a simple example (see Listing 9-16) that 1 decided to throw another less frequently used control, the ProgressBar, into the program. You have seen a progress bar whenever you install software (it's that bar that seems to take forever to slide across). The example is simply a repeating one-minute timer.
Listing 9-16: The One-Minute Timer
namespace MinuteTimer { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); seconds = 0; } protected: void Dispose(Boolean disposing) //... private: System::Windows::Forms::Timer * timer; private: System::Windows::Forms::Label * lbsecs; private: System::Windows::Forms::ProgressBar * progressBar; private: System::ComponentModel::IContainer * components; Int32 seconds; void InitializeComponent(void) { this->components = new System::ComponentModel::Container(); this->lbsecs = new System::Windows::Forms::Label(); this->progressBar = new System::Windows::Forms::ProgressBar(); this->timer = new System::Windows::Forms::Timer(this->components); this->SuspendLayout(); // // lbsecs // this->lbsecs->Location = System::Drawing::Point(25, 25); this->lbsecs->Name = S"lbsecs"; this->lbsecs->Size = System::Drawing::Size(50, 25); this->lbsecs->TabIndex = 0; this->lbsecs->TextAlign = System::Drawing::ContentAlignment::MiddleRight; // // progressBar // this->progressBar->Location = System::Drawing::Point(80, 25); this->progressBar->Maximum = 60; this->progressBar->Name = S"progressBar"; this->progressBar->Size = System::Drawing::Size(300, 25); this->progressBar->TabIndex = 1; // // timer1 // this->timer->Enabled = true; this->timer->Tick += new System::EventHandler(this, timer_Tick); // // Form1 // this->AutoScaleBaseSize = System::Drawing::Size(6, 15); this->ClientSize = System::Drawing::Size(450, 80); this->Controls->Add(this->progressBar); this->Controls->Add(this->lbsecs); this->Name = S"Form1"; this->Text = S"The One Minute Timer"; this->ResumeLayout(false); } private: System::Void timer_Tick(System::Object * sender, System::EventArgs * e) { // Write current tick count (int 10th of second) to label seconds++; seconds %= 600; lbsecs->Text = String::Format(S"{0}.{1}", (seconds/10).ToString(), (seconds%10).ToString()); // Update ProgressBar progressBar->Value = seconds/10; } }; }
The ProgressBar simply shows the amount complete of some activity. You specify the starting point (Minimum) and the end point (Maximum) for which you want to monitor the progress, and then you simply update the value of the ProgressBar between these two points. The default start and end values are 0 to 100, representing progress from 0 percent to 100 percent, which is the most common use for the ProgressBar. In this example, because I am representing seconds in a minute, it made more sense to go from 0 to 60. Updating the ProgressBar itself is very simple, as it will move over automatically when the value exceeds the specified step factor.
Figure 9-18 shows what MinuteTimer.exe looks like when you execute it.
Figure 9-18: The one-minute timer