CIS Logo SVC Logo

   Computing & Information Systems
   Department

 

Schoology Facebook        Search CIS Site      Tutorials

Software Design Using C++



Fourth Advanced Windows Forms Example



Overall Plan


The goal is to create the app shown in this picture. This Windows forms app is for a fictitious business called Larry's Car Lot. The app allows the user to specify the desired price range and then to see the details on Larry's cars that fit this price range. Only one car's data is visible at a time. A scroll bar is used to look through the entire list of data.

We will only use a short list of cars for this app and will hard code the data inside of the software itself. A better program would read the list of car data from a file (or perhaps retrieve it from a database).

Getting Started


Create a new Windows forms app, perhaps named showcars. Change FormBorderStyle to Fixed3D, MaximizeBox to False, and Text to "Larry's Car Lot". Resize the form as needed.

Designing the Form


We want to place on our form the labels, button, and other controls as shown in our picture of the running application. Note the box around the top part of the form. This is a group box control. Put this onto your form first. (Drag it from the toolbox and resize it to fit the top of the form.) Change the Text property for this group box to "Enter desired price range:".

Next, place the labels and controls onto this group box. The labels should use a bold font so that they show up better. We don't care about the names of the labels with Text property "Minimum:" and "Maximum:". However, the label with Text property "Error occurred" should be given the name FindErrorLabel so that we can later easily refer to it in code. Make the foreground color (ForeColor property) red and bold. Set the Visible property to False. We will change this to True within the code if an error occurs in finding matching car data.

Use MinTextBox as the name of the text box next to the "Minimum:" label. Similarly, use MaxTextBox as the name of the text box next to the "Maximum:" label. Used Fixed3D for the BorderStyle for both. Make the font bold and the Text property blank. Name the button as FindButton and change its Text property to "Find". Make the font bold.

Next we place onto the form the controls that do not go on the group box. Put the five labels onto the form as seen in our picture of the app. Don't bother with bold for these. The text box next to the "Type:" label should be named TypeTextBox. The text box next to the "Year:" label should be named YearTextBox. The text box next to the "Doors:" label is to be named DoorsTextBox. Name the text box next to the "Horsepower:" label as HorsepowerTextBox. Also name the text box next to the "Price:" label as PriceTextBox. Clear the Text property for all five labels and set the ReadOnly property to True for all five.

The final control needed is a horizontal scroll bar. This is labeled HScrollBar in the Toolbox. Drag this to the bottom center of your form and change its name to HorizontalScrollBar. As someone uses the scroll bar its Value property changes. This Value fits a certain range, which you can adjust. By default the range is probably 0 through 100. Change the Maximum to 12. Make the LargeChange property 3, which means that when the user clicks, not on the arrow, but to the left or the right of the square on the scroll bar, the Value property will go down or up by 3. This is to help the user to quickly scroll through a lot of data. The SmallChange property should be 1. That means that when the user clicks on one of the arrows on the scroll bar, the Value property will go up or down by 1. Set the Value property to 0 initially. Our code will change it as needed.

You can adjust the TabIndex and TabStop properties of the controls if you wish. That can make it easier for the user to use your app. 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


Our Windows forms app is similar to the console app on car objects found in the Objects and Classes section of the Software Design Using C++ web pages. That console app used a product class and derived from it a car class via inheritance. We can reuse this code, but will make minor adjustments, such as leaving out the Print function. Instead of printing an object's data to the screen we will retrieve it using the various get functions and then display it on our form. The old console app held the data on the cars in an array of Car objects. We will change that slightly, using instead an array of pointers to Car objects.

In Solution Explorer right click on "Header Files" under your showcars project. Select Add, Add New Item. Click on the Header File icon and fill in product2.h as the name of the new file. Once you have your new, blank header file, copy in the needed code from this version of the product2.h file.

Then use Solution Explorer to right click on "Source Files" for your project. Select Add, Add New Item. Click on the C++ File icon and fill in product2.cpp as the file name. Copy in the needed code from this version of the product2.cpp file. Note that it has been modified to include the stdafx.h header file used by Visual Studio to give access to commonly used header files.

Similarly, add this version of the cars.h header file to your project. Also add this version of the cars.cpp file to your project.

Change the top of your Form1.h code to look like the following. All this does is to include the cars.h header file, set up a constant for the number of cars, and create a type for an array of pointers to Car objects.


#pragma once

// Additions for ShowCars app:
#include "cars.h"

const int MaxCars = 8;

typedef Car * CarArrayPtrType[MaxCars];  // Type for an array of pointers to Car objects.
// End of additions

In the same file, find the beginning of the Form1 class and add the 2 sections of additions shown below. The first section sets up private fields in our form for 2 arrays of pointers to Car objects. The first array, CarPtrs is used for the original car data. The second one, ResultsPtrs, is used to point to the specific Car objects whose prices fit the desired range. The NumResults field will be used to indicate how many cars fit this desired range. The second section of additions below is inside the constructor for the Form1 class. It creates the eight car objects and has the CarPtrs entries point to these objects. NumResults is initialized to zero and the AddMyScrollEventHandler function, described later, is called.


