In this paper we will discuss a better way of building Windows client applications using the Borland VCL Library. As a result, we will have a library and sample application that will allow us to build module and UI independent Windows applications more easily.
The paper is divided into two parts:
Develop a simple Application Framework. This part is not dependant on Developer Express libraries and so VCL developers who are not Developer Express customers can use it. Improvement of the Application Framework by using Developer Express libraries. To compile and run the applications provided, you should have installed the following Developer Express Libraries:Issues considered in this paper include:
the best way of implementing application layouts - using Frames inside the main form Module Inheritance using Frames Using the native VCL Actions layer How long it takes to migrate from one Menu&ToolBar library to another. Using this Application Framework, you can do it pretty fast, as you only need to modify the code in one module. Setting up the ExpressNavBar control at run-time Using ExpressBars and ExpressGrid in the Application Framework Adding a printing capability using the ExpressPrinting LibraryWe will develop our application framework piecemeal, starting from the simplest task and at each step we will add functionality, thus keeping code changes as simple as possible. There are 6 steps in total. You can download and compile the project at every step.
Contents
Application Framework. Why should I care? Create the simplest module-independent application framework Application Layout Introduce Actions Improving the Application Framework by using Developer Express components Add Developer Express Navbar control Add Developer Express Bars library Create Developer Express Grid module Add Printing capability into the Application FrameworkApplication Framework. Why do I need it?
Borland has introduced into the VCL Library many useful classes to help us build Windows Form applications fast and perfectly. So why should we add another layer to our code? There was a time when I did not even think about such things.
Almost 10 years ago, I joined a company that developed Contact and Sales management systems. At that time, they used VB+MS SQL for their development. However, a month after I joined, Borland released Delphi 1, the first RAD tool with a true object oriented language. It was decided to use Delphi for the next large project. We were really excited about it. We decided that we would have one application for all our modules, so we could reuse our code better. We were young. Most of us, including me, had just graduated from University. We worked like mad dogs coding dozens and dozens of specs for different modules. Everything went well, until we started to test our application in the real environment. Suddenly, we found that fixing bugs was not such an easy task as we had thought. Fixing a bug in one module was producing several bugs in other modules. Coding new modules was taking more and more time. The logic of our menu/toolbar system that were using for dozens of different modules became a complete nightmare. Nobody was able to understand it since it contained a lot of "case/switch" operations, loads of "if" statements etc. We were only able to finish the project by everybody putting in enormous amounts of personal time. When the project finished (it took about a year) everybody was extremely tired and exhausted. Most of the developers left the company for a vacation and never came back!
I will lie if I say that we only had problems because we did not use an Application Framework. There were a lot of mistakes made during the project. I guess we made nearly all the possible mistakes that could be made while working on a software project. Of course we never improved our code. Automatic tests - what were they? At that time, we had not heard about them and we hardly did any testing at all. Everybody only cared about their own modules and the shared code was a real mess. I could go on, but I'm sure you know what I'm saying.
However, I know for sure that not having Application Framework is one of the main reasons we had problems during development that could have been resolved fairly easily. When the project was almost finished, I found time and looked at the most of the modules. I was pretty surprised. Much of the required functionality of every module was quite common, although it had been implemented in different ways.
The next time I participated in a similar project, I pushed everybody to spend several days on creating a very simple framework. It allowed us:
to add/remove modules by adding/removing one line of code to share common functionality between modules unify menu/toolbars usage.The time spent on coding this layer was paid back many times during further development. From that time, I have used a modification of that framework in most of my Windows applications. While working at Developer Express, I have looked at the code of projects written by our customers. There have been some good approaches but some implementations were not good. There were some projects that reminded me of my first experiments in writing large applications. Sometimes people were fighting with introducing inheritance in the modules, Menu and ToolBar systems etc. I feel that this article will definitely help them enormously. Those who have already written their own Application Framework and use it successfully may well be able to borrow some ideas and code. We at Developer Express would be happy to know that this article will make your life a little easier (helping developers is the main reason we are working here at Developer Express).
Create the simplest module-independent application framework
In this first step, we will create an Application Framework library that will allow the creation of independent Modules. The main form, on which modules will be shown, will not know anything about the content of such modules. The Modules themselves will not know where they are shown and located. It will allow you to use the module within different parts of your application(s) and develop and/or test modules separately from the main application code. You and your team will get the impression and feeling that your application is well written. This is a more psychological thing, but it is very effective.
Application Layout
Here is the typical layout of an SDI application, first introduced in MS Outlook.
The menu/toolbar system is marked in blue, the navigation panel in yellow, the status bar/panel in green and the working area in gray.
Let's create an application using this layout. It will contain two modules: Module 1 and Module 2.
In this application, we will use a standard menu, a Panel control docked to the left with containing a list box (to create the navigation area). To create the working area (in gray), we will add a Panel that has its dock property set to fill the area. Finally we will place a splitter control between the navigator and the working area. To simplify the task, we will not include a task bar into our application at this time.
The current task is to create an application framework that allows creation of independent modules with a developer being able to add/remove a single line of code for adding/removing each module
All modules within applications written based on our framework will be inherited (directly or indirectly) from TfrmCustomModule. This class is inherited from Delphi's TFrame class. The main form class will only know about the TfrmCustomModule class and it should not have a clue about its descendants.
In the current step, we will not put any functionality into the CustomModule class. As you may see it just adds an onDestroy event. We will need it later in the module registration unit.
[Delphi]Since it is a very bad approach to create all modules on application start-up, we need to create a module registration unit. I named it modules.pas. There are two classes in it: TModuleInfo, TModuleInfoManager.
The TModuleInfo class contains the module's name and module class properties. We will use the module name for identification purposes and the module class for creating an instance of the module/frame class when we need to show the module.
The TModuleInfoManager class contains a list of registered modules in our application. You should use the RegisterModule method to register a new module. The ShowModule method will show the module on a particular windows control. The Count and Items properties let us examine all registered modules.
You should use the ModuleInfoManager global function to get access to the TModuleInfoManager object.
Here is the interface part of the modules.pas unit. Please download the Step1 application source to review its implementation section.
[Delphi]Now we need set-up our menu system and navigation controls, so that the end user may navigate through our modules.
[Delphi]The last step is to create new modules and register them into our Application Framework
[Delphi]Summary
As you can see, with just a little code having been written, we have achieved our aim of creating a framework with independent modules.
Of course, this Application Framework doesn't have a lot of features and in the real world you will need to enlarge it. For example, in most applications I've written, there was a need to provide module security. Based on security privileges, the module may be accessible or not by the end-user. This is quite easy to introduce by just writing code in the module registration method. The base module doesn't implement any features and this is not normal either. In most cases, you will need to introduce functionality directly into the base module. The only thing you have to remember is that any feature introduced into the base module will appear in the rest of the modules automatically. Thus, you want to provide a module inheritance scheme, for example: CustomModule -> CustomDBModule -> CustomGridModule etc., where every module adds functionality.
Here is the link to the source code of the application written in Delphi.
Introduce Actions
In the previous steps we built a small Application Framework that allows us to create independent modules. Now it is time to think about adding functionality to the modules and the first problem that we need to resolve is how to create the layer between UI objects in the main form and the business code in the modules.
In other words, we want to have a Menu and Toolbar system on the main form. Menu items and toolbar buttons need their visible, enable and other properties to reflect the business logic contained in the module currently displayed. Menu items and toolbar items should not know details of the module displayed and modules should not be aware of the existence of menus and toolbars at all. We even want to be able to change UI controls, e.g. move from a standard menu system to the Developer Express ExpressBars Library or vice versa without changing the code in the modules. Also, we want to be able to test module functionality in a test engine that does not even create a main form.
Basically, we need one more layer between the UI on the main form and the business code in the modules. I call this layer Actions.
VCL has a native Action layer. We can't use it as is though, because it would destroy modules independently from our application. However, we can write code around the VCL Actions library to do everything we need.
The action module is really simple using the VCL actions library. First, create a new data module class. Drop a TActionManager component on it, write code within the TactionManager's Execute event and write several lines of code around data module class. To add a new action, you just have to create a new action in the ActionManager component. To bind the action with the UI control, you have to assign its Action property to the appropriate action component, Your UI object has to support Actions, of course. Standard VCL and Developer Express controls all support actions technology.
[Delphi]Finally, we need to add some functionality to the CustomModule class. To register the actions supported, you nedd to call the RegisterAction method inside the overridden RegisterActions method. The IsActionSupported method returns whether the action is supported or not. Override UpdateActionsState to change the action's Enabled and IsDown properties.
Here is the interface section of the modified CustomModule.pas unit:
[Delphi]Here is the small example of using Actions in a TfrmCustomModule descendant
[Delphi]Summary
Here is the link to the source code of the application written in Delphi.
Improving the Application Framework by using Developer Express components
Add Developer Express Navbar control
We created the first version of the Application Framework in the previous step and it works fine from our point of view. However, if we were to show it to the boss or our end-user, they would laugh at us. A Listbox is not the best choice for a navigation control in modern applications since Windows users expect state of the art UI controls. Developer Express provides the ExpressNavBar control with over ten different paint styles to enhance the display. It will allow you to give your application a modern look.
The NavBar library has easy to use designers that will help you to set up the control at design-time. Unfortunately however, we have to forget the designers and do everything by code since the main module must not know about the modules we are going to introduce into the system (and we want to manage modules by adding/removing a single line of code).
First, we have to introduce additional features into our Application Framework. NavBar controls divide items into categories, so we have to introduce categories into our module registration classes. Furthermore, we want to have images for items and groups in the Navbar control, as it will radically improve the look of the application. Thus, we need to introduce image properties in our registration classes also.
Here are the changes that we have to do in the Modules.pas unit:
Add class TCategoryInfo
[Delphi]Add Category and ImageIndex properties to the TModuleInfo class. This is so we can save information about the category to which the module belongs and save its image index to show the appropriate image in the NavBar control. Here are the changes to the TModuleInfo class:
[Delphi]Add CategoryCount, Categories properties and AddCategory, GetCategoryByName methods to the TModuleInfoManager class. It will allow us to add categories to our Framework and retrieve them. We will use it in the next step.
[Delphi]The next step is to create NavBar control groups, items and links accordingly to the modules registered in our Application Framework. We have to change the RegisterModules method in the main form unit
[Delphi]Here is the code in NavBar control's link click event handler:
[Delphi]The last step is to Register Categories in the Application Framework. It may be done, for example, in the initialization section of the main form
[Delphi]Summary
Now our application looks much better because of using a Developer Express NavBar control as the navigation control.
Here is the link to the source code of the application written in Delphi. You need the Developer Express ExpressNavBar control installed in your environment to be able to compile and run this application.
Add Developer Express Bars library
Now it is time to replace the old-fashioned standard menu and toolbar system with another one. Because of our Actions layer, this is not a big problem. Whichever library we decide to choose, we only need to modify the main form. Here we will show how to migrate to the Developer Express ExpressBars Library.
We are using VCL Actions technology, so before migrating to another Menu and Toolbar system, you have to check whether it supports VCL Actions technology. ExpressBars support's VCL Actions.
Drop a TdxBarManager component on the main form. Use the TdxBarConverter to replace the standard main menu with the ExpressBars main menu.
We will use the TdxBarListItem class for navigation between modules. We need to modify the RegisterModules method
[Delphi]Here is the code for the BarListItemClick event
[Delphi]The last step is to create the toolbars and bar items needed, place the bar items where required and bind them to VCL actions at design-time. That is it.
Summary
As you can see, we did all modifications to the main module only and the task was quite easy. How do you migrate from one Menu&ToolBar library to another in your application? I guess it could be a real pain.
Here is the link to the source code of the application written in Delphi. You should have the Developer Express ExpressNavBar control and the ExpressBars Library installed in your environment to be able to compile and run this demo.
Create Developer Express Grid module
We have greatly improved the appearance of our application by replacing the standard controls with the Developer Express XtraNavBar and XtraBars libraries. Now it is time to think about improving our framework in terms of module content. In your application, you are unlikely to inherit the "end-user" module from the CustomModule directly. In most applications, there are usually several modules that show objects/records in a list. Generally, we use a Grid control for this purpose and this is typically the central control in an application. You will need to write code around the grid, e.g. showing/hiding the column customization window etc. Of course, it makes no sense to code this functionality for every module containing a grid control. We will create a CustomGridModule module. The Developer Express ExpressQuantumGrid will be located on this module. As well as grid actions, we will introduce Export actions. Although we will add support for Export actions in the base module, by default these actions will be disabled, so actual Grid Modules have to override methods to re-enable them.
To introduce the Grid Module, we need to make modification to the Main Form, the Action DataModule and create a new module: CustomGridModule. It has to be derived from CustomModule.
We need to modify the Main Form and Action Data Module to add actions, ExpressBars items and link actions to ExpressBars items as we have already done earlier. The process is absolutely the same.
A more interesting task is to add support for Export actions into CustomModule. By default, the export actions will then be visible to all modules, but they will be disabled. To enable them, the module have to override two methods: SupportedExportTypes and DoExport. Here is the code that implements the Export action support in CustomModule.
[Delphi]The implementation of Export Actions in the CustomGridModule is quite obvious:
[Delphi]To implement Grid Actions, we will use the TcxGridOperationHelper class that you will find in the cxGridUIHelper.pas file that is shipped with the product. It implements standard grid operations for different Views.
[Delphi]Summary
During this step, we have created the base list module for our Application Framework.
Here is the link to the source code of the application written in Delphi. It contains an additional module derived from the base grid module. You should have the Developer Express ExpressNavBar control, ExpressBars and ExpressQuantumGrid Libraries installed in your environment to be able to compile and run the application.
Add Printing capability into the Application Framework
The last feature that we will add into our Application Framework is a printing capability. We will introduce Print Actions and implement them in the base module in the same way as we did for Export Actions.
After adding Print Actions support into CustomModule, we will have three additional protected virtual methods: HasPrinting, DoPrint and DoPreview.
These methods are overridden in CustomGridModule to add the ability to print the ExpressQuantumGrid. Drop a TdxComponentPrinter component from the ExpressPrinting Library on the CustomGridModule and create a report link for the Grid located on the module. With this, introducing print support for CustomGridModule is a very easy task.
[Delphi]Summary
With this step, we have introduced Print support into the Application Framework and we implemented it for the base Grid module.
Here is the link to the source code of the application written in Delphi. You should have the Developer Express ExpressNavBar control, ExpresPrinting, ExpressBars and ExpressQuantumGrid Libraries installed in your environment to be able to compile and run this demo.
本文地址:http://com.8s8s.com/it/it4352.htm