Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]-1

.pdf
Скачиваний:
110
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Applications Using Windows Forms

The Limits > Upper and Limits > Lower menu items allow a more constrained range of values to be used for generating an entry. There need to be checks here, too; the range must be inside the range that is permitted for a given lottery, and the range must be wide enough to allow the required number of different values to be generated.

Finally the Help > About menu item should display a message box displaying information about the application.

The first step in implementing the application so that it operates as described previously is to add a context menu for the buttons that display lottery entry values. That’s surprisingly easy.

Adding a Context Menu

A context menu in a CLR Forms application is just another control. It’s the ContextMenuStrip control that is in the Menus & Toolbars group of controls in the Toolbox window. Click the ContextMenuStrip control and then click in the grey area at the bottom of the Editor window to add the control to the application. The context menu displays in the form below the existing menu. If you click in the first menu, you can insert the item name. You can enter &Choose for the text for menu item, which makes Alt+C the accelerator for the item. It would be better to have a more meaningful name for the context menu, so open the Properties window for the ContextMenuStrip control and change its (Name) property value to buttonContextMenu. You could also change the value for the (Name) property for the Choose menu item to chooseValue.

You can now enable the buttonContextMenu control as the context menu for each of the buttons on the two lottery entry tabs. To do this, set the value of the ContextMenuStrip property for each button to the name of the ContextMenuStrip control, buttonContextMenu. The name appears in the dropdown list in the value cell for the property so you just click it to set the value.

Creating Event Handlers

The process of building the GUI graphically using the Form Design capability has automatically generated code for the controls you have added. There is now a member of the Form1 class for every control you have added to the form, and code to initialize these has been added to the InitializeComponent() function. If you look at the function, you’ll see that it now contains a vast amount of code that sets properties for individual controls and assembles controls into their containers. Creating the GUI graphically is a breeze — you’ll need a few more brain cells ticking over to move the application on from there.

To make the program work the way that you want, you must create event handler functions for the events you want the application to recognize and process, and implement these functions to make user interactions with the GUI do what you want. Although the code to handle the events cannot be generated automatically, the IDE can still help by generating the skeleton event handler functions and registering them with the event delegates.

You can view the events for a control by clicking the events button in the Properties window for the control. This displays all the possible events for the control and you set name of the function that is to handle a particular event in the cell to the right of the event name. Of course, you identify handler functions only for the events you want to recognize and in most instances this is a very small proportion

of the possible events for a control. You’ll want to create handler functions for the Click event for the menu items for all the menu items and each of the buttons on the tabs. There’s a shortcut for creating a

1049

Chapter 21

handler function for a control to respond to a Click event; you just double-click the control in the Editor window and the code is created and displayed, and the function is registered as a handler for the event object. This creates a unique event handler for the control, but sometimes you’ll want to make a single function handle events of a given type for more than one control. In this case, the Properties window route is the way to do it.

Event Handlers for Menu Items

You can start by creating an event handler function for the Play menu item. Double-clicking the menu item creates the following handler function code:

private: System::Void playMenuItem_Click(System::Object^ sender, System::EventArgs^ e)

{

}

This is a skeleton handler containing no code in the body of the function. The first parameter is a handle referencing the control from which the event originated, and the second argument provides information related to the event. The type of the first argument when a handler is called corresponds to the type of the control that originated the event, and this is ToolStripMenuItem^ in this case because the handler function is called when the Play menu item is clicked; the menu item handle is stored in the playMenuItem member of the Form1 class and you can check out its type in the class definition. Similarly, the actual type of the second argument to a Click event handler depends on the type of the control.

The handler function name that is generated by default is playMenuItem_Click. You must not change the function name in the code. In fact, I recommend you do not change any names in the code that is automatically generated; always do it through the Properties window. If you don’t like the name that has been created for the event handler function, you can change it through the Click event property value in the Properties window for the control.

The name of the handler function has been registered with the event object by the following statement in the InitializeComponent() function:

this->playMenuItem->Click +=

gcnew System::EventHandler(this, &Form1::playMenuItem_Click);

This statement adds the function name to the Click delegate in the playMenuItem object that is a member of the Form1 class. You learned about delegates and how event handler functions were registered in Chapter 9.

You can add handlers for the Limits > Upper, the Limits > Lower and the Help > About menu items by double-clicking each of them. You can do the same for the Choose menu item in the context menu.

Adding Members to the Form1 Class

Before you start implementing the handlers for the menu items, there are some additional fields that you’ll need to store data relating to the constraints on the values for the lottery entries. You know how to add a new field to the Form1 class; it’s the way you have already used extensively to add members to a class. Switch to Class View, right-click the Form1 class, and select Add > Add Variable from the

1050

