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

Visual CSharp .NET Programming (2002) [eng]

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

}

...

}

The first empty (default) constructor shown in Listing 8.4 is what is invoked if you instantiate with a statement like

Tribe tr = new Tribe();

The other two constructors are invoked if the instantiation matches their signature. For example, to invoke the third constructor shown, you could instantiate a Tribe object like this:

Tribe tr = new Tribe("Gondor", "Numenorians", 1064, System.Drawing.Color.Green, System.Drawing.Color.Gold, 5, 7, 1020);

It's really neat that, as one types in the code that instantiates a class in the Code Editor, dropdown arrows display the signature of each possible constructor.

Class Variables, Fields, and Properties

Class fields are simply public variables belonging to a class-also referred to as member variables-meaning that they are declared at the class level. (A variable declared inside a method is always private to the method and cannot be declared using the public keyword.)

Like other instance class members, fields can be declared with access modifiers. A variable declared using private can only be accessed within an instance and is intended to be used internally, while a public member variable (a field) can be accessed outside the instance.

For example, in the GroupOfPeople class, the lines

private Tribe tr; private CityState cs;

private Civilization civil;

declare private instance variables, while the lines

public bool isAlive = false; public string Name;

public frmTribe frmTribe;

all declare public instance variables-also known as class fields.

Properties are more elegant versions of class variables. As you've previously seen, properties can be accessed externally to a class in the same way as a class field. However, internally, classes are implemented using the special methods known as get and set accessors. Here's a simple example from the GroupOfPeople class:

private long m_Population = 0; public long Population {

get {

return m_Population;

}

set {

m_Population = value;

}

}

The get accessor of the property uses the return keyword to return a value when the property is accessed. The set accessor uses the value keyword to assign a value when the property is set. An internal class variable, m_Population, is used to keep track of the property's value between use of the accessors.

Note By convention, the private variables that track property values are named with an m_ prefix.

The property mechanism can be used to validate values, calculate changes to values, and access internal object state information. Properties can be made read-only (by omitting the set accessor) or write-only (by omitting the get accessor). In this way, they serve as an effective object-oriented programming mechanism, by encapsulating the implementation details.

For example, in the Tribe class in the Guns, Germs, and Steel application, the SpeciesAnimals property validates a value passed to it to make sure that it does not exceed the allowable value set in a global constant field before updating the property value by setting the private member variable:

private int m_SpeciesAnimals = 0;

...

public int SpeciesAnimals { get {

return m_SpeciesAnimals;

}

set {

if (value <= Globals.totalAnimals) m_SpeciesAnimals = value;

}

}

Whose Form Is It?

And, in whose instance am I, anyway? These turn out to be not entirely rhetorical questions. As we've seen in this chapter, all code in C# is within classes. Furthermore, applications start running by invoking an object instance of a class, e.g.:

Application.Run(new Form1());

These facts lead to the natural question of how to communicate references and values between class instances. Since Windows forms are wholly encapsulated as individual classes, communicating between instances of different form class objects is something that must be done in any multiform Windows application.

Note This issue was essentially papered over in VB6 (and earlier versions of VB). In VB6, you can invoke a form member as though the form were a static class without formally creating a class instance-so inter-form communication is a non-issue. VB6 programmers who are new to .NET should pay special attention to this section.

One way of dealing with this is to use public static variables to pass references and values back and forth-since these variables can be accessed without instantiation from any class. Creating a class module for static public variables and constants was explained earlier in this chapter in the 'Static Members Example.'

However, for several reasons, this is probably not the best way to communicate between class instances. (Extensive use of static public variables in this way is unnecessarily consumptive of resources. It also violates the encapsulation features of good OOP design.)

In particular, there are better options for managing Windows forms applications intracommunication. Let's look at a couple of possibilities.

Using a Property to Pass a Form Instance

Properties are ideal for holding a reference to a class instance in a second form class. To see how this works, use the Add New Item dialog to add a new form to a Windows application project. If you didn't change their default names, you now have two forms-and classes-in the project named Form1 and Form2.

