Simple_Application_Framework_for_VCL

类别:Delphi 点击:0 评论:0 推荐:
 

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:

ExpressNavBar Library Ver 1.x ExpressBars Library Ver 5.x ExpressQuantumGrid Suite Ver 4.x ExpressPrinting Library Ver 2.x

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 Library

We 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 Framework

Application 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]

unit CustomModule;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs;

type
  TfrmCustomModule = class(TFrame)
  private
    FOnDestroy: TNotifyEvent;
  public 
    destructor Destroy; override;
    property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy;
  end;

  TfrmCustomModuleClass = class of TfrmCustomModule;

implementation

{$R *.dfm}

{ TfrmCustomModule } 
destructor TfrmCustomModule.Destroy;
begin 
  if Assigned(OnDestroy) then
    OnDestroy(self);
  inherited
end

end

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]

unit Modules;

interface 

uses Classes, Controls, CustomModule;

type 

// Contains information about a module 
TModuleInfo = class 
private 
  FModuleClass: TfrmCustomModuleClass;
  FModule: TfrmCustomModule;
  FName: string;
  function GetActive: Boolean;
protected 
  // Create the module instance 
  procedure CreateModule;
  // Destroy the module instance 
  procedure DestroyModule;
public 
  constructor Create(const AName: string; AModuleClass: TfrmCustomModuleClass);
  destructor Destroy; override;
  //Make the module invisible 
  procedure Hide;
  //Show the module on a particular control 
  procedure Show(AParent: TWinControl);

  // Return True if the module is active currently 
  property Active: Boolean read GetActive;
  property Module: TfrmCustomModule read FModule;
  property Name: string read FName;
end

//Manage module info classes 
TModuleInfoManager = class 
private 
  FModuleList: TList;
  FActiveModuleInfo: TModuleInfo;

  function GetCount: Integer;
  function GetItem(Index: Integer): TModuleInfo;
public 
  constructor Create;
  destructor Destroy; override;

  // Return the module info by its name 
  function GetModuleInfoByName(const AName: string): TModuleInfo;
  // Register Module module in the manager 
  procedure RegisterModule(const AName: string; AModuleClass: TfrmCustomModuleClass);
  // Show the module on the control 
  procedure ShowModule(const AName: string; AParent: TWinControl);

  // The module info of the currently active module 
  property ActiveModuleInfo: TModuleInfo read FActiveModuleInfo;
  // Return the number of registered modules 
  property Count: Integer read GetCount;
  property Items[Index: Integer]: TModuleInfo read GetItem; default;
end;

// Return the instance of the global TModuleInfoManager object 
function ModuleInfoManager: TModuleInfoManager;

Now we need set-up our menu system and navigation controls, so that the end user may navigate through our modules.

[Delphi]

constructor TfrmMain.Create(AOwner: TComponent);
begin 
  inherited Create(AOwner);
  // Set up menu and navigation controls 
  RegisterModules;
  // Show the first module at start-up 
  if ModuleInfoManager.Count > 0 then 
    ShowModule(ModuleInfoManager[0].Name);
end;

procedure TfrmMain.RegisterModules;
var
  I: Integer;
  AMenuItem: TMenuItem;
begin 
  //Go through all modules 
  for I := 0 to ModuleInfoManager.Count - 1 do 
  begin
    // Add item into list box 
    lblNavigation.Items.Add(ModuleInfoManager[I].Name);
    // Add new sub menu item 
    AMenuItem := TMenuItem.Create(self);
    mView.Add(AMenuItem); 
    AMenuItem.Caption := ModuleInfoManager[I].Name;
    // Use tag to identify the module 
    AMenuItem.Tag := I;
    AMenuItem.OnClick := mViewClick;
  end;
end;

procedure TfrmMain.ShowModule(const AName: string);
begin 
  // Lock windows updates for the main window during showing the module 
  LockWindowUpdate(Handle);
  try 
    ModuleInfoManager.ShowModule(AName, pnlWorkingArea);
  finally 
    // Refresh the main window 
    LockWindowUpdate(0);
    RedrawWindow(Handle, nil, 0, RDW_ERASE or RDW_FRAME or RDW_INVALIDATE or RDW_ALLCHILDREN);
  end;
end;

procedure TfrmMain.lblNavigationClick(Sender: TObject);
begin 
  if lblNavigation.ItemIndex < 0 then exit;
  // Show the module 
  ShowModule(lblNavigation.Items[lblNavigation.ItemIndex]);
end;

procedure TfrmMain.mViewClick(Sender: TObject);
begin 
  // Show the module 
  ShowModule(lblNavigation.Items[TMenuItem(Sender).Tag]);