Applications Using Windows Forms

context menu. Alternatively, you can just add the code manually, but be sure you don’t intrude on the region of the class definition that is reserved for use by the Form Design operations. You’ll need to add the following variables as private members:

private:

 

int lottoValuesCount;

// Number of values in Lotto entry

int euroValuesCount;

// Number of values in Euromillions entry

int euroStarsCount;

// Number of stars in Euromillions entry

int lottoLowerLimit;

// Minimum value allowed in Lotto

int lottoUpperLimit;

// Maximum value allowed in Lotto

int lottoUserMinimum;

// Lower lotto range limit from user

int lottoUserMaximum;

// Upper lotto range limit from user

int euroLowerLimit;

// Minimum value allowed in Euromillions

int euroUpperLimit;

// Maximum value allowed in Euromillions

int euroStarsLowerLimit;

// Minimum stars value allowed in Euromillions

int euroStarsUpperLimit;

// Maximum stars value allowed in Euromillions

int euroUserMinimum;

// Lower euro range limit from user

int euroUserMaximum;

// Upper euro range limit from user

int euroStarsUserMinimum;

// Lower euro stars range limit from user

int euroStarsUserMaximum;

// Upper euro stars range limit from user

You also need to add a private field of type Random to the Form1 class:

 

 

Random^ random;

// Generates pseudo-random numbers

 

 

An object of type Random can generate pseudo-random values of various types. You’ll use this to generate the values for a lottery entry.

All of these fields must be initialized in the class constructor. Make sure the Form1 constructor definition looks similar to this:

public ref class Form1 : public System::Windows::Forms::Form