Before we get started, use the Properties window to change the value of the Text property of Form1 to something like "The first form is very happy!". We'll be accessing this value from the Form2 instance in our demonstration.

Next, add a property named OtherForm of type Form to the Form2 class:

private Form m_OtherForm = null; public Form OtherForm {

get {

return m_OtherForm;

}

set {

m_OtherForm = value;

}

}

Note The OtherForm property is actually of type System.Windows.Forms.Form, but since the System.Windows.Forms namespace is included by default in the Form2 class in a using directive, we don't have to fully qualify the Form type. Form1 (and Form2) inherit from the general Form class, so Form, as the base type, will do to store a reference to either derived class-or to any class derived from Form.

Next, back in Form1, add code to a Button click event that declares and instantiates an instance of the Form2 class named form2:

Form2 form2 = new Form2();

Use the this keyword to store a reference to the current instance of Form1 in Form2's OtherForm property:

form2.OtherForm = this;

Finally, use the Show method form2 instance of the Form2 class to display the second form:

form2.Show();

Back in form2, we can add a click procedure that demonstrates that we can access the instance members of the object based on Form1, by changing the Text in the caption bar of the Form2 instance to include the Text value in the Form1 instance:

private void btnWho_Click(object sender, System.EventArgs e) { this.Text = "Form1 was called and said, \" " + OtherForm.Text + "\"";

}

If you run this code, display the second form, and click the button on it, you'll see that methods belonging to the second form instance can, indeed, access the instance members of the first form (Figure 8.10). The complete code in this example is shown in Listing 8.5.

Figure 8.10: A reference to the instance of the Form1 class can be stored in a Form2 class instance property.

Listing 8.5: Using a Property to Pass a Reference to a Form Instance

//Form1

...

private void btnShow_Click(object sender, System.EventArgs e) { Form2 form2 = new Form2();

form2.OtherForm = this; form2.Show();

}

...

//Form2

...

private Form m_OtherForm = null;

public Form OtherForm { get {

return m_OtherForm;

}

set {

m_OtherForm = value;

}

}

private void btnWho_Click(object sender, System.EventArgs e) { this.Text = "Form1 was called and said, \" " + OtherForm.Text + "\"";

}

...

Using a Reference to a Form Instance

A different approach is to use a reference to another form class instance in the current instance. Here's how this might work.

Go back to the example shown in the previous section, and use the Add New Item dialog to add a new form class to the project (by default, it will be called Form3).

In Form3, add OK and Cancel buttons and a TextBox. The TextBox will be used for text that will be retrieved in a TextBox on Form1 when the user presses OK. Add a declaration for a public field to hold the string at the class level:

public string theText;

In the OK Button's click event, assign the TextBox's Text property to the field. Next, set the object instance's DialogResult property to the enumeration value DialogResult.OK. Finally, close the instance:

private void btnOK_Click(object sender, System.EventArgs e) { theText = this.txtSendBack.Text;

this.DialogResult = DialogResult.OK; this.Close();

}

In Form1, add a TextBox to receive the text from the Form3 instance, add a Button to display the instance, and wire the logic. Here's the click event code that declares and instantiates an instance of Form3 and retrieves the text when the user clicks OK:

private void btnShow3_Click(object sender, System.EventArgs e) { Form3 form3 = new Form3();

if (form3.ShowDialog(this) == DialogResult.OK){ this.txtGiveHere.Text = form3.theText;

}

form3.Dispose();

}

The ShowDialog Method

The ShowDialog method of a form instance displays the form modally, meaning that when the form is open, a user cannot switch to another form in the application and that the code below the ShowDialog statement doesn't get executed until the form is closed. (With a regular Show method, the code below the statement runs immediately.)

By including the current form instance as a parameter (with the this keyword) in the ShowDialog method call, the current instance becomes the owner of the Form3 instance. When one form is an owner of a second form, it means that the second form always appears "on top" of the first form, and that when the first form is minimized, so is the second form.