end;

The last step is to create new modules and register them into our Application Framework

[Delphi]

unit module1;

interface 

uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, custommodule, StdCtrls, modules;

type 
  //The module has to be inherited from the custom module class 
  TfrmModule1 = class(TfrmCustomModule)
    Label1: TLabel;
  private 
    { Private declarations } 
  public 
    { Public declarations } 
  end;

implementation 

{$R *.dfm} 

initialization 
  // Register the class within our Application Framework 
  ModuleInfoManager.RegisterModule('Module1', TfrmModule1);

end.

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]

unit dmActions;

interface 

uses 
  SysUtils, Classes, AppEvnts, XPStyleActnCtrls, ActnList, ActnMan;

type 
  TdmAppActions = class(TDataModule)
    ActionManager: TActionManager;
    Action1: TAction;
    Action2: TAction;
    Action3: TAction;
    procedure ActionManagerExecute(Action: TBasicAction;
      var Handled: Boolean);
  private 
    function GetActionCount: Integer;
    function GetAction(Index: Integer): TBasicAction;
    // VCL actions disable UI controls if they have no events assigned  
    // The easiest solution to avoid this is to assign a dummy event 
    procedure DoFakeVCLAction(Sender: TObject);
    procedure FakeVCLActions;
  public 
    constructor Create(AOwner: TComponent); override;
    // Return the action count 
    property ActionCount: Integer read GetActionCount;
    property Actions[Index: Integer]: TBasicAction read GetAction;
  end;

var 
  dmAppActions: TdmAppActions;

// Returns the global instance of Actions class 
function AppActions: TdmAppActions;

implementation 

uses Forms, Modules;


{$R *.dfm} 

// returns the global instance of Actions class 
function AppActions: TdmAppActions;
begin 
  if(dmAppActions = nil) then 
    dmAppActions := TdmAppActions.Create(Application);
  Result := dmAppActions;
end;

{ TdmAppActions } 
constructor TdmAppActions.Create(AOwner: TComponent);
begin 
  inherited Create(AOwner);
  FakeVCLActions;
end;

procedure TdmAppActions.FakeVCLActions;
var 
  I: Integer;
begin 
  for I := 0 to ActionCount - 1 do 
    Actions[I].OnExecute := DoFakeVCLAction;
end;

procedure TdmAppActions.DoFakeVCLAction(Sender: TObject);
begin 
  //do nothing 
end;

function TdmAppActions.GetActionCount: Integer;
begin 
  Result := ActionManager.ActionCount;
end;

function TdmAppActions.GetAction(Index: Integer): TBasicAction;
begin 
  Result := ActionManager.Actions[Index];
end;

// Handler for Execute event of ActionManager component 
procedure TdmAppActions.ActionManagerExecute(Action: TBasicAction;
  var Handled: Boolean);
begin 
  // Call the ExecuteAction method of the currently showing module 
  if (ModuleInfoManager.ActiveModuleInfo <> nil) then 
    Handled := ModuleInfoManager.ActiveModuleInfo.Module.ExecuteAction(Action);
end;

end.

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]

unit CustomModule;

interface 

uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ActnList;

type 
  TActionNotification = procedure(Action: TBasicAction) of object;

  TfrmCustomModule = class(TFrame)
  private 
    // The list of supported actions 
    FSupportedActionList: TList;
    FOnDestroy: TNotifyEvent;

    // Returns the event handler for the action 
    function GetNotificationByAction(Action: TBasicAction): TActionNotification;
  protected 
    // Register the supported action 
    procedure RegisterAction(const Action: TBasicAction; ANotification: TActionNotification);
    // The descendant classes have to override this method to register supported actions 
    procedure RegisterActions; virtual;
  public 
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    // Override the base Tframe's ExecuteAction behaviour 
    function ExecuteAction(Action: TBasicAction): Boolean; override;

    // Returns True if the module supports the action 
    function IsActionSupported(Action: TBasicAction): Boolean;
    // Make all supported actions visible and unsupported actions invisible on module activation 
    procedure UpdateActionsVisibility; virtual;
    // Updates action states 
    procedure UpdateActionsState; virtual;
    property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy;
  end;

  TfrmCustomModuleClass = class of TfrmCustomModule;

Here is the small example of using Actions in a TfrmCustomModule descendant

[Delphi]

unit module1;

interface 

uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, custommodule, StdCtrls, modules;