{

public:

Form1(void)

: lottoValuesCount(6), euroValuesCount(5), euroStarsCount(2), lottoLowerLimit(1),lottoUpperLimit(49),

lottoUserMinimum(lottoLowerLimit),lottoUserMaximum(lottoUpperLimit),

euroLowerLimit(1), euroUpperLimit(50), euroStarsLowerLimit(1),euroStarsUpperLimit(9), euroUserMinimum(euroLowerLimit),euroUserMaximum(euroUpperLimit), euroStarsUserMinimum(euroStarsLowerLimit),euroStarsUserMaximum(euroStarsUpperLimit)

{

InitializeComponent();

//

random = gcnew Random;

//

}

That’s all the new class data members you need for now, so back to event handling.

1051

Chapter 21

Handling the Play Menu Event

The playMenuItem_Click() handler should create a new set of values on the buttons for the tab that is currently visible. Recall earlier in this chapter that you set the (Name) property values for the tabs in the TabControl control as lottoTab and euroTab. If you look in the now extensive Form1 class definition, you’ll find two variables of type TabPage^ with these names. Objects of type TabPage have a Visible property of type bool that has the value true if the page is visible and false if it is not. This is just what you need to implement the handler for the Play menu item.

The outline logic for the handler can use the values of the Visible property for the tab pages like this:

private: System::Void playMenuItem_Click(System::Object^ sender, System::EventArgs^ e)

{

if(lottoTab->Visible)

{

// Generate and set values for Lotto entry

}

else if(euroTab->Visible)

{

// Generate and set values for Euromillions entry

}

}

If the Visible property for the lottoTab page is true, you create a new Lotto lottery entry, and if the Visible property for the euroTab page is true, you create a Euromillions lottery entry. Although both tabs cannot be visible at the same time, it’s as well to test positively for both tab pages because a user might click the Play menu item when the Web Page tab is visible.

The process for generating a set of values for the two lotteries have some things in common. For lotto you must generate six different random integers in a given range. For Euromillions you must generate five different integers within a given range and then generate two different integers within another range. A helper function to generate an arbitrary number of integers within a given range would be useful. You could define such a function something like this:

void GetValues(array<int>^ values, int min, int max)

{

// Fill the array with different random integers from min to max...

}

The Length property for the values array tells you how many values are to be generated. The calling function just needs to create an array of the appropriate size and pass it as the first argument to the GetValues() function. The second and third arguments specify the limits for the values to be generated.

Add the GetValues() function as a private member of the Form1 class and complete the definition like this:

void GetValues(array<int>^ values, int min, int max)

{

values[0] = random->Next(min, max);

// Generate first random value

// Generate remaining random values for(int i = 1 ; i<values->Length ; i++)

1052

Applications Using Windows Forms

{

for(;;)

// Loop until a valid value is found

{

 

//Generate random integer from min to max values[i] = random->Next(min, max);

//Check that its different from previous values

if(IsValid(values[i], values, i))

//

Check

against previous values...

break;

//

...it

is different so end loop

}

}

}

Any value within the range is fine for the first element. Subsequent values must be checked against the preceding values that have been generated, and the IsValid() function does this. Here’s how you can implement this function:

//Check whether number is different from values array elements

//at index positions less than indexLimit

bool IsValid(int number, array<int>^ values, int indexLimit)

{

for(int i = 0 ; i< indexLimit ; i++)

{

if(number == values[i]) return false;

}

return true;

}

Add this function as a private member of the Form1 class. It’s operation is simple: it checks the first argument against the elements in the array specified by the second argument that have index values less than the third argument and returns false if the first argument is equal to any of the array elements; otherwise, it returns true to indicate the first argument is valid.

The indefinite for loop in the GetValues() function continues to execute and generate new random values until the IsValid() function returns true, whereupon the inner loop ends and the next iteration of the outer for loop executes to find the next unique value.

You can now use the GetValues() function in the implementation of the Play menu Click event handler:

private: System::Void playMenuItem_Click(System::Object^ sender, System::EventArgs^ e)

{

array<int>^ values;

// Variable to store a handle to array of integers

if(lottoTab->Visible)

 

{

 

// Generate and set values for Lotto entry

values = gcnew array<int>(lottoValuesCount); // Create the array GetValues(values, lottoUserMinimum, lottoUserMaximum); // Generate values SetValues(values, lottoValues);

}

else if(euroTab->Visible)

1053

Chapter 21

{

// Generate and set values for Euromillions entry values = gcnew array<int>(euroValuesCount); GetValues(values, euroUserMinimum, euroUserMaximum); SetValues(values, euroValues);

values = gcnew array<int>(euroStarsCount);

GetValues(values, euroStarsUserMinimum, euroStarsUserMaximum); SetValues(values, euroStars);

}

}

The Lotto entry is created in three steps:

1.

2.

3.

Create the array to hold the values.

Generate the values by calling the GetValues() function.

Set the values as text on the buttons by calling the SetValues() function.

The sequence of steps is repeated twice for the Euromillions lottery entry: once for the set of five values and again for the set of two stars.

You can make use of the fact that the buttons are contained within a GroupBox control when implementing the SetValues() function. The Controls property for a GroupBox object returns a collection on all the controls that have been added to the object. The collection that is returned by the Controls property for a GroupBox itself has a default indexed property that accesses the controls in the collection. The collection is first-in last-out like a stack, so index values for the property accesses the controls in the reverse sequence from the sequence in which you added them to the group box. You can implement the SetValues() function as a private member of the Form1 class like this:

// Set values as text on buttons in a GroupBox control void SetValues(array<int>^ values, GroupBox^ groupBox)

{

Array::Sort(values); // Sort values in ascending sequence

int count = values->Length - 1;

for(int i = 0 ; i<groupBox->Controls->Count ; i++) safe_cast<Button^>(groupBox->Controls[i])->Text = values[count-i].ToString();

}

After sorting the array of values, you set the count variable as the index value for the last element in the array. The loop then stores the string representation of the values in the Text property for each Button control in reverse sequence. The expression groupBox->Controls[i] results in a handle of type Control^ that references the control corresponding to index i in the collection, and you cast this to type Button^ before accessing the Text property to set its value.

You don’t have to implement the capability that the SetValues() function provides using the GroupBox object. All the buttons are explicit members of the Form1 class, so you could take the pedestrian approach and access each of them directly to set the value for the Text property. You need at least two separate functions—one for the Lotto entry and one for the Euromillions entry, although the latter might more conveniently be split into two functions making three in all. Here’s how the function to set values for the Lotto entry might look:

1054

Applications Using Windows Forms

void SetNewValues(array<int>^ values)

{

Array::Sort(values);

lottoValue1->Text = values[0].ToString(); lottoValue2->Text = values[1].ToString();

lottoValue3->Text = values[2].ToString(); lottoValue4->Text = values[3].ToString(); lottoValue5->Text = values[4].ToString(); lottoValue6->Text = values[5].ToString();

}

This function uses the handle to each button to set its Text property. Functions to set the Text property for the Euromillions entry could be implemented in essentially the same way. You would then need to modify the playMenuItem_Click() handler function to call these functions to set the values.

You can now recompile Ex21_01 to see if it all works. If you managed to enter all the code without typos, you should be able to generate a Lotto entry like the one shown in Figure 21-18.

Figure 21-18

The numbers on the buttons appear in ascending sequence so they look neat and tidy. If they do not appear in sequence, it may be because you did not add the buttons to the GroupBox control in an orderly manner.

The program also produces an entry for the Euromillions lottery as in Figure 21-19.

Now that the basic functionality is there, it’s time to develop the application further.

1055

Chapter 21

Figure 21-19

Handling Events for the Limits Menu

There are three menu items on the Limits menu, and you’ll need a handler for the Click event for each. Double-click each of the menu items in turn to generate the handler functions and register each of them to handle the Click event.

The Click event handler for the Reset menu item is going to be the easiest to implement because all it has to do is set the user limits back to be the same as the limits imposed by the lottery that’s currently visible. The Click event handlers for the other two menu items are going to involve rather more work. You will have to make provision for the limit values to be entered somehow, and the obvious way to do this is to display a dialog box when the menu item is clicked. Clearly, the next step in developing the application requires an understanding of how you can create a dialog box.

Creating a Dialog Box

The Toolbox window provides a few standard dialog boxes; all of them are quite fancy, but none of them are suitable in this instance. Because you need something very specific in this program, you must create the dialog box yourself. A dialog box is just a form with its FormBorderStyle property value set to FixedDialog, so you’ll get a lot of help from the Form Designer in creating the dialog box.

Select Project >Add New Item on the main menu, or press Ctrl+Shift+A to display the dialog box shown in Figure 21-20.

Select the UI from the Categories: list in the right pane and the template as Windows Form and enter the name as shown in Figure 21-20. This is the dialog window you display when setting the upper or lower limits for the Lotto entry. You’ll create another dialog form for the Euromillions lottery entry later on. When you click the Add button, a new form is added to the project and is displayed in the Editor window. The class type for the new dialog box is the name you supplied, LottoLimitsDialog.

1056

Applications Using Windows Forms

Figure 21-20

Press F4 to display the Properties window for the new form. You can change the Text property value to “Set Limits for Lotto Values,” and this text is displayed in the title bar of dialog window; you can adjust the width of the window by dragging the right side until the title bar text is visible. You can also set the value for the StartPosition property in the Layout group of properties to Center Parent so the dialog window displays at the center of the parent form that displays it — this is the application window in the example. Because this is going to be a dialog box and not an application window, set the FormBorderStyle property value to FixedDialog. The dialog window should not be minimized or maximized by the user when it is displayed, so set the MinimizeBox and MaximizeBox properties in the Window Style group to False to remove the capability. A dialog box should be closed through the buttons that you’ll provide in the dialog window, so set the ControlBox property value to False to remove the control and system boxes from the title bar.

The next step is to add two buttons towards the bottom of the form; these are going to be the OK and Cancel buttons for the dialog box. Set the Text property value for the button to the left to be “OK” and the (Name) property to be lottoOK. You can also set the value for the DialogResult property in the Behavior group to OK. The values for the same properties for the right button should be “Cancel,” lottoCancel, and Cancel, respectively. The effect of setting the DialogResult property value for the buttons is that the value of the DialogResult property for the dialog box is set to the value corresponding to the value for the DialogResult property for the button that was clicked to close the dialog box. This gives you the possibility of testing programmatically for which button was used to close the dialog and execute different code depending on whether it was the OK button or the Cancel button.

Now that you have added the buttons to the dialog box, you can return to the dialog properties and set the values for the AcceptButton and CancelButton properties in the Misc property group to lottoOK and lottoCancel, respectively. This has the effect that pressing the Enter key while the dialog box is displayed clicked the OK button, and pressing the Esc key clicks the Cancel button.

1057

Chapter 21

You have more than one control in the dialog box that you could use to permit a limit value to be entered. You can use a ListBox control to enable the user to select from a list of possible values, so you can try that here. You should add two Label controls to the dialog box form with two ListBox controls alongside, as shown in Figure 21-21.

Figure 21-21

The (Name) property for the list boxes should be lottoLowerList and lottoUpperList for the top and bottom. As you see, I resized the ListBox controls to be the same height as the Label controls and a width sufficient to display a single limit value. I also changed the font Size property to 10 and the ScrollAlwaysVisible property to True. Make sure the SelectionMode property value is One for both list boxes, as you want to allow only one item to be selected from a list box at one time.

The GUI for the dialog box is complete, but to make it do what you want you are back in coding mode again. You can start with the code that populates the ListBox controls with limit values.

Adding a List to a ListBox

The list that a ListBox controls is a set of objects that are stored as handles of type Object^, so any kind of object can be stored in the list. In the example you want to store a set of integer limit values in each list box, and for the most part you are able to rely on autoboxing and unboxing to convert values of type int to and from objects of type Int32 whenever necessary. The Items property for a ListBox object returns a reference to a collection of the objects in the list box; this collection has an Add() method that adds an object that you pass as the argument to the list. A ListBox object has a large number of properties including the Enabled property that has the value true when the user can interact with the list box and the value false when interaction is to be inhibited.

The basic process for loading up the list for a list box is the same for both ListBox controls, so you could code a private function member of the LottoLimitsDialog class that is generalized to add a range of integers to a list box:

1058