Warning It's a good idea to include a call to the Dispose method of the modal form instance. This is the rare case in the .NET Framework where you need to be somewhat concerned about explicit de-allocation of resources. When a modal form is closed via the button with the X at the top-right of the form window, it is actually hidden, not disposed of (marked as garbage)-so you need to call its Dispose method to mark it for collection. In production code, it probably makes sense to include the Dispose method in a Finally block, as explained in Chapter 6, to make sure that it gets run.

Run the project and click Show Form3. Next, enter some text in the TextBox in Form3 and click OK (Figure 8.11). The text that you entered in the TextBox in Form3 is now displayed in the original form.

Figure 8.11: If you enter text in the TextBox in the instance of Form3 (left) and click OK, the text will appear in the TextBox of the original form (right).

Listing 8.6 shows the code for this example.

Listing 8.6: Using a Reference to a Form Instance

//Form1

...

private void btnShow3_Click(object sender, System.EventArgs e) { Form3 form3 = new Form3();

if (form3.ShowDialog(this) == DialogResult.OK){ this.txtGiveHere.Text = form3.theText;

}

form3.Dispose();

}

...

//Form3

...

public string theText;

private void btnOK_Click(object sender, System.EventArgs e) { theText = this.txtSendBack.Text;

this.DialogResult = DialogResult.OK; this.Close();

}

private void btnCancel_Click(object sender, System.EventArgs e) { this.Close();

}

...

Passing a Form Instance to a Method

It's also easy-and very useful-to pass form instances to instance methods. In this section, I'll show you two examples from the Guns, Germs, and Steel application.

In the first example, a modal dialog is opened from a click event in the application's primary form, using the technique just explained:

