|
Visual inheritance is a concept that most people seem to be familiar with. However, when programmers are asked whether they're using visual inheritance, most say that they don't take advantage of it. The next section will show you what visual inheritance is, and how to take advantage of it quickly and easily within your applications. By the time you're done with this section, you will have become a convert and will use visual inheritance in all your Windows Forms applications in the future. Using Inherited FormsWhen you create a class that inherits from another class, the newly created class inherits those members and methods that the child class is allowed to inherit. The same is true for an inherited form. When you look at it from the lowest level, forms are just classes. A form that inherits from another form is still just one class inheriting from another class. The trick to remember is that a form renders its GUI through inheritable members, properties, and methods. By virtue of this inheritance, child forms inherit their GUI look and feel from their parents. Just as with standard child classes, child forms are free to reuse any behavior or property of a parent. In addition, they can choose to override and provide their own implementation for any property or method where it is applicable. You have already seen how to create reusable controls and you have seen their benefits. With a reusable control, you have a self-contained piece of functionality and user interface that you can reuse across multiple forms. With an inherited form, you create some piece of the user interface and functionality that will be provided free of charge to all child forms inheriting from the same parent. This gives you the ability to rapidly create a consistent look and feel throughout your Windows Forms application as well as giving you the ability to easily change that look and feel. To truly see the power of visual inheritance, consider a scenario in which not using it could be disastrous. As an example, assume that you have created a large Windows Forms application that has at least 50 forms in it. On each of those forms, you have placed the company logo and an area within the logo that you can click to bring up the company's home page. Now assume that your boss has just told you to replace all 50 of those forms with the new company logo, and that the link should no longer open the company website; it should instead open a Word document that will be installed with the application. If you haven't used visual inheritance to create the application, you will be stuck making the change manually to all 50 forms. In addition, you will have to retest every one of the forms because the code is duplicated on each and a mass-change operation like this is highly prone to errors and typos. However, if you had used visual inheritance, you could have simply made the change to the topmost ancestor in the hierarchy of inherited forms. All the child forms would be aware of the change automatically, and you would not have to rewrite a single line of code in any of them. WinForms Visual Inheritance in ActionThis section will show you an example of creating a system that uses visual inheritance. This example illustrates a sample company that wants to create a few standard forms that will be used to create a consistent look and feel for all of their applications. To build this sample yourself, first create a library that contains a hierarchy of inherited forms. The sample will be finished by creating an application that takes advantage of the forms that use visual inheritance. Open Visual Studio .NET 2003 and create a new solution called VisualInheritance. In that solution, add a new Windows Application called FormsLibrary. FormsLibrary will become a class library eventually. It was started as a Windows Application because doing so enables a developer to right-click the project and choose Add Windows Form, which can't be done with a standard class library. Start coding with the form at the top of the hierarchy of company forms, CompanyDefault. Add a new Windows Form to the FormsLibrary project and call it CompanyDefault. To that form, add a panel that docks on top. You can decorate this any way you like, but at some point, add a label to the top panel and call it lblApplicationName. Next, add a new property to the form like the following: protected string ApplicationName { get { return lblApplicationName.Text; } set { lblApplicationName.Text = value; } } This property will be used by child forms that want to set the application name. This gives the child forms the capability to change the display of something that was created by the parent form. Although the code could have exposed the label itself as protected, doing so violates some of the guidelines of encapsulation that are always good to follow and also gives a cleaner, more maintainable interface. The protected keyword was used so that only forms that inherit from this form, but not unrelated forms, can set this property. Now right-click the FormsLibrary project and choose Add Inherited Form. Do not add a regular form; make sure that you choose Inherited. From there, navigate down to the user interface node and choose Inherited Form. Call the form CompanyOKCancel and when Visual Studio .NET asks you for the parent form, make sure that you choose CompanyDefault. When the form first appears, you will see that it looks just like the CompanyDefault form. The difference between this form and the parent form is that the inherited user interface elements are locked in position and are read-only. This is an important distinction. What you see in the designer is a partial render. The user interface elements inherited from the parent are not actually part of your design surface; they are drawn in place so that you can see what your form will look like at runtime to help you position new controls. As its name might suggest, you are going to add an OK button and a Cancel button to this form. To do so, create another panel and set the Dock property to the bottom of the form. I set the background to white to make it stand out against the content area of the form. To keep your code in sync with the example, name the OK button btnOK and the Cancel button btnCancel. Figure 17.1 shows the CompanyOKCancel form from inside the Visual Studio .NET designer. Figure 17.1. The CompanyOKCancel form in the Visual Studio .NET designer.Before moving on to writing the code that will inherit from these two forms, some event handling must be added. The OK and Cancel buttons are no good to the user if forms that inherit from CompanyOKCancel cannot make use of them. To allow child forms to use these buttons and still maintain proper encapsulation, some events must be created for the child form to consume and respond to. Listing 17.1 shows the complete code-behind for the CompanyOKCancel form. Listing 17.1. The CompanyOKCancel Inherited Formusing System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace SAMS.Evolution.CSharpUnleashed.VisualInheritance.Library { public class CompanyOKCancel : SAMS.Evolution.CSharpUnleashed.VisualInheritance.Library.CompanyDefault { private System.Windows.Forms.Panel panel2; private System.Windows.Forms.Button btnCancel; private System.Windows.Forms.Button btnOK; private System.ComponentModel.IContainer components = null; protected delegate void ButtonClickDelegate(object sender, System.EventArgs e); protected event ButtonClickDelegate OKClicked; protected event ButtonClickDelegate CancelClicked; public CompanyOKCancel() { // This call is required by the Windows Form Designer. InitializeComponent(); } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } // *** Designer Generated Code Hidden from Listing private void btnOK_Click(object sender, System.EventArgs e) { if (OKClicked != null) { OKClicked(sender, e); } } private void btnCancel_Click(object sender, System.EventArgs e) { if (CancelClicked != null) { CancelClicked(sender, e); } } } } The important parts of this listing are the delegates and the events. The delegates dictate the type of the event, and the events are exposed as protected members. This allows child forms that inherit from this form to consume these events, but no other forms can do so. As you can see with the event handlers for the btnOK and btnCancel Click events, if a child form has subscribed to listen to the appropriate events, it will be notified. This method of deferring events to a child from the parent is an excellent way of maintaining encapsulation and good object-oriented design while still maintaining flexibility and functionality. Continue this example by creating some forms that inherit from the forms in the base library. The first step is to change the FormsLibrary Windows application to a class library. You can do that by changing the output type to Class Library in the Project Properties dialog. Next, add a Windows application to the solution called Visual Inheritance. Add a reference from the new Windows application to the class library project that we just finished building. Add a new inherited form called InheritedForm, and choose CompanyDefault as the parent form. Then add another inherited form called InheritedOKCancel and choose CompanyOKCancel as the parent form. To add some event handlers for the CompanyOKCancel form's events to the InheritedOKCancel form, set the code for the InheritedOKCancel form to the code shown in Listing 17.2. Listing 17.2. The InheritedOKCancel Form Code-Behindusing System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace SAMS.Evolution.CSharpUnleashed.VisualInheritance { public class InheritedOKCancel : SAMS.Evolution.CSharpUnleashed.VisualInheritance.Library.CompanyOKCancel { private System.Windows.Forms.Label label1; private System.ComponentModel.IContainer components = null; public InheritedOKCancel() { // This call is required by the Windows Form Designer. InitializeComponent(); this.CancelClicked += new ButtonClickDelegate(InheritedOKCancel_ CancelClicked); this.OKClicked += new ButtonClickDelegate(InheritedOKCancel_OKClicked); } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } // Windows forms designer code hidden for listing private void InheritedOKCancel_CancelClicked(object sender, EventArgs e) { MessageBox.Show(this, "You clicked Cancel."); } private void InheritedOKCancel_OKClicked(object sender, EventArgs e) { MessageBox.Show(this, "You clicked OK."); } } } The code in Listing 17.2 sets up the event handlers in the child form to respond to events fired from controls in the parent form. This particular fact is extremely important to remember because it will help you create powerful hierarchies of reusable form templates for your applications in the future. Now that you have created your inherited forms, create a few test controls on the main form. Create two buttons on the main form. The first button will launch the first form you created, and the second button will launch the second form. Figure 17.2 shows the first form after it has been launched. Figure 17.3 shows the second form after it has been launched. Click the OK button on the InheritedOKCancel form to test the event-handling setup. Figure 17.2. The InheritedForm Windows Form.Figure 17.3. The Inherited OKCancel Windows Form.Visual Inheritance Best PracticesAs you saw in the previous code sample, a separate library was created that contained the forms from which the code would inherit. This is a design pattern that I highly recommend you adopt. When creating a hierarchy of reusable forms, you want to be able to use those forms across multiple projects to provide your developers with a consistent look and feel, and a minimum set of functionality. If your forms are in a separate library that is designed to stand on its own, but that can be easily integrated by child forms, you will find that the whole experience of creating a Windows Forms application might become easier and less frustrating. Another design pattern that you should follow is that of maintaining encapsulation. As you saw, the code ensured that properties, methods, and events to be used by child forms were set as protected, and they didn't allow the controls used by parent forms to be accessed directly by child controls. Forcing this kind of encapsulation also forces you to think more closely about the interaction model between the hierarchy of forms. Inherited forms and visual inheritance can be extremely powerful tools, and if you spend a little extra design time up front, the rewards will more than pay for the time spent. |
|