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

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

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

Applications Using Windows Forms

System::Void euroValue_Click(System::Object^ sender, System::EventArgs^ e)

{

Button^ button = safe_cast<Button^>(sender);

array<Button^>^ buttons = {euroValue1, euroValue2, euroValue3,

euroValue4, euroValue5 }; SetNewValue(button, buttons, euroUserMinimum, euroUserMaximum);

}

This works exactly the same as the handler for the Lotto buttons. The array contains the handles to the five buttons in the values group, and the SetNewValue() function does the rest. If you open the Properties window for each of the remaining four buttons in the group, you can select this function to respond to the Click event for each of them. Be sure you select euroValue_Click and not lottoValue_Click!

Follow the same procedure for the Stars buttons on the Euromillions tab. You can implement the handler as:

System::Void euroStar_Click(System::Object^ sender, System::EventArgs^ e)

{

Button^ button = safe_cast<Button^>(sender); array<Button^>^ buttons = { euroStar1, euroStar2 };

SetNewValue(button, buttons, euroStarsUserMinimum, euroStarsUserMaximum);

}

Set the handler for the Click event for the second button to be euro_StarClick and you are done. If you recompile the example, you should be able to generate a new value for any button on either tab just by clicking it. The last piece to complete the example is to allow the user to enter a value for a button.

Responding to the Context Menu

Right-clicking a button brings up a context menu with a single menu item, Choose. When the user clicks this item, the program should display a dialog box that allowed a suitable value to be entered. Click the name of the context menu in the Design tab for Form1 and then double-click the menu item to create the Click event handler.

The first problem is to determine which group of buttons was clicked to cause the event. Each group on buttons is in its own GroupBox control, and the GroupBox class has a Controls property that returns a reference to an object of type Control::ControlCollection that represents the collection of controls in the group box. The Control::ControlCollection class defines the Contains() function that returns true if the control that you pass as the argument is within the collection and false otherwise. Thus you have a way to determine to which group of buttons the button causing the Click event belongs. An outline implementation of the event handler looks like this:

System::Void chooseValue_Click(System::Object^ sender, System::EventArgs^ e)

{

// Get the button that was clicked for the context menu, then...

if(lottoValues->Controls->Contains(theButton))

{

// the button is from the lotto group...

}

else if(euroValues->Controls->Contains(theButton))

{

1079

Chapter 21

// The button is in the Values group...

}

else if(euroStars->Controls->Contains(theButton))

{

// The button is in the Stars group...

}

}

That sorts out which group of buttons is involved, at least in principle. But there’s still a bit of a problem — how do you find out which button was right-clicked to open the context menu?

The chooseValue_Click() handler is called when the Choose menu item is clicked, so the sender parameter for the handler identifies the menu item, not the button. You need a handler that responds to the original click on the button and you can create this by double-clicking buttonContextMenu in the Design pane for Form1. You can complete the code for the handler function that is created like this:

System::Void buttonContextMenu_Opening(System::Object^ sender, System::ComponentModel::CancelEventArgs^ e)

{

contextButton = safe_cast<Button^>(buttonContextMenu->SourceControl);

}

This casts the sender handle to type Button^ and stores it in the contextButton member of the Form1 class. Because in this case the event is for the context menu, the sender parameter identifies the component that was clicked to display it. Of course, you have yet to add the contextButton variable as a private member of the Form1 class:

private:

Button^ contextButton; // Button that was right-clicked for context menu

All you need to do now is figure out what to do next.

The Logic for Dealing with the Choose Menu Item

The process for responding to the Choose item being clicked can be the same whichever group of buttons is involved, and it could work something like the following:

1.Display a dialog box to allow a value to be entered.

2.Check that the value is valid — that is, within range and different from the other buttons.

3.Display a message box if the value is not valid and allow the entry to be retired or the dialog operation to be cancelled.

4.If the value is valid, update the button that was right-clicked with the new value.

The first step in implementing this process is to create a new dialog form.

Creating the Dialog Form

Press Ctrl+Shift+A to display the Add New Item dialog box; then select UI as the category and Windows Form as the template. Enter the name as UserValueDialog and click the Add button. You can