private void btnAddaTribe_Click(object sender, System.EventArgs e) { frmSettings dlg = new frmSettings();

if (dlg.ShowDialog(this) == DialogResult.OK){

...

Within the modal dialog, in the OK button's click event before the DialogResult property is set, the user's choices are stored in member fields and properties, as explained earlier in this chapter:

private void btnOK_Click(object sender, System.EventArgs e) { m_Name = txtName.Text;

m_Text = txtText.Text;

...

this.DialogResult = DialogResult.OK; this.Close();

}

Back in the Form1 code, the instance of frmSettings is passed to the CreateNewTribe instance method of the GroupOfPeople class:

gop.CreateNewTribe (dlg);

Within the CreateNewTribe method, the instance of frmSettings is used to populate member values:

public void CreateNewTribe(frmSettings dlg) { tr = new Tribe();

this.Name = tr.Name = dlg.tribeName; this.Text = tr.Text = dlg.tribeText;

...

}

The second example centers around the fact that I used MDI child forms to display the changing values of population and so forth for each culture. (MDI applications are explained in Chapter 4.)

For this to work, I needed to be able to assign the reference to the instance of the MDI parent form (Form1) to the MdiParent property of each child form. But this couldn't be done from Form1 itself because of the facade functionality of the GroupOfPeople class (in fact, it had to be done from an instance method within that class).

So I passed the current instance to the instance method:

gop.CreateForm (this);

The CreateForm method was declared with a Form as its argument in a parameter named parentForm:

public void CreateForm (System.Windows.Forms.Form parentForm)

Within the method, I created a new instance of frmTribe, the MDI child form:

frmTribe = new frmTribe();

The newly instantiated frmTribe's MdiParent property was assigned to the instance value of the main form passed into the method:

frmTribe.MdiParent = parentForm;

And then I could go merrily about populating the form:

frmTribe.BackColor = tr.TribalColor; frmTribe.ForeColor = tr.TextColor;

...

Listing 8.7 shows these two examples of passing form instances to methods.

Listing 8.7: Passing Form Instances to Methods

//Form1

...

GroupOfPeople gop;

private void btnAddaTribe_Click(object sender, System.EventArgs e) { frmSettings dlg = new frmSettings();

if (dlg.ShowDialog(this) == DialogResult.OK){ gop = new GroupOfPeople(); gop.CreateNewTribe (dlg);

gop.CreateForm (this);

...

}

dlg.Dispose();

}

...

//frmSettings

...

private void btnOK_Click(object sender, System.EventArgs e) { m_Name = txtName.Text;

m_Text = txtText.Text;

...

this.DialogResult = DialogResult.OK; this.Close();

}

...

// GroupOfPeople

...

public void CreateNewTribe(frmSettings dlg) { tr = new Tribe();

this.Name = tr.Name = dlg.tribeName; this.Text = tr.Text = dlg.tribeText;

...

}

public void CreateForm (System.Windows.Forms.Form parentForm){

frmTribe = new frmTribe(); frmTribe.MdiParent = parentForm; frmTribe.BackColor = tr.TribalColor; frmTribe.ForeColor = tr.TextColor;

...

}

...

Methods

A method is, of course, the class member that used to be called a function (if it returned a value) or a procedure (if it did not). In fact, in today's world, a method is sometimes referred to as a member function.

As you've already seen numerous times in this book, a method declaration specifies the return type of the method, followed by the method's identifier, followed by the method's typed parameter list within parentheses (the parentheses are required whether or not there are

any parameters). If the method does not return a value, it is said to return void. Using the void keyword rather than a return type specifies this. For example, the declaration of the CreateNewTribe method shown in Listing 8.7

public void CreateNewTribe(frmSettings dlg)

makes it clear that no return value is expected from the method.

Methods are usually declared using one of the member access modifiers described earlier, in Table 8.3, and can also be marked with one of the modifiers described in Table 8.4.

out, ref, and Arrays as Parameters

If a method parameter is marked with the out or ref keyword, the method refers to the same variable that was passed into the method. Any changes made to the parameter within the method will be reflected in that variable when control returns to the calling method. By default, method parameters do not behave this way. Changes made inside a method to a parameter declared without these keywords are not made to the variable when control is passed back to the calling method. (Of course, you can use an object reference passed to a method within the method to change the members of the referenced object. Since the object reference is already a reference, it doesn't need to be marked with out or ref as a value type would.)

The difference between out and ref is that an argument passed to a ref parameter must be definitely assigned, while that is not the case for out parameters.

Note If one version of a method has an out or ref parameter and another does not, that is sufficient for the method to be considered overloaded.

Arrays can be used as method parameters, and passed as arguments to methods, like any other type. When the array parameter is marked with out or ref, this is a useful way to encapsulate actions taken on arrays.

For example, the following method fills a string array marked with out:

private void FillArray (out string [] theArray){ theArray = new string[3] {"Madam ", "I'm", " Adam"};

}

Since out rather than ref was used, the array need not be initialized when the method is called:

string [] theArray; FillArray (out theArray);

The contents of the array can then be displayed in a TextBox, as shown in Figure 8.12:

for (int i=0; i <theArray.Length; i++){ txtGiveHere.Text += theArray[i];

}

Figure 8.12: Arrays passed to a method using out or ref retain value changes to the array elements in the calling method.

Listing 8.8 shows the method with the array parameter marked with out, and the method that calls it.

Listing 8.8: Passing an Array to a Method Using an out Parameter

private void FillArray (out string [] theArray){ theArray = new string[3] {"Madam ", "I'm", " Adam"};

}

private void btnArray_Click(object sender, System.EventArgs e) { string [] theArray; // Initialization not needed

FillArray (out theArray);

for (int i = 0; i < theArray.Length; i++){ txtGiveHere.Text += theArray[i];

}

}

Overloading Methods

Within a class, you can have more than one declaration for a given method name, provided that the method return type, and the types of the parameters of each same-named method, are different. (The same idea was discussed earlier in this chapter relating to class constructors, in the 'Constructors' section.)

The point of having multiple overloads for a method is that the programmer using the method (who may be the same programmer who wrote the overloads) can use whichever version is right for a particular circumstance. It's often the case that the most basic version of a method