Building a Complex Custom Control: Rolodexby Jesse Liberty, author of Programming Visual Basic 2005
This article marks the end of "Liberty on Whidbey" and the beginning of a new series: "Liberty On Beta 2." Each article will demonstrate a real-world problem I've had to solve for a client, and will leave you with a complete design and working code. The goal of the column is to put Beta 2 to work and to explore applying the constructs and concepts of C#, ASP.NET, and Windows programming to solving real-world problems.
Analysis and Requirements
My customer asked for a complex Windows application that included, as one of its features, the ability to scroll through a list of his customers, suppliers, and employees using the visual metaphor of a Rolodex, much as he might look at contacts in Outlook. The Rolodex is shown in Figure 1.
Double-clicking on an entry in the program will bring you to a detail page for that entry (not implemented in this article, and left as an exercise for the reader). Note that for this article, I'll be using the NorthWindDatabase, available with SQL Server and Access, and soon with SQLServerExpress.
There is no Rolodex control in .NET, and so we'll build our own composite control, consisting of custom panels, custom RolodexEntries, and various standard controls such as panels, buttons, and scroll bars. We want to build the custom controls to provide maximum flexibility, so that we can reuse a well-factored design to display not only customers, but also suppliers, employees, and data from other tables as the requirements change.
Figure 1. Rolodex
There are two ways to approach a custom control of the complexity of the Rolodex. One is to build it incrementally, and the other is to design it up front. To be honest, I would normally build this project incrementally ("get it working and keep it working"), factoring out common code as I go. To make this article clear, however, I've designed and built the entire project (so that you don't have to suffer the dead ends and frustrations along the way), and I'll describe the architecture as if I were able to design it on paper without having to revise it once I started writing code (which is great in theory, but not so hot in practice).
We will create three custom controls, and a design pattern to create more as we need them. The controls are RolodexPanel, RolodexEntry, and RolodexCustomerEntry. In addition, we will create a form to hold a RolodexPanel, frmRolodex, and we will create a derived form: frmCustomerRolodex. We will factor as much common code into the base classes as possible so that we can later add new RolodexEntry types (e.g., RolodexSupplierEntry) and new forms (e.g., frmSupplierRolodex).
Areas of Responsibility
Following the principle of encapsulation, each class will have one job to do, one area of responsibility. The job of the Rolodex Panel is to lay out RolodexEntry objects (without regard to whether they are Customer or Supplier or other RolodexEntry subclasses). The job of the derived Rolodex entries is to know what information they display and to notify the form if they are clicked.
Notice that there are derived forms and derived entry objects, but only one form for all. (One form to rule them all, one form to find them, one form to bring them all and in the darkness bind them.)
The job of a frmRolodex is to hold a RolodexPanel, and the job of the derived forms is to know how to retrieve the data to create the Rolodex Entries that are held in the Panel.
Thus, the architecture looks like this:
Figure 2. Rolodex architecture
The Rolodex Form sub-type (e.g., FormCustomerRolodex) has exactly one RolodexPanel, which in turn has one or more (usually 12) RolodexEntry objects. The particular RolodexEntry object used will determine which information is displayed. At the moment, nothing enforces that a frmCustomerRolodex will display RolodexCustomerEntry objects, though that is the intention.