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

Visual CSharp .NET Programming (2002) [eng]

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

Next, in the original form and at the form class level, declare a Form variable, then instantiate the new Form, a ColorDialog variable, a Color variable initialized to the quaintly named Color.AliceBlue-which happens to be the first value in the Color enumeration-and the SortedList:

Form2 Form2 = new Form2();

private System.Windows.Forms.ColorDialog colorDialog1; Color theColor = Color.AliceBlue;

SortedList sl = new SortedList();

Implement the Color common dialog-which also sets the background color of the button that invokes it-as follows:

private void btnColor_Click(object sender, System.EventArgs e) { colorDialog1.AllowFullOpen = true;

colorDialog1.AnyColor = true; colorDialog1.ShowDialog(); theColor = colorDialog1.Color; btnColor.BackColor = theColor;

}

Save the keys and values to the SortedList:

private void btnSave_Click(object sender, System.EventArgs e) { sl.Add("Text",txtText.Text);

sl.Add("BackColor", theColor);

}

The only thing remaining is to show the new form, retrieve the values, and use them to set the Form2 properties. Here's how this would look if one were using the index features of the SortedList:

private void btnGet_Click(object sender, System.EventArgs e) { Form2.Show();

Form2.BackColor = (Color) sl.GetByIndex(0); Form2.Text = sl.GetByIndex(1).ToString();

}

Note that the BackColor has to be cast to (Color) since-once again-it has been stored as simply an object. Similarly, the text value that has been saved as an object must be reconverted to a string.

Actually, I think it's much more fun to use keys and values than an index, and, anyhow, if you wanted to use an index, you'd have used an array in the first place. Here's how the procedure looks using the dictionary functionality of the SortedList:

private void btnGet_Click(object sender, System.EventArgs e) { Form2.Show();

Form2.Text = sl["Text"].ToString(); Form2.BackColor = (Color) sl["BackColor"];

}

Note once again that conversion and casting from object to string and Color is required.

It is time to run the project. Enter a text string in the TextBox, and click the Choose Color button to open the Color dialog (Figure 7.13).

Figure 7.13: The user enters text and a color via the common dialog; these choices are then saved with appropriate keys to a SortedList.

Next, save the changes to the SortedList. Finally, click the third button to display the new form with the properties retrieved from the SortedList (Figure 7.14).

Figure 7.14: User selections are retrieved by key from the SortedList and applied to a new form.

Listing 7.15: Using a SortedList to Store and Retrieve Text and a Color by Key

...

Form2 Form2 = new Form2();

private System.Windows.Forms.ColorDialog colorDialog1; Color theColor = Color.AliceBlue;

SortedList sl = new SortedList();

private void btnColor_Click(object sender, System.EventArgs e) { colorDialog1.AllowFullOpen = true;

colorDialog1.AnyColor = true; colorDialog1.ShowDialog(); theColor = colorDialog1.Color; btnColor.BackColor = theColor;

}

private void btnSave_Click(object sender, System.EventArgs e) { sl.Add("Text", txtText.Text);

sl.Add("BackColor", theColor);

}

private void btnGet_Click(object sender, System.EventArgs e) {

Form2.Show();

//Form2.Text = sl.GetByIndex(1).ToString(); Form2.Text = sl["Text"].ToString();

//Form2.BackColor = (Color) sl.GetByIndex(0); Form2.BackColor = (Color) sl["BackColor"];

}

...

Conclusion

This chapter explained how to work with arrays and other structures, such as collections, stacks, and queues, for storing groups of objects. This is not the most exciting topic in the universe, but it has great utility. Almost all programs use these structures. Choosing the proper structure for manipulation of your data, and implementing it correctly, will go a long way towards assuring the soundness of your projects.

Let's move to something that is truly exciting, and the heart of programming in C#: objectoriented programming

Chapter 8: The Life of the Object in C#

Overview

Guns, Germs, and Steel

The Facade pattern

Working with classes, class members, and access modifiers

Instance vs. static members

Constructors

Working with instances of forms

Working with methods, including overriding virtual methods

Five steps to creating an event

