分享
 
 
 

Simple_Application_Framework_for_VCL

王朝delphi·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

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.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有