6.2 Inheriting from Forms and User Controls

6.2 Inheriting from Forms and User Controls

Visual Studio .NET allows you to add an inherited form or control as a new item to a project. When you add such an item, it will display the Inheritance Picker dialog, which lists appropriate classes from the solutions in your project (this includes all the forms you have defined, or all the user controls, if you are creating an inherited control). Inheriting from a form or a user control requires the relevant projects in your solution to have been built. If classes you expect to see are missing from the list, check that your solution builds without errors. The Inheritance Picker also provides a Browse button, so that you can derive from classes defined in components outside your solution.

Once you have selected your base class, the usual Forms Designer will be shown, just as it would be for a normal form or composite control. But rather than displaying a blank canvas, the editor will show the contents of the base classyour new user interface element will initially look exactly like its base class. Not surprising, as inherited classes always have all their base class's members .

Of course, inheritance allows us to extend the functionality of the base class, not just to replicate it, so the editor lets us add new controls to the derived class. The editor annotates base class controls with a small icon, as shown on the text box and button in Figure 6-1, to enable you to tell the difference between controls from the base class and controls added in the derived class.

Figure 6-1. Showing controls from the base form
figs/winf_0601.gif

The code generated for derived forms and controls is straightforward. As Examples Example 6-3 and Example 6-4 show, it is almost identical to the code generated for a normal form. So there is the usual constructor, which calls the InitializeComponent method where the form's controls are created and initialized . There is also the normal Dispose function, where resources are cleaned up. As usual, you can add your own initialization code at the end of the constructor and your own cleanup code in the Dispose method. The only obvious difference between this and a normal form is that the class no longer derives directly from Form ; it inherits from your chosen base class (as the bold line shows).

Example 6-3. A derived form's basic structure in C#
  public class DerivedForm : BaseForm  {     private System.ComponentModel.IContainer components = null;     public DerivedForm()     {         InitializeComponent();     }     protected override void Dispose( bool disposing )     {         if( disposing )         {   if (components != null)    {       components.Dispose();   }         }         base.Dispose( disposing );     }     private void InitializeComponent()     {         . . .         Usual designer-generated code         . . .     } } 
Example 6-4. A derived form's basic structure in VB
 Public Class DerivedForm  Inherits BaseForm  Public Sub New()         MyBase.New()         InitializeComponent()     End Sub     Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)         If disposing Then   If Not (components Is Nothing) Then       components.Dispose()   End If         End If         MyBase.Dispose(disposing)     End Sub     Private components As System.ComponentModel.IContainer     Private Sub InitializeComponent()         . . .         Usual designer-generated code         . . .     End Sub End Class 

This similarity of structure means that both the base and the derived classes will have InitializeComponent and Dispose methods . If the class is going to initialize new instances correctly, both InitializeComponent methods need to run. Likewise, both Dispose methods need to execute when the form is destroyed . This will indeed happen, although it works differently for initialization and disposalthere is a subtle distinction between the ways the InitializeComponent and Dispose methods interact with their counterparts in the base class.

Because InitializeComponent is always declared as private , the base and derived implementations are considered by the CLR to be two completely different methodsthe IntializeComponent method in the derived class does not override the one in the base class, because you cannot override a private method. Both of these will run because of the way the CLR constructs objects: first the base class's constructor will run, calling the base class's InitializeComponent method, and then the derived class's constructor will run, calling the derived class's InitializeComponent method. In the VB version shown in Example 6-4, the base class's constructor is called explicitly in any case by the MyBase.New statement.

Conversely, the Dispose method is always marked as protected override in C# and as Protected Overloads Overrides in VB. This means that the method replaces the base class's implementation. However, there is a call to base.Dispose or MyBase.Dispose at the end of this method (just as there would be in the implementation of Dispose in the base class). This is the standard patternderived classes always override Dispose and then call the base class's Dispose at the end, ensuring that all the classes in the hierarchy get a chance to clean up properly. This means that, unlike the InitializeComponent methods, the first Dispose method to run will be that of the most derived class, and the calls then work their way up the inheritance hierarchy [2] (until they get to System.ComponentModel.Component , the base class of Control , which is the place in the hierarchy where the Dispose method is introduced). This model supports any depth of inheritanceit is entirely possible to derive from an inherited form or control. Each class can add its own collection of controls to the set supplied by the base class.

[2] Although the mechanisms involved are very different, this echoes C++, where construction starts with the base class, but destruction starts with the most derived class.

If you build an inherited UI component in C#, you will notice that by default the Designer restricts what you can do to a base class's controls. We will now examine how the interaction between a derived class and its base's controls works, and how to grant a derived class access to the controls in a base class.

6.2.1 Interacting with Controls in the Base Class

In the last chapter, we saw how the controls you write have two faces: the API seen by the developer and the user interface seen by the end user. Inheritance complicates the picture a littlethere are two different ways a developer can use your control: one is simply to instantiate it and use it, but the other is to derive from it. Developers who write derived classes will get to see the programming interface. But most forms are written without much of an API, which can cause problems for a deriving class. As we will see, a base form often needs to be written with deriving classes in mind for inheritance to be successful.

Unlike C#, VB.NET makes the protection level Friend by default. (This is equivalent to internal in C#.) This means that controls will be accessible to any code in the same component. So if you derive from a VB.NET form or user control, the behavior you see will depend on whether your derived class is in the same project as the base class. If it is in the same project, all the controls will be accessible, but if it is not, the behavior will be the same as you would see in C#the controls in the base class will be inaccessible.

The most obvious difference between a control's API and its UI is that features visible to the end user are usually inaccessible to the developer. On screen, it might be clear that a composite control consists of some labels, text boxes, and buttons , but the child controls that represent these are usually private implementation details of the form's class. So although any deriving class that you write will have all the same child controls, they will not be directly accessible to your code because they will be private members of your base class.

Figure 6-2 shows a base form and a class derived from that form in the editor. In both cases, the OK button has been selected, but notice how the selection outline is different. With the base class, which is where the OK button is defined, we see the normal outline with resize handles. But on the derived class, we get a solid outline with no handles. This is how the form editor shows us that a control cannot be modified. Any attempt to move, resize, or otherwise change the control will be unsuccessful . Indeed, the control will be completely invisible to any code in the derived form even though it will be present on screen, so for controls with readable properties, such as a text box, the deriving class will not even be able to read those properties.

Figure 6-2. A base form and a derived form
figs/winf_0602.gif

This is the default behavior for C# base classesif you do not take steps in your base class to make your controls accessible to derived classes, they will be present but untouchable, like the OK button above. However, it is easy enough to change thiscontrol accessibility is based entirely around the member protection provided by the CLR. The OK button is inaccessible to the derived class because its associated member will have been defined as private in the base class, as shown in Example 6-5. (Note, if the base class was written in VB.NET, it would be marked Friend , as shown in Example 6-6. This would mean that if the derived form were in the same project as the base class, the button would be inaccessible.)

Example 6-5. A control accessible only to the base class
 // C#  private System.Windows.Forms.Button buttonOK; 
Example 6-6. A control accessible only to other classes in the same project
 // VB Friend WithEvents Button1 As System.Windows.Forms.Button 

If you change this to protected , the control will become visible to all derived classes. The Designer provides a way of doing this: in the properties page for a control there is a property called Modifiers under the Design section. By default, this is private (in C# projects) or Friend (in VB projects), but you can choose any protection level offered by the CLR. If you make this accessible to derived classes (i.e., protected ), they will be able to retrieve and modify your control's properties. The Forms Editor indicates this by showing a normal outline with resize handles when you select the control, as shown in Figure 6-3.

Figure 6-3. A base's control accessible in a derived form
figs/winf_0603.gif

Once a control from the base class is no longer private, your derived class can modify any of its properties except its name . This means that you can, for example, move it, resize it, change its caption, enable or disable it, or make it invisible.

Deciding which controls should be accessible to derived classes is an important consideration when designing a base class. If a control from a base class cannot be moved, this could seriously hamper the ability to build useful derived forms. Controls marked as private cannot be moved or resized to adapt their layout to the needs of the derived. Later on in this chapter, we will look at how best to approach this and other inheritance issues when considering how to design for inheritance.

For the truly determined, gaining access to private controls is not an insurmountable problem. Although the member field in the base class is private , that merely prevents you from using the control through that particular field. If you can obtain a reference to it by other means, you can still modify all its properties. It is always possible to do this because all the controls on a form can be accessed through its Controls property. This collection is enumerable, so you can just search for the control with a foreach loop. This is not recommended though. It might help you as a last resort, but it's a bit of a hackthe base class will not expect you to surreptitiously modify its private controls like this. Furthermore, there is no support for it in the Designeryou must write code to do this.


6.2.2 Event Handling

If the base class provides a non-private control, not only can you modify its properties, you can also add event handlers. So in the example above, we could add our own Click handler for the OK button. However, there is a subtle difference between the way events work and the way most properties work, which means this will not always have the effect you anticipate.

The crucial thing to remember when handling events in a derived class is that event sources can support multiple handlers. This makes events different from properties such as Location if the derived class sets that property, it will replace any value that the base class gave it. With an event, if the derived class adds an event handler, that handler will be called in addition to any that were specified by the base class. So in the example above, if the base class has already attached a Click handler to the OK button, and we then add another of our own, it will run after the base class's handler.

So when it comes to handling events from child controls, we cannot replace the base class's behavior, we can only add to it. Again, this illustrates that unless the base class is designed with inheritance in mind, we could run into trouble.

Beyond certain pitfalls with poorly designed base classes, inheriting from forms or composite controls is mostly straightforward, thanks to the support provided by the Forms Designer. However, it is possible to derive from other types of controls, albeit with less help from the development environment, as we will now see.



. Net Windows Forms in a Nutshell
.NET Windows Forms in a Nutshell
ISBN: 0596003382
EAN: 2147483647
Year: 2002
Pages: 794

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net