1080

Applications Using Windows Forms

now open the Properties window for the form by pressing F4 and setting the property values to make it a dialog box. Set the ControlBox, MinimizeBox, and MaximizeBox property values to False, and set the Text property to “User Value Input.”

Add OK and Cancel buttons to the dialog form as well as a Label control and a TextBox control, as shown in Figure 21-30.

Figure 21-30

Set the Text and (Name) property values for the OK button to OK, and the value for the DialogResult property should be set to OK. The values for the Text, (Name), and DialogResult properties for the Cancel button should be Cancel. Set the value of the (Name) property for the TextBox control to be textBox and the value for the TextAlign property as Center. The (Name) property for the Label control can be label, and the Text property can be anything you like because you’ll change this in the code to suit the circumstances.

You can now display the properties for the dialog form once again and set the values for the

AcceptButton and CancelButton properties to OK and Cancel, respectively.

Developing the Dialog Class

The value entered in the TextBox control must be available to the Form1 object, so add a property to the

UserValueDialog class to store it:

public:

property int Value;

This is an example of a trivial scalar property so get() and set() functions are supplied by default.

The dialog object needs to know what the limits are for the value because the handler for the OK button in the dialog class is verifying that the value is legal. For the same reason, the dialog object needs to know what the current values on the buttons are to ensure they are not duplicated. You could add three further property members to the UserValueDialog class to store the data:

1081

Chapter 21

public:

property int LowerLimit;

property

int UpperLimit;

 

property

array<int>^ Values;

// Current button values

The Form1 object needs to be able to change the value of the Text property for the label control, depending on the limits in effect for button values when the dialog box is displayed; you can add a public member function to the UserValidDialog class to do this:

public:

void SetLabelText(int lower, int upper)

{

label->Text = L”Enter your value between “ + lower +L” and “ + upper;

}

You could conceivably pick up the limits from the properties in the dialog object, but this would require that the properties were always set first. By using parameters for the limits you remove this dependency.

You can create the dialog object in the Form1 class constructor, but you’ll need to add a private Form1 class member to store the handle:

private: UserValueDialog^ userValueDialog;

You’ll also need an #include directive for UserValueDialog.h in the Form1.h header file.

Adding the following line to the constructor creates the dialog object:

userValueDialog = gcnew UserValueDialog;

If you double-click the OK button in the UserValueDialog form, you’ll create the Click event handler for the button. This function retrieves the value entered in the TextBox control, and checks that the value is within the limits and is different from the current set of values. If the value is not valid for any reason, the function displays a message box. Here’s how you implement that:

System::Void OK_Click(System::Object^ sender, System::EventArgs^ e)

{

::DialogResult result;

// Stores return value from Show()

if(String::IsNullOrEmpty(textBox->Text))

// Chheck for null or empty string

{

 

result = MessageBox::Show(this,

 

L”No input - enter a value.”,

L”Input Error”,

MessageBoxButtons::RetryCancel,

MessageBoxIcon::Error);

if(result == ::DialogResult::Retry)

//

If Retry button clicked

DialogResult = ::DialogResult::None;

//

...prevent dialog from closing...

else

//

...otherwise...

DialogResult = ::DialogResult::Cancel;// ...close the dialog.

return;

 

 

}

 

 

int value = Int32::Parse(textBox->Text);

// Get text box value

1082

 

Applications Using Windows Forms

 

 

bool valid = true;

// Indicator for valid entry

for each(int n in Values)

// Check input against current values

if(value == n)

// If it’s the same...

{

 

valid = false;

// ...it is invalid.

break;

// Exit the loop

}

 

// Check limits and result of previous validity check if(!valid || value < LowerLimit || value > UpperLimit)

{

result = MessageBox::Show(this,

L”Input not valid.” +

L”Value must be from “ + LowerLimit + L” to “ + UpperLimit +

L”\nand must be different from existing values.”, L”Input Error”,

MessageBoxButtons::RetryCancel,

MessageBoxIcon::Error); if(result == ::DialogResult::Retry)

DialogResult = ::DialogResult::None; else

DialogResult = ::DialogResult::Cancel;

}

 

else

 

Value = value;

// Store the input in the property

}

A message box is displayed if the Text property for the text box is null or an empty string. The message box shows an error message and has Retry and Cancel buttons instead of OK and Cancel for a change. If Retry is clicked, the user wants another go at input so you prevent the dialog box from closing by setting its DialogResult property to ::DialogResult::None. The only other possibility is that the user clicked Cancel in the message box, in which case you set the DialogResult property for the dialog object to ::DialogResult::Cancel, which has the same effect as clicking the Cancel button for the dialog box.

The Text property for the TextBox control returns a handle of type String^. You convert this to an integer by passing the handle to the static Parse() function in the Int32 class. You compare the value from the text box with the elements from the values array that represent the current set of button values. The new value should be different from all of these, so if you find one that is the same, you set valid to false and exit the loop.

The condition for the if statement following the for each loop checks the value against the limits and the current value of valid by ORing the conditions together. If any of the three expressions are false, the condition is false, and you display a message box. This works in the same way as the previous message box and displays an error message and Retry and Cancel buttons. If the value does indeed turn out to be valid you store it in the Value property for the dialog object ready for retrieval by the event handler in the Form1 object that started the whole process off.

Handling the Click Event for the ChooseMenu

You are now going to complete the skeleton of the chooseValue_Click() handler function using the capabilities that you have added to the UserValueDialog class. The handle for the button that was

1083

Chapter 21

right-clicked is already stored in the contextButton member because the buttonContextMenu_ Opening() handler that you added earlier is executed first.

System::Void chooseValue_Click(System::Object^ sender, System::EventArgs^ e)

{

array<int>^ values;

//

Array to store

current button values

array<Button^>^ theButtons;

//

Handle to aray

of buttons

// Check if the button is in the lottoValues group box if(lottoValues->Controls->Contains(contextButton))

{

// the button is from the lotto group...

array<Button^>^ buttons = {lottoValue1, lottoValue2, lottoValue3, lottoValue4, lottoValue5, lottoValue6};

theButtons = buttons; // Store array handle at outer scope values = GetButtonValues(buttons); // Get array of button values

// Set up the dialog ready to be shown userValueDialog->Values = values = GetButtonValues(buttons); userValueDialog->LowerLimit = lottoUserMinimum; userValueDialog->UpperLimit = lottoUserMaximum;

userValueDialog->SetLabelText(lottoUserMinimum, lottoUserMaximum);

}

// Check if the button is in the euroValues group box else if(euroValues->Controls->Contains(contextButton))

{

// The button is in the Values group...

array<Button^>^ buttons = {euroValue1, euroValue2, euroValue3, euroValue4, euroValue5};

theButtons = buttons; // Store array handle at outer scope values = GetButtonValues(buttons); // Get array of button values

// Set up the dialog ready to be shown userValueDialog->Values = values; userValueDialog->LowerLimit = euroUserMinimum; userValueDialog->UpperLimit = euroUserMaximum;

userValueDialog->SetLabelText(euroUserMinimum, euroUserMaximum);

}

// Check if the button is in the euroStars group box else if(euroStars->Controls->Contains(contextButton))

{

// The button is in the Stars group...

 

 

array<Button^>^ buttons = { euroStar1,

euroStar2 };

 

theButtons = buttons;

//

Store array handle at outer scope

values = GetButtonValues(buttons); // Get array of

button values

// Set up the dialog ready to be shown

 

 

userValueDialog->Values = values;

 

 

 

userValueDialog->LowerLimit = euroStarsUserMinimum;

 

userValueDialog->UpperLimit = euroStarsUserMaximum;

 

userValueDialog->SetLabelText(euroStarsUserMinimum, euroStarsUserMaximum);

}

1084

Applications Using Windows Forms

// Display the dialog

if(userValueDialog->ShowDialog(this) == ::DialogResult::OK)

{

// Determine which button value should be replaced for(int i = 0 ; i<theButtons->Length ; i++)

if(contextButton == theButtons[i])

{

values[i] = userValueDialog->Value; break;

}

 

Array::Sort(values);

// Sort the values

// Set all the button values

for(int i = 0 ; i<theButtons->Length ; i++) theButtons[i]->Text = values[i].ToString();

}

}

