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