public __gc class Form1 : public System::Windows::Forms::Form
{	
// Additions for ShowCars app:
private:
    CarArrayPtrType CarPtrs, ResultsPtrs;
    int NumResults;
// End of additions:

public:
    Form1(void)
    {
        InitializeComponent();
        // Added for ShowCars app:
        CarPtrs[0] = new Car("Ford Mustang", 1995, 2856.99, 2, 150);
        CarPtrs[1] = new Car("Chevy Nova", 1977, 620.88, 4, 100);
        CarPtrs[2] = new Car("Chevy Blazer", 1994, 1350.99, 2, 110);
        CarPtrs[3] = new Car("Ford Bronco", 1998, 3169.99, 2, 105);
        CarPtrs[4] = new Car("Honda Accord GX", 2005, 19200.00, 4, 166);
        CarPtrs[5] = new Car("Toyota Avalon XLS", 2006, 32125.00, 4, 268);
        CarPtrs[6] = new Car("Toyota Camry XLE", 2006, 24500.00, 4, 158);
        CarPtrs[7] = new Car("Toyota Prius", 2006, 21933.00, 4, 76);
        NumResults = 0;
        AddMyScrollEventHandler();
        // End of additions
    }

Since we have created new objects, we need some way to get rid of them when our app is finished. The right way to handle this might be to turn our Car class into a so-called managed class. Or, if you want to leave our unmanaged class alone, you could inherit a new class from the Form1 class and override its Dispose method so as to release the memory used by the Car objects. You might want to read the MSDN article on Cleaning Up Unmanaged Resources. All of this is more complicated than what we want to do here, so for a quick way to get rid of the Car objects, let's just add the code to free them up to the Dispose method of the existing Form1 class. Here is how the Dispose method inside Form1.h would then look:


void Dispose(Boolean disposing)
{
    // Addition for ShowCars app:
    int k;
    for (k = 0; k < MaxCars; k++)
        delete CarPtrs[k];
    // End of addition

    if (disposing && components)
        {
        components->Dispose();
        }

    __super::Dispose(disposing);
}

Code for the Click Handler


In Design View for Form1.h, double click on the button in order to generate the outline for the click handler. Then in Form1.h, change it to a function prototype. That is, make it look like the following. Note the semicolon on the end of the function prototype line. It is best to include the comments as they will remind you (and inform others) about what this function does.


private:
    // Click handler for FindButton:
    // Finds data on all cars that fit the indicated price range.
    // NumResults is set to the number of such matches while
    // the initial entries of the ResultsPtrs array are set to point to
    // the Car objects that match the price range.
    // This function also causes the first match (if any) to be displayed on the form.
    System::Void FindButton_Click(System::Object * sender, System::EventArgs * e);

We then place the code for this click handler at the bottom of the Form1.cpp file. Make it match the following:


// Click handler for FindButton.
// Finds data on all cars that fit the indicated price range.
// NumResults is set to the number of such matches while
// the initial entries of the ResultsPtrs array are set to point to
// the Car objects that match the price range.
// This function also causes the first match (if any) to be displayed on the form.
System::Void Form1::FindButton_Click(System::Object * sender, System::EventArgs * e)
    {
    int k;
    double UpperBound, LowerBound, Price;
    
    FindErrorLabel->Visible = false;
    NumResults = 0;

    try
        {
        LowerBound = Convert::ToDouble(MinTextBox->Text);
        UpperBound = Convert::ToDouble(MaxTextBox->Text);

        for (k = 0; k < MaxCars; k++)
            {
            Price = CarPtrs[k]->GetPrice();
            if ((LowerBound <= Price) && (Price <= UpperBound))
                ResultsPtrs[NumResults++] = CarPtrs[k];
            }

        if (NumResults == 0)   // No matches were found.
            DisplayResults(-1);
        else
            {
            HorizontalScrollBar->Maximum = NumResults + 1;
            DisplayResults(HorizontalScrollBar->Value);
            }
        }
    catch (...)
        {
        FindErrorLabel->Visible = true;
        }
    }

The above code begins by extracting from the input text boxes the bottom and top numbers for the user's desired price range. These two numbers are called LowerBound and UpperBound. Next, the function loops through all of the Car objects, checking the price of each to see if it fits the desired range. If so, the next unused slot in the ResultsPtrs array is set to point to the matching Car object. When this loop ends, NumResults tells us how many Car objects fit the price range (if any). In the case where there are no matches, we call the DisplayResults function with -1 as the parameter. We will look at the DisplayResults function below, but normally it is called with a parameter value of 0, 1, etc., where the parameter value is the index in the ResultsPtrs array for the Car object that we wish to display on the form. The -1 is a sign to the function to instead display a "no results" type of message.

As long as one or more matches were found, the above code puts an appropriate value into the Maximum property of our scroll bar. This is so that we can scroll through Value 0, 1, 2, etc. to display matching car 0, 1, 2, etc. Notice that in this case we call DisplayResults using the Value property of the scroll bar as the parameter. That is how we pick out the correct car, by using this Value as an index.

Finally, note that the above code uses exception handling. If anything goes wrong (such as having something in an input text box that cannot be converted to a number) then we make the red error label to be visible. Once you have built your app, try this out. Put something that is not a valid number into the text box for the minimum or maximum price and see what happens.

Save all of your files before going on.

Adding the Display Function


Next we add a helping function. You can use Class View to assist with this or you can manually add everything yourself. See our previous example on how to add a function with Class View. In the present case, find the Form1 class under your showcars class in Class View. Right click on the Form1 class and Select Add, Add Function. Name the new function DisplayResults, give it one parameter of type int, and have it return nothing (void). For type of access, use private. That is, this will be a private function of the class.

However you create your new function, the prototype should appear at the bottom of your Form1.h file and should look like this:


// This function displays on the form the data for the Car object indicated
// by index, that is, the Car object pointed to by ResultsPtrs[index].
// If index is -1, a no results message is shown.
// If index is too large, the last Car object (pointed to by the valid entries
// of ResultsPtrs) is shown.
void DisplayResults(int index);

In Form1.cpp, make the code for this new function match the following:


// This function displays on the form the data for the Car object indicated
// by index, that is, the Car object pointed to by ResultsPtrs[index].
// If index is -1, a no results message is shown.
// If index is too large, the last Car object (pointed to by the valid entries
// of ResultsPtrs) is shown.
void showcars::Form1::DisplayResults(int index)
    {
    ShortString Temp;
    int LastIndex;

    // Clear out any old results:
    TypeTextBox->Clear();
    YearTextBox->Clear();
    DoorsTextBox->Clear();
    HorsepowerTextBox->Clear();
    PriceTextBox->Clear();

    // Display the data:
    if (index == -1)
        PriceTextBox->Text = S"None";
    else if ((index > -1) && (index < NumResults))
        {
        ResultsPtrs[index]->GetName(Temp);
        TypeTextBox->Text = Convert::ToString(Temp);
        YearTextBox->Text = Convert::ToString(ResultsPtrs[index]->GetYear());
        DoorsTextBox->Text = Convert::ToString(ResultsPtrs[index]->GetDoors());
        HorsepowerTextBox->Text = Convert::ToString(ResultsPtrs[index]->GetHorsepower());
        // You might want to format the price to 2 decimal places, the following does not bother:
        PriceTextBox->Text = Convert::ToString(ResultsPtrs[index]->GetPrice());
        }
    else   // Display the last match:
        {
        LastIndex = NumResults - 1;
        ResultsPtrs[LastIndex]->GetName(Temp);
        TypeTextBox->Text = Convert::ToString(Temp);
        YearTextBox->Text = Convert::ToString(ResultsPtrs[LastIndex]->GetYear());
        DoorsTextBox->Text = Convert::ToString(ResultsPtrs[LastIndex]->GetDoors());
        HorsepowerTextBox->Text = Convert::ToString(ResultsPtrs[LastIndex]->GetHorsepower());
        // You might want to format the price to 2 decimal places, the following does not bother::
        PriceTextBox->Text = Convert::ToString(ResultsPtrs[LastIndex]->GetPrice());
        }
    }

This DisplayResults function begins by clearing out any old data sitting in the text boxes where we show our results. Then it checks the parameter value with which it was called. If this is -1, it simply puts the string "None" into the text box normally used for the price of the matching car. The other output boxes are left empty.

In the normal case, where the value of the index parameter is between -1 and the number of matches, NumResults (not including equality on either end), the code displays the data for the Car object pointed to by the entry at index index in the ResultsPtrs array. To retrieve this data we use the various "Get" functions supplied by the Car class. The returned values are then placed into the appropriate text boxes for display.

If somehow the value of index is larger than it should be, the above function simply displays the last valid matching car data. This section of code should never be reached, but it is put here just in case. For example, we don't want strange things to happen if the scroll bar somehow generates a larger than expected Value which then gets passed to the DisplayResults function.

Save all of your files before going on.

Adding a ScrollEventHandler


We need some code that tells the app what to do when the user clicks on the scroll bar. At the bottom of Form1.h, but before the closing } for the namespace and the closing }& for the class, fill in the following:


private:
    void AddMyScrollEventHandler()
        {
        // Add event handlers for the OnScroll event.
        HorizontalScrollBar->Scroll += new ScrollEventHandler(this, 
        &Form1::HorizontalScrollBar_Scroll);
         }

     // Create the Scroll event handler.
     void HorizontalScrollBar_Scroll(Object * sender, ScrollEventArgs * e)
         {
         // Display the new data:
         DisplayResults(HorizontalScrollBar->Value);
         }

You might, at this point, look up ScrollEventHandler by using Help, Search in Visual Studio. Be prepared for lots of technical information, however! Roughly, the AddMyScrollEventHandler function, which we earlier saw was called in the constructor for class Form1, adds a new event handler for the "OnScroll" event. The new handler is the HorizontalScrollBar_Scroll function. As you can see from the code for this function, all it does is to call DisplayResults using as the actual parameter the current value of Value from the scroll bar control. As we already know, the DisplayResults function then displays the data for the car indicated by this parameter value.

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