You first define two array variables, one to hold the buttons, the other to hold the button values. You need to declare these here because these arrays are created within one or other of the if statement blocks and you’ll want to access them outside the if blocks.

The first three if statements determine which group box contains the button that was right-clicked to open the context menu. The processes within the three if blocks are essentially the same, but the arrays created will be different. The array of buttons is created from the variables holding the handles to whichever set of buttons the contextButton belongs. The array handle is then stored in theButtons to make it accessible in the outer scope. You then call a function you have yet to add, GetButtonValues(), that returns an array containing the integer values from the buttons. Finally, in the if block you set the three properties for the dialog box object and call its SetLabelText() function to set the label text according to the applicable limits. The contextButton has to belong to one of the three group boxes as these are the only buttons that have the context menu available.

When one or other of the if blocks has executed, you display the dialog box by calling its ShowDialog() function in the condition for the fourth if statement. If the ShowDialog() function returns ::DialogResult::OK, you execute the code in the if block. This first determines which button should have its value replaced by comparing contextButton handle with the handles in the theButtons array. As soon as you find a match, you replace the corresponding element in the values array with

the new value and exit the loop. After sorting the values, you update the Text property for each of the buttons in the theButtons array and you are done.

The implementation of the GetButtonValues() function in the Form1 class looks like this:

// Creates an array of button values from an array of buttons array<int>^ GetButtonValues(array<Button^>^ buttons)

{

array<int>^ values = gcnew array<int>(buttons->Length);

for(int i = 0 ; i<values->Length ; i++) values[i] = Int32::Parse(buttons[i]->Text);

return values;

}

1085

Chapter 21

Here you create an array of integer values the same length as the array of button handles that is passed as the argument. You then populate the values array with the int equivalents of the string returned by the Text properties of the buttons, and return the handle to the values array.

After compiling the project once more, you should have a fully functional application. You can generate lottery entries for a variety of lotteries with the range of values constrained or not. You can also elect to generate new random individual values in an entry or choose your own. It has always worked but never won for me; of course, working is a measure of success.

Summar y

In this chapter, you assembled a Windows Form application that uses the controls that you are most likely to need in the majority of programs. It should be apparent that Windows Forms programs are geared exclusively to using the Design capability. All the code for a class goes into the class definition, so with a very complex form the class is many lines of code. With a production application the code consists of a number of large classes that are rather unstructured, and difficult to modify and maintain at the code level. You should therefore always use the Design capability and the Properties window to expedite changes wherever you can and whenever you need to access the code, use Class View to find your way around.

The key points to keep in mind from this chapter include:

An application window is a form, and a form is defined by a class derived from the

System::Form class.

A dialog window is a form that has its FormBorderStyle property value set to FixedDialog.

A dialog box can be created as a modal dialog by calling its ShowDialog() function or as a modeless dialog by calling its Show() function.

You can control whether or not a dialog box closes by setting the DialogResult property value for the dialog object.

A ComboBox control combines the capabilities of a ListBox and a TextBox and allows selection of an item from a list or a new item to be entered from the keyboard.

A NumericUpDown control allows the entry of numeric data by stepping through values within a given range with a given step increment.

You can add the definition of a Click event handler for a control by double-clicking the control in the Form Design tab.

You can specify an existing function to be a handler for a given event for a control through the Properties window. Clicking the Events button in the Properties window displays the list of events for a control.

You should only change the names of automatically generated class members through the Properties window — not directly using the Code editor.

1086

Applications Using Windows Forms

Exercises

You can download the source code for the examples in the book and the solutions to the following exercises from http://www.wrox.com.

1.Modify Ex21_01 so that it displays a dialog box that you created as a dialog form when the Help | About menu item is clicked.

2.Modify Ex21_01 so that the dialog box that displays for the Choose context menu item uses a ListBox control instead of the text box and displays the complete set of legal values that can be chosen.

3.Investigate the properties and functions available for the WebBrowser control and modify Ex21_01 to allow a URL to be entered through a TextBox so that the WebBrowser control displays the page at the URL that was entered.

1087