type 
  //The module has to be inherited from the custom module class 
  TfrmModule1 = class(TfrmCustomModule)
    Label1: TLabel;
    CheckBox1: TCheckBox;
    Label2: TLabel;
    Edit1: TEdit;
    procedure CheckBox1Click(Sender: TObject);
  private 
    // Handler for Action1 
    procedure DoAction1(Action: TBasicAction);
  protected 
    // Register supported actions 
    procedure RegisterActions; override;
  public 
    // Update action states 
    procedure UpdateActionsState; override;
  end;

implementation 

uses dmActions;

{$R *.dfm} 

{ TfrmModule1 } 

procedure TfrmModule1.RegisterActions;
begin 
  inherited RegisterActions;
  // Register Action1 into the supported actions list  
  RegisterAction(AppActions.Action1, DoAction1);
end;

procedure TfrmModule1.UpdateActionsState;
begin 
  inherited UpdateActionsState;
  // Make action1 enabled if checkbox1 is unchecked 
  AppActions.Action1.Enabled := not CheckBox1.Checked;
end;

// Show the times that Action1 has been performed in Edit1 
procedure TfrmModule1.DoAction1(Action: TBasicAction);
begin 
  Edit1.Text := IntToStr(StrToInt(Edit1.Text) + 1);
end;

procedure TfrmModule1.CheckBox1Click(Sender: TObject);
begin 
  UpdateActionsState;
end;

initialization 
  // Register the class within our Application Framework 
  ModuleInfoManager.RegisterModule('Module1', TfrmModule1, 0, 0);

end.

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]

// Contains information about the category 
TCategoryInfo = class
private 
  FName: string;
  FImageIndex: Integer;
  function GetIndex: Integer;
public 
  constructor Create(AName: string; AImageIndex: Integer);
  property Index: Integer read GetIndex;
  property ImageIndex: Integer read FImageIndex;
  property Name: string read FName;
end;

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]

// Contains information about the module 
TModuleInfo = class 
private 
    …
  FCategory: TCategoryInfo;
  FImageIndex: Integer;
    …
public 
  constructor Create(const AName: string;
                     AModuleClass: TfrmCustomModuleClass;
                     ACategory: TCategoryInfo;
                     AImageIndex: Integer = -1);
    …
  property Category: TCategoryInfo read FCategory;
  property ImageIndex: Integer read FImageIndex;
end;

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]

//Manage module info classes 
TModuleInfoManager = class 
private 
    ...
  FCategoryList: TList;

  function GetCategoryCount: Integer;
  function GetCategory(Index: Integer): TCategoryInfo;
    …
public 
    …
  // Add new category 
  procedure AddCategory(const AName: string; ImageIndex: Integer);
  // Returns the CategoryInfo object by its name 
  function GetCategoryByName(const Name: string): TCategoryInfo;
  // Register Module module in the manager 
  procedure RegisterModule(const AName: string; AModuleClass: TfrmCustomModuleClass;
    ACategory: TCategoryInfo = nil; AImageIndex: Integer = -1);

  // Return the number of categories 
  property CategoryCount: Integer read GetCategoryCount;
  property Categories[Index: Integer]: TCategoryInfo read GetCategory;
end;

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]

procedure TfrmMain.RegisterModules;
var 
  I: Integer;
  ANavBarGroup: TdxNavBarGroup;
  ANavBarItem: TdxNavBarItem;
    …
begin 
  //Go through all categories 
  for I := 0 to ModuleInfoManager.CategoryCount - 1 do 
  begin 
    // Add NavBar group 
    ANavBarGroup := NavBar.Groups.Add;
    // Set NavBar group caption 
    ANavBarGroup.Caption := ModuleInfoManager.Categories[I].Name;
    // Set NavBar group large image 
    ANavBarGroup.LargeImageIndex := ModuleInfoManager.Categories[I].ImageIndex;
    // Use large images to show them in the NavBar group 
    ANavBarGroup.UseSmallImages := False;
  end;
  //Go through all modules
  for I := 0 to ModuleInfoManager.Count - 1 do
  begin 
    // Add new item into navBar 
    ANavBarItem := NavBar.Items.Add;
    // Set NavBar item caption 
    ANavBarItem.Caption := ModuleInfoManager[I].Name;
    // Set NavBar item image index 
    ANavBarItem.SmallImageIndex := ModuleInfoManager[I].ImageIndex;
    // Use tag to identify the module 
    ANavBarItem.Tag := I;
    // Add the item into appropriate NavBar group 
    NavBar.Groups[ModuleInfoManager[I].Category.Index].CreateLink(ANavBarItem);
    …
  end;
end;

Here is the code in NavBar control's link click event handler:

[Delphi]

procedure TfrmMain.NavBarLinkClick(Sender: TObject;
  ALink: TdxNavBarItemLink);