Overloading methods and operators

Polymorphism

Creating a custom collection

Is object-oriented programming 'much ado about nothing'? (Please don't hate me because I dare to ask the unthinkable.)

In the words of Bertrand Meyer, a distinguished OOP evangelist and creator of the Eiffel language, 'Object technology is at its core the combination of four ideas: a structuring method, a reliability discipline, an epistemological principle, and a classification technique.' In my opinion, these four ideas boil down as follows:

The 'structuring method' is the concept of classes used as the blueprint for objects.

The 'reliability discipline' is the concept of a contract between objects.

The 'epistemological principle' is the idea of describing classes by their interfaces and members.

The 'classification technique' creates a taxonomy for a given domain by following the chain of class inheritance within the domain.

I believe that object-oriented programming is much, much more than a fad and is here to stay as an important part of the way software developers work. But it's also not suitable as

the solution to every problem. (Remember the saying that, to a hammer, everything looks like a nail.)

In non-OOP procedural languages-such as FORTRAN and early versions of Visual Basic-one can create structures that simulate an object-oriented programming environment. Oddly enough, in a completely object-oriented environment such as C#, sometimes one wants to do the reverse: create procedural code and ignore (as much as possible) C#'s classes.

Object-oriented programming in C# has two faces: inward and outward. The inward face is the C# language and the .NET class libraries: no matter what you do, you are writing code that is placed in the context of an OOP system, so you may as well make good use of it as much as you can.

The outward face is projects that you create, in which you can use the taxonomy, structures, and mechanisms of C# as models for how your classes should be structured and what your code should look like (that is, if you are not creating a single procedural piece of code with a click event).

The chapter details the mechanics of creating OOP applications in C#.

Guns, Germs, and Steel

Jared Diamond's Pulitzer Prize-winning book Guns, Germs, and Steel (W.W. Norton, 1996) is an attempt to answer the broad question of why some human societies achieve material success and others do not. Diamond's answer-in short-is that the fate of human societies depends on the availability of various kinds of resources far more than on any cultural or racial differences between peoples. He summarizes his own book as follows:

History followed different courses for different peoples because of differences among peoples' environments, not because of biological differences among peoples themselves.

In his book, Diamond has created a wonderfully deterministic model for predicting the success of human populations. As I explain in more detail below, according to this model, the extent that certain environmental resources are present determine whether a given society will succeed and expand-if they are not, the society will never achieve the critical mass required for success and will be easy prey for assimilation by a more successful society.

At this point, you, dear reader, may well be wondering what a section on the historical causes of the fate of human societies is doing in a programming book. The answer is that I have adopted-and greatly simplified-the model proposed in Guns, Germs, and Steel to create the running example of object-oriented programming used in this chapter. In this example, 'tribes' are created with an initial population and resource characteristics. We can then see whether over time these tribes succeed (turning first into city-states and then large civilizations), become static (reaching an equilibrium situation in which the population neither expands nor contracts), or wither (and become extinct). We can also see what happens when two tribes clash. For example, our model should be able to predict the outcome of the onesided sixteenth-century conflict between the Spaniards and the Incas, which is used as a case study in Guns, Germs, and Steel.

In Diamond's book, numerous factors (including population density, raw materials, climate, topography, and the presence of certain kinds of plants and animals) are said to determine whether a tribe will graduate from the hunter-gatherer stage. In my 'back of the envelope' model, I've used just two of Diamond's factors as a proxy for all of them: the number of species of large-seeded grasses and of animal species suitable for domestication.

The next step in the book's model is to observe that if a society does not become large and dense, it will never develop the germs (and resistance to those germs) that kill off less dense, less resistant populations that the society comes into contact with. In my simplified model, complications such as density and proximity to disease-carrying animals are ignored; once a tribe hits 100,000 people, it starts producing germs (and resistance) in proportion to its growth. An index of bacteria density represents this in my application-even though, in the real world, much of the harm to indigenous peoples was done via viral disease (rather than bacterial) and via single diseases such as smallpox rather than a high overall count.

Finally, successful tribes must develop technology and commerce. For this to happen, there must be a leisure class-people who don't have to spend all their time hunting, gathering, or farming for food. (This implies that the number of calories expended in getting and preparing the food is less than the calories obtained in eating the food.) In my simplified model, once a tribe hits 1,000,000 people, and provided it has a growth rate of greater than 50% every twenty years, we assume it is creating technology and commerce (in proportion to its rate of growth).

Note Not all the code for the Guns, Germs, and Steel model can fit in the text of this chapter. You can download the complete code for the project from the Sybex website.

The User Interface

The primary user interface for the Guns, Germs, and Steel application is based on a single MDI form, shown in Figure 8.1. Each culture created by the user is shown in a child window in the client area of the form. (MDI applications are explained in Chapter 4, 'Building a Better Windows Interface.')

Figure 8.1: Statistics on cultures created by the user are displayed within the client area of the primary MDI form.

The mechanism for simulating advancing time is provided by a Timer component (which was explained in Chapter 4.)

The panel along the bottom of the form shown in Figure 8.1 allows the user to 'slow time down' or 'speed time up' using a TrackBar control. To achieve the effect I wanted,

I set the TrackBar Minimum property to 1, its Maximum to 100, and its Value to 90. I then added code to the TrackBar ValueChanged event to change the value of the Timer Interval property:

private void trackBar1_ValueChanged(object sender, System.EventArgs e){ timer1.Interval = Math.Abs(trackBar1.Value - 101) * 100;

}

Note The little formula in the TrackBar ValueChanged event produces a range of values between 1,000 and 10,000 (one to ten seconds) for the Timer Interval property.

The buttons beneath the TrackBar allow the user to

Start and stop the global timer

Reset the application by closing all child forms and de-referencing the items in the collection they are based upon

Cause a clash between two cultures

Add a tribe

Tribes are the first stage of cultures in this model-they may, with luck and perseverance, grow up to be city-states or civilizations. Figure 8.2 shows the interface used to initialize a tribe by assigning it a name, an initial population, and colors for screen display, and indicating the resources available to it.

Figure 8.2: Tribes are started with an initial population and access to the specified plant and animal resources.

The Application Architecture

Successful tribes grow to become city-states, and city-states that are lucky and play their cards right become civilizations (think classical Rome). This progression is modeled in the application with a base Tribe class. The CityState class inherits from the Tribe class, and the Civilization class inherits from the CityState class because each inherited class adds to the functionality of its base. This relationship is shown in the Class View window in Figure 8.3.

Figure 8.3: The Class View window shows that Civilization inherits from CityState, which inherits from Tribe, adding new (or overridden) members as it inherits.

An encapsulating class, GroupOfPeople, serves as a kind of traffic director among the three classes in the Tribe inheritance chain. The GroupOfPeople class

Instantiates objects of the classes it encapsulates

Directs to the appropriate class in the inheritance chain based on the current characteristics of a people

Fires events as appropriate, such as when a tribe has become extinct, or when a civilization reaches its Malthusian limits (and must reach for the stars)

Implements 'clashes' between different peoples through the use of overloaded comparison

Manages the display of a peoples' current characteristics

By the way, GroupOfPeople, as an encapsulating class, is an example of the "Facade" design pattern-a design pattern being an abstraction that identifies the key aspects of a common design structure that is useful for creating a reusable object-oriented design. According to Gamma, Helm, Johnson, and Vlissides, sometimes affectionately referred to as the "Gang of Four" and authors of the well-known Design Patterns: Elements of Reusable ObjectOriented Software (Addison-Wesley, 1994):

The intent of the Facade pattern is to provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

In other words, the programmer-or the MDI parent form, which contains the bulk of the userdriven program logic-does not have to worry about Tribe, CityState, or Civilization objects, or managing the display based on these objects. GroupOfPeople objects-which are items of a collection named PeoplesCollection-handle it all.

This general architecture is shown in Figure 8.4.

Figure 8.4: A collection is formed of GroupOfPeople objects, which, in turn, manage subsystem functionality.

Within the primary application form (the MDI form), the user can start and stop the Timer, and speed up or slow down how fast time appears to go by. In addition, code that uses the members of instantiated GroupOfPeople objects accomplishes the following tasks:

Add a new GroupOfPeople (which starts as a tribe).

Respond to the Timer's Elapsed event (which signifies that a generation, defined as twenty years, has gone by).

Respond to the events fired by objects in the PeoplesCollection collection of GroupOfPeople.

Determine the results of a clash between two GroupOfPeople objects-using the overloaded comparison operators provided by the object-and publish the results.

This application was built using several OOP design features, but, of course, not all OOP facilities found their way into it. It's self-evident that trade-offs are involved in constructing a chapter like this one around a large single example; on the whole, I think the pluses outweigh the minuses.

I found that the bulk of the work was creating a single instance that worked the way I wanted it to. After that, thanks to C#'s object-oriented context, I was able to quickly generalize my single instance into as many objects as I'd like, and easily engineer interactions between different instances. Readers should be able to interpolate these techniques and use them in their own applications.

Creating a Class

The class is the basic unit, or blueprint, for object construction in C# .NET. All objects in C#- and everything is an object, including variables, types, forms, and so on-are based on classes and their members. The members of an object correspond to the members of the class upon which it is based.

Inheritance is an important mechanism for extending the usefulness of a class. A derived class is one that has inherited from a base class.

Creating and invoking a simple class with a field, a property, a method, and an event as members was explained in the 'Creating a Class Library' section of Chapter 5, 'Reflecting on Classes.' You'll find the material in Chapter 5 a good introduction to the information presented here.

A class is created by first declaring it-using the class keyword-and then defining its members.

Declaring a Class

The basic form of a class declaration is to provide an access level (as described later in this section), followed by the keyword class, followed by an identifier (the class name). By convention, class names start with an initial capital. Members of the class follow the declaration, between curly braces. For example:

public class Tribe {

// Class body goes here

}

Note You can also include attributes in the class declaration. An attribute is metadata associated with an element of a program. For more information about attributes, search for the topic 'Attributes Tutorial' in online help.

To create a class that inherits from another base class, at the end of the class declaration place the colon operator (:), followed by the base class name. For example, the CityState class inherits from the Tribe class, so it is declared as follows:

public class CityState : Tribe { // Class body goes here

}

Tip The Visual Basic .NET equivalent of the : operator is the Inherits keyword.

Class Modifiers

The public keyword in the class declaration is an access modifier. Access modifiers are used to control access to classes and class members (see "Member Access" later in this chapter). The access modifiers allowed for a class are listed in Table 8.1, and general modifiers are shown in Table 8.2.

 

 

Table 8.1: Class Access Modifiers

 

 

Modifier

 

Description

 

 

 

internal

 

Only accessible to classes within the same assembly (this is the default access

 

 

level).

 

 

 

public

 

Accessible to classes outside the assembly. Most classes are declared with public

 

 

access.

 

 

 

Note Nested classes-classes within classes-are actually class members, and can be marked with the full set of class member (rather than class) access keywords, including protected and private. Those modifiers are discussed later in this chapter.

 

 

Table 8.2: Class General Modifiers

 

 

Modifier

 

Description

 

 

 

abstract

 

Contains no implementation and is unusable as is; derived classes implement the

 

 

members of an abstract class.

 

 

 

new

 

Explicitly hides a member inherited from a base class. Infrequently used in a class

 

 

declaration, since it must be used with a nested class.

 

 

 

sealed

 

Cannot be inherited.

 

 

 

Classes can be marked internal, in which case they are only visible within an assembly, or public-meaning they can be accessed externally to the assembly that they are in. If you leave the access modifier off, the class will default to internal. However, in practice most classes are declared public.

Classes can also be marked using the abstract modifier, which means that the class can only be used as the base class for other classes (put another way, an abstract class must be inherited from), or sealed, meaning that the class cannot be used as a base class (put another way, a sealed class cannot be inherited from).

It is perfectly reasonable to combine an access modifier with general modifiers in a class declaration. For example, the Dinosaur class declaration used in an example later in this chapter is

public abstract class Dinosaur