Software Design Using C++
Third Advanced Windows Forms Example
The Goal
The goal is to create the app shown in this picture.
The idea is that the user enters a list of temperature values in the text box on the left and then clicks
one of the three buttons in the middle. The first button causes the app to compute and display
the average of the temperatures. The next button causes the app to show the average (in the small text
box as for the previous button) and also those temperatures which are above the average (in the large
text box on the right). The last button is similar as it shows the average and also the list of
temperatures which are below the average.
Our picture shows the application after the user has entered a list of
temperatures and clicked the "Find Above Avg" button.
Getting Started
Since you have created a number of Windows forms apps by now, complete directions will not be given.
Instead the main steps will be outlined, with new items looked at in more detail. Of course, you also
know that you can often do things in other ways, select different colors, etc.
Create a new Windows forms app and place on it the labels, buttons, and 3 text boxes as shown in
our picture of the running application.
Change the text field of the form to Temperatures.
Name the text box on the left as OriginalTextBox, the small text box as AvgTextBox, and
the large text box on the right as ResultsTextBox.
Let's adjust some properties of the overall form. You probably want to change the
FormBorderStyle to Fixed3D so that the edges cannot be dragged by the user. It makes
little sense to allow the user to resize this form. Also change the MaximizeBox property to False
so that the user is not presented with a maximize button in the upper right corner of the app.
How about properties for AvgTextBox? Set ReadOnly to True since the user should not be able to
manually change the value of the average. That's about all that you need to do here.
In OriginalTextBox set Multiline to True so that the box can contain many lines of data.
Change the size of this text box so that it can display several lines of data at once.
For the Scrollbars property select Vertical so that a vertical scroll bar will be available
for the user to scroll through the items in the box.
In ResultsTextBox set ReadOnly to True and Multiline to True.
Adjust the size of the box in a way similar to what you did with OriginalTextBox.
For the Scrollbars property select Vertical.
For the topmost of the 3 buttons, change its name to AverageButton and the displayed text to
"Find Average". For the middle button, change its name to AboveAvgButton and the text to
"Find Above Avg". For the last button, change its name to BelowAvgButton and the text to "Find Below Avg".
You can adjust the TabIndex and TabStop properties of the text boxes and buttons if you wish.
Save all of your files. Also right click on the new project in Solution Explorer
and set this project to be the startup project.
Beginning on the Coding
Begin by adding the 2 lines marked below to the top of your Form1.h file.
These changes are primarily to allow use to use NumberFormatInfo
(as we have elsewhere) to control the formatting of numbers.
#pragma once
#using <mscorlib.dll> // added this for NumberFormatInfo, etc.
namespace Temperatures
{
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::Globalization; // added for NumberFormatInfo
|
Double click on each of the 3 buttons so as to generate the outline of a click handler function for each.
These will be located in Form1.h, but we will want the code for these functions in Form1.cpp.
Thus, in Form1.h change the click handler outlines to simply be function prototypes as shown below:
// Click handler for the AverageButton:
private: System::Void AverageButton_Click(System::Object * sender, System::EventArgs * e);
// Click handler for the AboveAvgButton:
private: System::Void AboveAvgButton_Click(System::Object * sender, System::EventArgs * e);
// Click handler for the BelowAvgButton:
private: System::Void BelowAvgButton_Click(System::Object * sender, System::EventArgs * e);
|
First Click Handler
In Form1.cpp fill in the complete click handler for the first button so that it matches the following:
// Click handler for the AverageButton:
System::Void Form1::AverageButton_Click(System::Object * sender, System::EventArgs * e)
{
double Total = 0.0;
double Avg;
IEnumerator * IE;
// Exception handling to detect problems such as including a blank line of data in the avg.
try
{
AvgTextBox->Clear();
ResultsTextBox->Clear();
CultureInfo * MyCI = new CultureInfo(S"en-US", false);
NumberFormatInfo * nfi = MyCI->NumberFormat;
IE = OriginalTextBox->Lines->GetEnumerator();
nfi->NumberDecimalDigits = 2;
while (IE->MoveNext())
Total = Total + IE->Current->ToString()->ToDouble(nfi);
Avg = Total / OriginalTextBox->Lines->get_Count();
AvgTextBox->Text = Avg.ToString(S"N", nfi); // only show 2 decimal digits
}
catch (FormatException * Ex)
{
AvgTextBox->Text = S"Error: Non-number or blank";
}
catch (...) // In case some other exception occurs
{
AvgTextBox->Text = S"Error Occurred";
}
}
|
Essentially the above code loops through all of the temperature numbers in OriginalTextBox
to find their total. Then it divides by the number of such temperatures,
found using OriginalTextBox->Lines->get_Count() , in order to calculate the
average. However, since various things can go wrong, such as a blank line in
OriginalTextBox , exception handling is used so that the program does not crash
in such instances. In those cases where an exception does occur, an error message string is placed
into AvgTextBox , the location where we would normally display the average temperature.
Note: When you get your app built, be sure to test it with non-number data. For example, put abc into
one line of OriginalTextBox . Also try pressing Enter at the end of the last number in this box
so that you have a blank line at the end of the data. All of these things should be caught and dealt with
by the above exception handling.
Note that this click handler begins by clearing the text boxes used for the answers (the average and the
list of temperature above or below average). We don't want old results still showing up!
The click handler also uses the method shown in the previous example
to format our numbers. Here we use 2 decimal places.
It is suggested that you look up IEnumerator in Visual Studio's help system as it is something
new. These enumerators are used to iterate through all of the items in a collection. They allow you
to read the data in the collection, but not to change it. The enumerator starts out pointing just before
the start of the collection. MoveNext advances the enumerator to point to the first data item,
then the next, etc. for each call of MoveNext . MoveNext returns false
if you are already on the last data item in the collection. That is why our code can use
MoveNext to control our while loop. Current obviously returns
the data item currently pointed to by the enumerator.
Save all of your files before going on.
Second Click Handler
Still in Form1.cpp, fill in the complete click handler for the second button.
Make it match the following:
// Click handler for the AboveAvgButton:
System::Void Form1::AboveAvgButton_Click(System::Object * sender, System::EventArgs * e)
{
int count, k;
double Avg;
AverageButton_Click(sender, e); // to be sure the average has been computed
try
{
CultureInfo * MyCI = new CultureInfo(S"en-US", false);
NumberFormatInfo * nfi = MyCI->NumberFormat;
count = OriginalTextBox->Lines->Length;
Avg = AvgTextBox->Text->ToDouble(nfi);
for (k = 0; k < count; k++)
{
if (OriginalTextBox->Lines[k]->ToDouble(nfi) > Avg)
{
ResultsTextBox->AppendText(OriginalTextBox->Lines[k]);
ResultsTextBox->AppendText(Environment::NewLine);
}
}
}
catch (...)
{
ResultsTextBox->Text = S"Error Occurred";
}
}
|
The above function begins by calling the first click handler. This may be unnecessary, but the
user might click the second button without first clicking the first button to find the average.
Thus it is safer to do things this way. You could perhaps instead try to get a number value out
of AvgTextBox and, if this raised an exception, only then call the first click handler.
The number formatting is the same kind of thing that we used before, though here we do not worry
about the number of decimal places. What is different is that we have used an ordinary
for loop to iterate through the data items in OriginalTextBox instead of
using an enumerator as we did for the first click handler. We can easily find out the number of
data lines in OriginalTextBox by using the Length property on the Lines
collection. Then each time around the loop we look to see if OriginalTextBox->Lines[k]
contains a number that is bigger than the average temperature. Note the array subscript notation
for accessing the k-th item of the Lines collection. This makes sense since
Lines is an array of strings. If we find a greater than average number, we use
AppendText to append it to the data items in ResultsTextBox .
A newline must be appended as well to separate each data item from the next. The code above will
leave an extra newline at the end of the list of data in ResultsTextBox , but that
does not hurt anything.
Save all of your files before going on.
Third Click Handler
In Form1.cpp fill in the complete click handler for the third button.
Use the following code:
// Click handler for the BelowAvgButton:
System::Void Form1::BelowAvgButton_Click(System::Object * sender, System::EventArgs * e)
{
int count, k;
double Avg;
AverageButton_Click(sender, e); // to be sure the average has been computed
try
{
CultureInfo * MyCI = new CultureInfo(S"en-US", false);
NumberFormatInfo * nfi = MyCI->NumberFormat;
count = OriginalTextBox->Lines->Length;
Avg = AvgTextBox->Text->ToDouble(nfi);
for (k = 0; k < count; k++)
{
if (OriginalTextBox->Lines[k]->ToDouble(nfi) < Avg)
{
ResultsTextBox->AppendText(OriginalTextBox->Lines[k]);
ResultsTextBox->AppendText(Environment::NewLine);
}
}
}
catch (...)
{
ResultsTextBox->Text = S"Error Occurred";
}
}
|
Other than the fact that this code looks for temperatures that are below, not above, the average
the code is the same as that used in the second click handler. No additional explanation, therefore,
should be needed.
At this point save all of your files. Then build and run your application to
see that it works reasonably. The running app should look like the one shown in
our picture.
Back to the main page for Software Design Using C++
|