begin 
  // Show the module 
  ShowModule(ModuleInfoManager.Items[ALink.Item.Tag].Name);
end;

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]

initialization 
  ModuleInfoManager.AddCategory('Category 1', 0);

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]

procedure TfrmMain.RegisterModules;
var 
  I: Integer;
    …
begin 
    …
  //Go through all modules 
  for I := 0 to ModuleInfoManager.Count - 1 do 
  begin 
    …
    // Add the item into the bar's List item 
    barListItem.Items.Add(ModuleInfoManager[I].Name)
  end;
end;

Here is the code for the BarListItemClick event

[Delphi]

procedure TfrmMain.barListItemClick(Sender: TObject);
begin 
  // Show the module 
  ShowModule(ModuleInfoManager.Items[barListItem.ItemIndex]);
end;

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]

  TExportType = (etHTML, etXML, etXLS, etText);
  TExportTypes = set of TExportType;

  TfrmCustomModule = class(TFrame)
    …
  protected 
    …
    // The descendant classes have to override this method to register the actions supported 
    procedure RegisterActions; virtual;
    // Do the export based on the export type 
    procedure DoExport(AExportType: TExportType; const AFileName: string); virtual;
    // Returns the supported export types 
    function SupportedExportTypes: TExportTypes; virtual;
    …
  end;

The implementation of Export Actions in the CustomGridModule is quite obvious:

[Delphi]

procedure TfrmCustomGridModule.DoExport(AExportType: TExportType; const AFileName: string);
begin 
  case AExportType of 
    etHTML: ExportGrid4ToHTML(AFileName, Grid);
    etXML: ExportGrid4ToXML(AFileName, Grid);
    etXLS: ExportGrid4ToExcel(AFileName, Grid);
    etText: ExportGrid4ToText(AFileName, Grid);
  end;
end;

function TfrmCustomGridModule.SupportedExportTypes: TExportTypes;
begin 
  Result := [etHTML, etXML, etXLS, etText];
end;

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]

constructor TfrmCustomGridModule.Create(AOwner: TComponent);
begin 
  inherited Create(AOwner);
  FGridOperationHelper := TcxGridOperationHelper.Create(self);
  FGridOperationHelper.Grid := Grid;
  FGridOperationHelper.OnUpdateOperations := DoGridUpdateOperations;
  FGridOperationHelper.OnCustomizationFormVisibleChanged := DoGridUpdateOperations;
end;

procedure TfrmCustomGridModule.RegisterActions;
begin 
  inherited RegisterActions;
  RegisterAction(AppActions.actionGridGrouping, DoActionGridGrouping);
    …
  RegisterAction(AppActions.actionGridColumnsCustomization, DoActionGridColumnsCustomization);
end;

procedure TfrmCustomGridModule.UpdateActionsState;
begin 
  inherited UpdateActionsState;
  AppActions.actionGridGrouping.Enabled := FGridOperationHelper.IsOperationEnabled[GROP_SHOWGROUPINGPANEL];
  AppActions.actionGridGrouping.Checked := FGridOperationHelper.IsOperationShowing[GROP_SHOWGROUPINGPANEL];
  AppActions.actionGridColumnsCustomization.Enabled := FGridOperationHelper.IsOperationEnabled[GROP_SHOWCOLUMNCUSTOMIZING];
  AppActions.actionGridColumnsCustomization.Checked := FGridOperationHelper.IsOperationShowing[GROP_SHOWCOLUMNCUSTOMIZING];
end;

function TfrmCustomGridModule.FocusedView: TcxCustomGridView;
begin 
  Result := Grid.FocusedView;
end;

procedure TfrmCustomGridModule.DoActionGridGrouping(Action: TBasicAction);
begin 
  FGridOperationHelper.DoShowGroupingPanel(not FGridOperationHelper.IsGroupingPanelShowing);
end;

procedure TfrmCustomGridModule.DoActionGridColumnsCustomization(Action: TBasicAction);
begin 
  FGridOperationHelper.DoShowColumnCustomizing(not FGridOperationHelper.IsColumnsCustomizingShowing);
end;

procedure TfrmCustomGridModule.DoGridUpdateOperations(Sender: TObject);
begin 
  UpdateActionsState;
end;

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]

// Returns True if the module supports printing 
function TfrmCustomGridModule.HasPrinting: Boolean;
begin 
  Result := True;
end;

procedure TfrmCustomGridModule.DoPrint;
begin 
  printerLinkGrid.Print(False, nil);
end;

procedure TfrmCustomGridModule.DoPreview;
begin 
  printerLinkGrid.Preview(True);
end;

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