CIS Logo SVC Logo

   Computing & Information Systems
   Department

 

Schoology Facebook        Search CIS Site      Tutorials

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++

Author: Br. David Carlson with contributions by Br. Isidore Minerd
Last updated: August 27, 2009
Disclaimer