Chapter 5. Inheritance

Now we will take up the most important feature of OO languages like C# (and VB.NET): inheritance. When we create a Windows form, such as our Hello form, the IDE (VS.NET Integrated Development Environment) creates a declaration of the following type.


public class  Form1 : System.Windows.Forms.Form

This says that the form we create is a child class of the Form class, rather than being an instance of it. This has some very powerful implications. You can create visual objects and override some of their properties so that each behaves a little differently. We’ll see some examples of this in the following.

Constructors

All classes have specific constructors that are called when you create an instance of a class. These constructors always have the same name as the class. This applies to Form classes as well as nonvisual classes. Here is the constructor the system generates for our simple hello window in the class Form1.


public class Form1 {
      public Form1(){            //constructor
             InitializeComponent();
       }

When you create your own classes, you must create constructor methods to initialize them, and you can pass arguments into the class to initialize class parameters to specific values. If you do not specifically include a constructor in any class you write, a constructor having no arguments is generated for you under the covers.

The InitializeComponent method is generated by the IDE as well, and it contains code that creates and positions all the visual controls in that window. If we need to set up additional code as part of the initialization of a Form class, we will always write a private init method that we call after the InitializeComponent method call.


public Form1(){
       InitializeComponent();
       init();
}

private void init() {
      x = 12.5f;        //set initial value of x
}

Drawing and Graphics in C#

In our first example, we’ll write a program to draw a rectangle in a PictureBox on a form. In C#, controls are repainted by the Windows system, and you can connect to the paint event to do your own drawing whenever a paint event occurs. Such a paint event occurs whenever the window is resized, uncovered, or refreshed. To illustrate this, we’ll create a Form containing a PictureBox, as shown in Figure 5-1.

Figure 5-1. Inserting a PictureBox on a Form

image

Then we’ll select the PictureBox in the designer and select the Events button (with the lightning icon) in the Properties window. This brings up a list of all the events that can occur on a PictureBox as shown in Figure 5-2.

Figure 5-2. Selecting the Paint Event for the PictureBox window

image

Double-clicking on the Paint Event creates the following empty method in the Form’s code.


private void pic_Paint(object sender, PaintEventArgs e) {
}

It also generates code that connects this method to the Paint Event for that picture box inside the InitializeComponents method.


this.pic.Paint += new PaintEventHandler(this.pic_Paint);

The PaintEventArgs object is passed into the subroutine by the underlying system, and you can obtain the graphics surface to draw on from that object. To do drawing, you must create an instance of a Pen object and define its color and, optionally, its width. This is illustrated here for a black pen with a default width of 1.


private void pic_Paint(object sender, PaintEventArgs e) {

  Graphics g = e.Graphics;              //get Graphics surface
  Pen rpen = new Pen(Color.Black);      //create a Pen
  g.drawLine(rpen, 10,20,70,80);        //draw the line
}

In this example, we show the Pen object being created each time a Paint Event occurs. We might also create the pen once in the window’s constructor or in the init method we usually call from within it.

Using Inheritance

Inheritance in C# gives us the ability to create classes that are derived from existing classes. In new derived classes, we only have to specify the methods that are new or changed. All the others are provided automatically from the base class we inherit. To see how this works, let’s consider writing a simple Rectangle class that draws itself on a form window. This class has only two methods: the constructor and the draw method.


namespace CsharpPats
{
public class Rectangle      {
       private int x, y, w, h;
       protected Pen rpen;
       public Rectangle(int x_, int y_, int w_, int h_)
       {
              x = x_;       //save coordinates
              y = y_;
              w = w_;
              h = h_;
              //create a pen
              rpen = new Pen(Color.Black);
       }
       //---------------
       public void draw(Graphics g) {
              //draw the rectangle
              g.DrawRectangle (rpen, x, y, w, h);
       }
}
}

Namespaces

We already mentioned the System namespaces. Visual Studio.NETalso creates a namespace for each project equal to the name of the project itself. You can change this namespace on the property page, or you can make it blank so that the project is not in a namespace. However, you can create namespaces of your own, and the Rectangle class shows a good reason for doing so. The System.Drawing namespace that this program requires to use the Graphics object also contains a Rectangle class. Rather than renaming our new Rectangle class to avoid this name overlap or “collision,” we can just put the whole Rectangle class in its own namespace, as we saw previously.

Then when we declare the variable in the main Form window, we can declare it as a member of that namespace.


CsharpPats.Rectangle rec;

In this main Form window, we create an instance of our Rectangle class.


private void init() {
       rect = new CsharpPats.Rectangle (10, 20, 70, 100);
}
//---------------
public Form1() {
       InitializeComponent();
       init();
}

Then we add the drawing code to our Paint event handler to do the drawing and pass the graphics surface on to the Rectangle instance.


private void pic_Paint(object sender, PaintEventArgs e) {
       Graphics g =  e.Graphics;
       rect.draw (g);
}

This gives us the display we see in Figure 5-3.

Figure 5-3. The Rectangle drawing program

image

Creating a Square from a Rectangle

A square is just a special case of a rectangle, and we can derive a Square class from the Rectangle class without writing much new code. Here is the entire class.


namespace CsharpPats {
      public class Square : Rectangle {
      public Square(int x, int y, int w):base(x, y, w, w) {
             }
      }
}

This Square class contains only a constructor, which passes the square dimensions on to the underlying Rectangle class by calling the constructor of the parent Rectangle class as part of the Square constructor.


base(x, y, w, w)

Note the unusual syntax: The call to the parent class’s constructor follows a colon and is before the opening brace of the constructor itself.

The Rectangle class creates the pen and does the actual drawing. Note that there is no draw method at all for the Square class. If you don’t specify a new method, the parent class’s method is used automatically, and this is what we want to have happen here.

The program that draws both a rectangle and a square has a simple constructor where instances of these objects are created.


private void init() {
       rect = new Rectangle (10, 20, 70, 100);
       sq = new Square (150,100,70);
}

The program also has a paint routine where instances of these objects are drawn.


private void pic_Paint(object sender, PaintEventArgs e) {
       Graphics g =  e.Graphics;
       rect.draw (g);
       sq.draw (g);
}

The display is shown in Figure 5-4 for the square and rectangle.

Figure 5-4. The Rectangle class and the Square class derived from it

image

Public, Private, and Protected

In C#, you can declare both variables and class methods as public, private, or protected. A public method is accessible from other classes, and a private method is accessible only inside that class. Usually, you make all class variables private and write getXxx and setXxx accessor functions to set or obtain their values. It is generally a bad idea to allow variables inside a class to be accessed directly from outside the class, since this violates the principle of encapsulation. In other words, the class is the only place where the actual data representation should be known, and you should be able to change the algorithms inside a class without anyone outside the class being any the wiser.

C# uses the protected keyword as well. Both variables and methods can be protected. Protected variables can be accessed within the class and from any subclasses you derive from it. Similarly, protected methods are only accessible from that class and its derived classes. They are not publicly accessible from outside the class. If you do not declare any level of accessibility, private accessibility is assumed.

Overloading

In C#, as well as in other object-oriented languages, you can have several class methods with the same name as long as they have different calling arguments or signatures. For example, we might want to create an instance of a StringTokenizer class where we define both the string and the separator.


tok = new StringTokenizer("apples, pears", ",");

By declaring constructors with different numbers of arguments, we say we are overloading the constructor. Here are the two constructors.


public StringTokenizer(string dataLine)                {
       init(dataLine, " ");
}
//----------
public StringTokenizer(string dataLine, string delim) {
       init(dataLine, delim);
}
private void init(string data, string delim) {
       //...
}

Of course, C# allows us to overload any method as long as we provide arguments that allow the compiler to distinguish between the various overloaded (or polymorphic) methods.

Virtual and Override Keywords

If you have a method in a base class that you want to allow derived classes to override, you must declare it as virtual. This means that a method of the same name and argument signature in a derived class will be called rather than the one in the base class. Then you must declare the method in the derived class, using the override keyword.

If you use the override keyword in a derived class without declaring the base class’s method as virtual, the compiler will flag this as an error. If you create a method in a derived class that is identical in name and argument signature to one in the base class and do not declare it as override, this also is an error. If you create a method in the derived class and do not declare it as override and also do not declare the base class’s method as virtual, the code will compile with a warning but will work correctly, with the derived class’s method called as you intended.

Overriding Methods in Derived Classes

Suppose we want to derive a new class called DoubleRect from Rectangle, which draws a rectangle in two colors offset by a few pixels.  We must declare the base class draw method as virtual.


public virtual void draw(Graphics g) {
  g.DrawRectangle (rpen, x, y, w, h);
}

In the derived DoubleRect constructor, we will create a red pen in the constructor for doing the additional drawing.


public class DoubleRect:Rectangle        {
private Pen rdPen;
public DoubleRect(int x, int y, int w, int h):
             base(x,y,w,h) {
       rdPen  = new Pen (Color.Red, 2);
}

This means that our new class DoubleRect will need its own draw method. However, this draw method will use the parent class’s draw method but add more drawing of its own.


public override void draw(Graphics g) {
      base.draw (g);       //draw one rectangle using parent class
      g.DrawRectangle (rdPen, x +5, y+5, w, h);
}

Note that we want to use the coordinates and size of the rectangle that was specified in the constructor. We could keep our own copy of these parameters in the DoubleRect class, or we could change the protection mode of these variables in the base Rectangle class to protected from private.


protected int x, y, w, h;

The final rectangle drawing window is shown in Figure 5-5.

Figure 5-5. The DoubleRect classes

image

Replacing Methods Using New

Another way to replace a method in a base class when you cannot declare the base class method as virtual is to use the new keyword in declaring the method in the derived class. If you do this, it effectively hides any methods of that name (regardless of signature) in the base class. In that case, you cannot make calls to the base method of that name from the derived class and must put all the code in the replacement method.


public new void draw(Graphics g) {
       g.DrawRectangle (rpen, x, y, w, h);
       g.DrawRectangle (rdPen, x +5, y+5, w, h);
}

Overriding Windows Controls

In C# we can easily make new Windows controls based on existing ones, using inheritance. We’ll create a TextBox control that highlights all the text when you tab into it. In C#, we can create that new control by just deriving a new class from the TextBox class.

We’ll start by using the Windows Designer to create a window with two TextBoxes on it. T hen we’ll go to the Project | Add User Control menu and add an object called HiTextBox. We’ll change this to inherit from TextBox instead of UserControl.


public class HiTextBox : Textbox {

Then before we make further changes, we compile the program. The new HiTextBox control will appear at the bottom of the Toolbox on the left of the development environment. You can create visual instances of the HiTextBox on any windows form you create. This is shown in Figure 5-6.

Figure 5-6. The Toolbox, showing the new control we created and an instance of the HiTextBox on the Windows Designer pane of a new form

image

Now we can modify this class and insert the code to do the highlighting.


public class HiTextBox : System.Windows.Forms.TextBox
{
       private Container components = null;
//-------------
private void init() {
       //add event handler to Enter event
       this.Enter += new System.EventHandler (highlight);
}
//-------------
//event handler for highlight event
private void highlight(object obj, System.EventArgs e) {
       this.SelectionStart =0;
       this.SelectionLength =this.Text.Length ;
}
//-------------
public HiTextBox()           {
       InitializeComponent();
       init();
}

And that’s the whole process. We have derived a new Windows control in about ten lines of code. That’s pretty powerful. You can see the resulting program in Figure 5-6. If you run this program, you might at first think that the ordinary TextBox and the HiTextBox behave the same because tabbing between them makes them both highlight. This is the “autohighlight” feature of the C# TextBox. However, if you click inside the TextBox and the HiTextBox and tab back and forth, you will see in Figure 5-7 that only our derived HiTextBox continues to highlight.

Figure 5-7. A new derived HiTextBox control and a regular TextBox control

image

Interfaces

An interface is a declaration that a class will contain a specific set of methods with specific arguments. If a class has those methods, it is said to implement that interface. It is essentially a contract or promise that a class will contain all the methods described by that interface. Interfaces declare the signatures of public methods but do not contain method bodies.

If a class implements an interface called Xyz, you can refer to that class as if it were of type Xyz as well as by its own type. Since C# only allows a single tree of inheritance, this is the only way for a class to be a member of two or more base classes.

Let’s take the example of a class that provides an interface to a multiple select list like a ListBox or a series of check boxes.


//an interface to any group of components
//that can return zero or more selected items
//the names  are returned in an Arraylist
       public interface Multisel    {
              void clear();
              ArrayList getSelected();
              Panel getWindow();
       }

When you implement the methods of an interface in concrete classes, you must declare that the class uses that interface, and you must provide an implementation of each method in that interface as well, as we illustrate here.


/// ListSel  class implements MultiSel interface
       public class ListSel : Multisel {
             public ListSel() {
             }
             public void clear() {
             }
             public ArrayList getSelected() {
                    return new ArrayList ();
             }
             public Panel getWindow() {
                    return new Panel ();
             }
       }

We’ll see how to use this interface when we discuss the Builder pattern.

Abstract Classes

An abstract class declares one or more methods but leaves them unimplemented. If you declare a method as abstract, you must also declare the class as abstract. Suppose, for example, that we define a base class called Shape. It will save some parameters and create a Pen object to draw with. However, we’ll leave the actual draw method unimplemented, since every different kind of shape will need a different kind of drawing procedure.


public abstract class Shape      {
       protected int height, width;
       protected int xpos, ypos;
       protected Pen bPen;
       //-----
       public Shape(int x, int y, int h, int w)       {
              width = w;
              height = h;
              xpos = x;
              ypos = y;
              bPen = new Pen(Color.Black );
       }
       //-----
       public abstract void draw(Graphics g);
       //-----
       public virtual float getArea() {
              return height * width;
       }
}

Note that we declare the draw method as abstract and end it with a semicolon rather than include any code between braces. We also must declare the overall class as abstract.

However, you can’t create an instance of an abstract class like Shape. You can only create instances of derived classes in which the abstract methods are filled in. So, let’s create a Rectangle class that does just that.


public class Rectangle:Shape       {
       public Rectangle(int x, int y,int h, int w):
                    base(x,y,h,w) {}
       //-----
       public override void draw(Graphics g) {
              g.DrawRectangle (bPen, xpos, ypos, width, height);
       }
}

This is a complete class that you can instantiate. It has a real draw method.

In the same way, we could create a Circle class that has its own draw method.


public class Circle :Shape       {
       public Circle(int x, int y, int r):
              base(x,y,r,r) {      }
       //-----
       public override void draw(Graphics g) {
              g.DrawEllipse (bPen, xpos, ypos, width, height);
       }
}

Now if we want to draw the circle and rectangle, we just create instances of them in the init method we call from our constructor. Note that since they are both of base type Shape, we can treat them as Shape objects.


public class Form1 : System.Windows.Forms.Form       {
       private PictureBox pictureBox1;
       private Container components = null;
       private Shape rect, circ;
       //-----
       public Form1()             {
              InitializeComponent();
              init();
       }
       //-----
       private void init() {
              rect = new CsharpPats.Rectangle (50, 60, 70, 100);
              circ = new Circle (100,60, 50);
       }

Finally, we draw the two objects by calling their draw methods from the Paint Event handler we created as we did previously.


private void pictureBox1_Paint(object sender, PaintEventArgs e) {
       Graphics g = e.Graphics ;
       rect.draw (g);
       circ.draw (g);
}

We see this program executing in Figure 5-8.

Figure 5-8. An abstract class system drawing a rectangle and circle

image

Comparing Interfaces and Abstract Classes

When you create an interface, you are creating a set of one or more method definitions that you must write in each class that implements that interface. There is no default method code generated; you must include that yourself. The advantage of interfaces is that they provide a way for a class to appear to be part of two classes: one inheritance hierarchy and one from the interface. If you leave an interface method out of a class that is supposed to implement that interface, the compiler will generate an error.

When you create an abstract class, you are creating a base class that might have one or more complete, working methods, but at least it is one that is left unimplemented and declared abstract. You can’t instantiate an abstract class, but you must derive from it classes that contain implementations of the abstract methods. If all the methods of an abstract class are unimplemented in the base class, it is essentially the same as an interface but with the restriction that you can’t make a class inherit from it as well as from another class hierarchy as you could with an interface. The purpose of abstract classes is to provide a base class definition for how a set of derived classes will work and then allow the programmer to fill in these implementations in the various derived classes.

Another related approach is to create base classes with empty methods. These guarantee that all the derived classes will compile but that the default action for each event is to do nothing at all. Here is a Shape class like that.


public class NullShape     {
              protected int height, width;
              protected int xpos, ypos;
              protected Pen bPen;
              //-----
              public Shape(int x, int y, int h, int w) {
                     width = w;
                     height = h;
                     xpos = x;
                     ypos = y;
                     bPen = new Pen(Color.Black );
              }
              //-----
              public virtual void draw(Graphics g){}
              //-----
              public virtual float getArea() {
                     return height * width;
              }
       }

Note that the draw method is now an empty method. Derived classes will compile without error, but they won’t do anything much. And there will be no hint of what method you are supposed to override, as you would get from using an abstract class.

Summary

We’ve seen the shape of most of the important features in C# in this chapter. C# provides inheritance, constructors, and the ability to overload methods to provide alternate versions. This leads to the ability to create new derived versions even of Windows controls. In the chapters that follow, we’ll show you how you can write design patterns in C#.

Programs on the CD-ROM

image

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset