(本文章是翻譯自:http://www.devexpress.com/Support/BestPractices/VCL/SAP/
發表在2004 11,12月開發高手, 但雜誌上有刪節
轉載請與作者聯系)
在這篇文章裏,我們將討論一種通過Borland VCL 更好地構建Windows客戶端應用程序的方法。最後,我們將產生一個運行庫以及一個能幫我們輕鬆實現模塊和用戶界面分離的應用程序的樣例。
本文分爲兩部分:
一、 在開發簡單應用程序框架部分,我們沒有采用Developer Express library,所以那些非Developer Express的客戶的VCL開發者可以使用;
進一步,採用Developer Express libraries來改善應用程序框架。如果要編譯運行這個程序,你必須安裝下列的Developer Express libraries:
| |||||||||||||||||||||||||||||||||||
本文摘要分類:
n 一種更好的程序設計方法——在你的程序主窗口中使用 Frames(框架)
n 在模塊繼承中使用Frames(框架)
n 使用原生的VCL Actions 層
n 要用多長時間才能將一個菜單和工具欄的功能添加到另外一個地方呢?這個程序框架能幫你很快完成,因爲你需要做的只是簡單的在一個地方修改代碼即可
n 在運行時設置 ExpressNavBar 控件
n 在應用程序框架中使用ExpressBars 和 ExpressGrid
n 使用ExpressPrinting 來增加打印功能
我們從先一個簡單的任務開始,逐步開發我們的應用程序框架,然後,每一步都將加入新功能,這樣能讓代碼的變化儘量保持簡單直接。整個過程分爲6步,你可以下載和編譯每一步的代碼。
目錄
一、 爲何要關心應用程序框架?
二、 創建最簡單的模塊獨立應用程序框架;
1. 應用程序界面佈局;
2. Actions 介紹;
三、 使用Developer Express 控件改善應用程序框架;
1. 增加Developer Express Navbar運行庫;
2. 增加Developer Express Bars運行庫;
3. 創建Developer Express Grid 模塊;
4. 爲應用程序框架增加打印功能;
一、 爲何我要關心應用程序框架?
Borland公司引進了許多實用的類到VCL 中,從而能夠幫助我們快速完善地建立 Windows From 應用程序。那麼,爲什麼我們還要在我們的代碼裏面多加入一層處理代碼呢?在很長時間內我都沒有想過這個問題。
大概十年前,我加入了一個開發客戶銷售管理系統的公司,當時,他們用VB+MS SQL開發系統,然而,在我加入公司一月後,Borland公司發佈了Delphi1,這是第一個真正面向對象的RAD工具。當公司決定採用Delphi 來開發下一個項目時,我們都感到十分興奮,我們決定所有模塊都共享一個Application,這樣能夠更好共享彼此的代碼。當時公司的大部分人,包括我在內都是剛剛從大學畢業的年輕人,我們投入地寫了一段段不同的代碼。一切進展得很順利,直到我們將代碼放到一個真實的環境中測試。突然,我們發現Bug修改不如所想的那般容易。修正一個模塊Bug的時候,又會在其它模塊中產生幾個新的bug,開發新的模塊,必須花費我們越來越多的時間以保證能與原來的模塊兼容,許多模塊代碼邏輯混合在菜單/工具欄下面,成了我們的噩夢,沒有人能夠理解那一大堆的“Case/Switch”、加載系統時候的If判斷等等類似的操作。爲了完成這個項目,我們只能投入大部分自己的私人時間。在項目完成的時候(大約花費了一年時間),每個人都非常疲憊。大部分開發人員離開公司去度假,而且再也沒有回來。
如果說是因爲我們沒有使用應用程序框架才導致了那麼多問題,那是在撒謊。其實在項目實施過程中存在大量的錯誤。我估計在我們的開發過程中,幾乎犯下了所有可能犯的錯誤。我們也沒有嘗試改進我們的代碼。“自動測試”——是什麼東西?在那個時候,我們根本沒有聽過它。而且我們根本沒有做任何測試。所有人只關心他負責的那部分模塊,代碼共享簡直是亂七八糟。我還可以繼續舉些例子,但我想你已經明白我要表達什麼了。
不管怎樣,我知道沒有使用一個應用程序框架是我們開發中遇到的主要問題之一,而這個問題其實是非常容易解決的。當項目臨近結束的時候,我花了點時間檢查了大部分模塊的代碼。我很詫異,每個模塊的大部分功能需求相當的簡單,即使實現它們的方式五花八門。
在下一次我參與一個類似的項目,我督促大家花幾天時間創建了一個非常簡單的應用程序框架。這個框架允許:
n 通過一行代碼就能增加刪除一個模塊
n 在模塊之間共享函數庫
n 統一的 菜單/工具欄 用法
開發這一層代碼所花費的時間,在開發新功能時候將被成倍的節省回來。從那時開始,在我開發的大部分程序,都將這個應用程序模塊略微修改加進程序中。當我在Developer Express 公司任職的時候,我瀏覽了其它同事的一些代碼,覺得一些實現方法很好,同時也有一部分代碼實現得不夠理想。在這裏的一些項目讓我不由地想起了第一次寫大型項目的情景。有時,一些同事反對引入繼承到模塊、菜單/工具欄實現中來。我想這篇文章會帶給他們很大的幫助。對於那些已經寫了自己的應用程序框架,並且已經成功使用的人,相信也能從本文中得到一些啓發和有用的代碼。我們Developer Express公司的人很樂於聽到通過這篇文章讓你的生活更愉快輕鬆了(我們在Developer Express 工作的一個主要原因就是幫助開發者)
二、 創建最簡單的模塊獨立應用程序框架
第一步,我們將創建一個能夠獨立構建模塊的應用程序框架。主窗體並不知道它將顯示什麼內容,而模塊也不清楚它將被顯示在哪裏。這樣允許你在不同程序的不同部分都能使用該模塊。而且可以和主程序並行使用測試模塊,如此一來,你和你的團隊將感到你們的程序非常棒,這很多時候只是一個心理上的感覺而已,但實際卻非常有效。
1. 應用程序界面佈局
MS Outlook 第一次引入瞭如下的經典SDI應用程序佈局:
菜單和工具欄用藍色部分標註,導航面板用黃色表示,狀態欄用綠色表示,而工作區則灰色表示。
讓我們根據這個佈局創建一個應用程序,它將包括兩個模塊:模塊1和模塊2。
在這個程序中,我們將使用一個標準的菜單,一個停靠在左邊包含一個Listbox控件(用於空出導航區的空間)的Panel控件。爲了創建工作區(灰色部分),我們將增加一個Panel控件,將它的dock屬性設置爲充滿所有空間。最後,我們在導航區和工作區之間放置一個splitter控件。爲了簡化這次工作,我們將不在程序中使用任務欄。
現在,我們的目標是要創建一個獨立功能模塊的應用程序框架,開發者用一行代碼就能增加或移除其中的每一個模塊。
基於我們的框架的程序的所有模塊都將從TfrmCustomModule 繼承(直接或間接的)。而TfrmCustomModule這個類是繼承自delphi自帶的TFrame 類。主窗體將只是知道TfrmCustomModule類,而對它的子類毫不瞭解。
在當前這一步裏,我們將不會放置太多的功能到CustomModule類中,你將會看到只是加了一個onDestroy的事件。在稍後我們註冊模塊時將需要它。
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
在程序啓動時就創建所有模塊是一個不好的習慣。所以,我們需要創建一個註冊模塊的單元。命名爲 modules.pas 它包含了兩個類: TModuleInfo, TModuleInfoManager。
TModuleInfo 類包含了模塊的名字和模塊中類相關的屬性信息。我們將使用模塊的名字來表示其包含功能,當我們需要顯示那個模塊時,我們將創建對應的模塊/框架 類的實例。
TModuleInfoManager 類包含一個我們程序中所有註冊模塊的註冊列表,你能通過它的 RegisterModule 方法來註冊一個新的模塊。 ShowModule 方法則將會顯示模塊到一個指定的windows控件中。 Count 和 Items 屬性則讓我們可以檢查所有已經註冊了的模塊。
你能使用全局函數 ModuleInfoManager 來訪問 TModuleInfoManager 這個對象。下面是modules.pas 單元的聲明部分。你可以下載 Step1 源程序來參考它的實現部分。
unit Modules;
interface
uses Classes, Controls, CustomModule;
type
//關於模塊包含的信息
TModuleInfo = class
private
FModuleClass: TfrmCustomModuleClass;
FModule: TfrmCustomModule;
FName: string;
function GetActive: Boolean;
protected
//創建模塊實例
procedure CreateModule;
//銷燬模塊實例
procedure DestroyModule;
public
constructor Create(const AName: string; AModuleClass: TfrmCustomModuleClass);
destructor Destroy; override;
//隱藏模塊
procedure Hide;
//顯示模塊到指定的控件中
procedure Show(AParent: TWinControl);
//如果當然模塊是激活的就返回 true
property Active: Boolean read GetActive;
property Module: TfrmCustomModule read FModule;
property Name: string read FName;
end;
//管理模塊的類信息
TModuleInfoManager = class
private
FModuleList: TList;
FActiveModuleInfo: TModuleInfo;
function GetCount: Integer;
function GetItem(Index: Integer): TModuleInfo;
public
constructor Create;
destructor Destroy; override;
//通過模塊的名字返回對應模塊的信息
function GetModuleInfoByName(const AName: string): TModuleInfo;
//註冊模塊到管理器
procedure RegisterModule(const AName: string; AModuleClass: TfrmCustomModuleClass);
//顯示模塊到指定控件中
procedure ShowModule(const AName: string; AParent: TWinControl);
//當前激活模塊的信息
property ActiveModuleInfo: TModuleInfo read FActiveModuleInfo;
//返回當前已註冊模塊的數量
property Count: Integer read GetCount;
property Items[Index: Integer]: TModuleInfo read GetItem; default;
end;
//返回全局TModuleInfoManager對象的實例
function ModuleInfoManager: TModuleInfoManager;
現在我們需要設置我們的菜單系統和導航控件,讓最終用戶可以操縱我們的模塊。
constructor TfrmMain.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
//安裝菜單和導航控件
RegisterModules;
//在啓動時顯示第一個模塊
if ModuleInfoManager.Count > 0 then
ShowModule(ModuleInfoManager[0].Name);
end;
procedure TfrmMain.RegisterModules;
var
I: Integer;
AMenuItem: TMenuItem;
begin
//遍歷所有模塊
for I := 0 to ModuleInfoManager.Count - 1 do
begin
//將每一項增加到列表框中
lblNavigation.Items.Add(ModuleInfoManager[I].Name);
// 增加子菜單項
AMenuItem := TMenuItem.Create(self);
mView.Add(AMenuItem);
AMenuItem.Caption := ModuleInfoManager[I].Name;
//使用 tag 來識別各個模塊
AMenuItem.Tag := I;
AMenuItem.OnClick := mViewClick;
end;
end;
procedure TfrmMain.ShowModule(const AName: string);
begin
//在顯示模塊時,鎖住當前主窗口的刷新
LockWindowUpdate(Handle);
try
ModuleInfoManager.ShowModule(AName, pnlWorkingArea);
finally
//刷新主窗口
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;
//顯示模塊
ShowModule(lblNavigation.Items[lblNavigation.ItemIndex]);
end;
procedure TfrmMain.mViewClick(Sender: TObject);
begin
//顯示模塊
ShowModule(lblNavigation.Items[TMenuItem(Sender).Tag]);
end;
最後一步是創建一個新模塊並且註冊到我們的應用程序框架中。
unit module1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, custommodule, StdCtrls, modules;
type
//新模塊必須從custom module類繼承
TfrmModule1 = class(TfrmCustomModule)
Label1: TLabel;
private
public
end;
implementation
{$R *.dfm}
initialization
//在應用程序框架中註冊模塊包含的類
ModuleInfoManager.RegisterModule('Module1', TfrmModule1);
End.
摘要
如您所見,我們僅僅用了少量代碼就完成了我們的創建一個模塊獨立的應用程序框架的目標。
當然,這個應用程序框架並沒擁有很多的特性,在實際環境中使用的時候,還必須擴充它的功能。例如在我寫過的大多數程序中,必須給模塊提供安全認證的功能。根據用戶權限來決定每個最終用戶能訪問或者不能訪問特定的模塊。 在模塊的註冊部分, 能很簡單的引入這個功能。 在這裏,基模塊沒有實現任何功能,但實際中這也並不常見。在大多數實際案例中,你必須在基模塊中就直接引入一些特定的功能。在這裏,你只要記住,你在基模塊引入的任何功能,都將自動引入到其它的子模塊中。 所以,你必須事先規劃好模塊的繼承方案。 例如:CustomModule -> CustomDBModule -> CustomGridModule… 等等,當然,每個新的模塊都會增加新的功能。
1. Actions 介紹
在上一步,我們實現了一個允許創建獨立模塊的簡單應用程序框架,現在我們將考慮如何新增功能到模塊中,首要問題是,我們要解決如何在主窗體UI和業務邏輯之間添加一個新的層(layer)。
換言之,我們希望在主窗體上有一個菜單和工具欄,菜單裏的項目和工具欄上的按鈕的Visible,Enable以及其它屬性必須能夠反映當前顯示模塊的業務邏輯狀態,菜單項目和工具欄項目並不知道當前顯示模塊的細節,而模塊也根本不知道菜單和工具欄的存在。我們期望能夠改變界面上不同的UI控件而不需要修改模塊中的代碼。如:把標準菜單替改成Developer Express ExpressBar,反之亦然。同時我們還希望能夠在沒有創建程序主窗口的情況下使用測試引擎來測試模塊的功能。
基本上,我們需要在UI和業務邏輯代碼之間引入一個層(layer)或多個層(layer)的代碼, 我們把它叫做Actions層(layer)。
VCL自己有一個原生的Action,但是在這裏,我們似乎不能直接使用它,因爲它會破環我們應用程序中模塊的獨立性。然而,我們可以擴展VCL Actions 來實現我們需要的功能。
通過VCL Actoins 來實現Action模塊非常簡單,首先,我們創建一個DataMoudle,拖一個TActionManager 控件到它上面,在TActionManager 的 Excute事件下面寫代碼同時在DataMoudle 中也添加幾行代碼。要添加新的Action,你只需要在ActionManager 控件中新建一個Action就可,然後要綁定的UI控件的Action屬性設置爲對應的action組件就可。 注意,你使用的UI對象必須支持Actions。當然, 標準的VCL和Developer Express的所有控件都支持Action技術。
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;
// 如果不指定Action事件,綁定的UI控件將是Disable狀態
// 最簡單的解決方法就是給它付一個假的事件
procedure DoFakeVCLAction(Sender: TObject);
procedure FakeVCLActions;
public
constructor Create(AOwner: TComponent); override;
//返回 action 的數目
property ActionCount: Integer read GetActionCount;
property Actions[Index: Integer]: TBasicAction read GetAction;
end;
var
dmAppActions: TdmAppActions;
//返回Actions類的全局實例
function AppActions: TdmAppActions;
implementation
uses Forms, Modules;
{$R *.dfm}
//返回Actions類的全局實例
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
//不執行任何代碼
end;
function TdmAppActions.GetActionCount: Integer;
begin
Result := ActionManager.ActionCount;
end;
function TdmAppActions.GetAction(Index: Integer): TBasicAction;
begin
Result := ActionManager.Actions[Index];
end;
//處理ActionManager控件的Execute 事件
procedure TdmAppActions.ActionManagerExecute(Action: TBasicAction;
var Handled: Boolean);
begin
//調用當前顯示模塊的ExecuteAction方法
if (ModuleInfoManager.ActiveModuleInfo <> nil) then
Handled := ModuleInfoManager.ActiveModuleInfo.Module.ExecuteAction(Action);
end;
end.
最後 我們必須增加一些功能到CustomModule類。爲了註冊支持Actions的功能,你需要調用RegisterAction方法,RegisterAction方法覆蓋了內部默認的RegisterAction方法。調用IsActionSupported方法將會返回是否是支持Action。 我們將覆蓋改寫UpdateActionsState方法來改變action 的Enabled和isDown 屬性。
下面是CustomModule.pas單元的聲明部分:
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
//支持actions的列表
FSupportedActionList: TList;
FOnDestroy: TNotifyEvent;
//返回Action的事件
function GetNotificationByAction(Action: TBasicAction): TActionNotification;
protected
//註冊支持的action
procedure RegisterAction(const Action: TBasicAction; ANotification: TActionNotification);
//繼承這個類的子類必須重寫覆蓋掉這個方法以便可註冊支持的actions
procedure RegisterActions; virtual;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
//重寫父類Tfrmae的ExecuteAction行爲
function ExecuteAction(Action: TBasicAction): Boolean; override;
//如果當前模塊支持action則返回 true
function IsActionSupported(Action: TBasicAction): Boolean;
//在模塊被激活時,所有支持actions將顯示出來,而不支持的則隱藏起來
procedure UpdateActionsVisibility; virtual;
//更新action狀態
procedure UpdateActionsState; virtual;
property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy;
end;
TfrmCustomModuleClass = class of TfrmCustomModule;
下面是一個在TfrmCustomModule 子類中使用Actions的簡單例子:
unit module1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, custommodule, StdCtrls, modules;
type
//新模快必須從定製模塊中繼承
TfrmModule1 = class(TfrmCustomModule)
Label1: TLabel;
CheckBox1: TCheckBox;
Label2: TLabel;
Edit1: TEdit;
procedure CheckBox1Click(Sender: TObject);
private
//處理Action1
procedure DoAction1(Action: TBasicAction);
protected
//註冊支持的actions
procedure RegisterActions; override;
public
// 更新模塊狀態
procedure UpdateActionsState; override;
end;
implementation
uses dmActions;
{$R *.dfm}
{ TfrmModule1 }
procedure TfrmModule1.RegisterActions;
begin
inherited RegisterActions;
//註冊Action1到支持actions列表
RegisterAction(AppActions.Action1, DoAction1);
end;
procedure TfrmModule1.UpdateActionsState;
begin
inherited UpdateActionsState;
//如果checkbo1沒有選中,則讓action1爲可操作
AppActions.Action1.Enabled := not CheckBox1.Checked;
end;
//顯示Action1被執行的次數到Edit1中
procedure TfrmModule1.DoAction1(Action: TBasicAction);
begin
Edit1.Text := IntToStr(StrToInt(Edit1.Text) + 1);
end;
procedure TfrmModule1.CheckBox1Click(Sender: TObject);
begin
UpdateActionsState;
end;
initialization
// 在我們的應用程序框架中註冊對應的類
ModuleInfoManager.RegisterModule('Module1', TfrmModule1, 0, 0);
End.
摘要
一、 使用Developer Express 控件改善應用程序框架
1. 增加Developer Express Navbar庫
在上一步,我們創建了第一版本的應用程序框架,看來它運行得很好。然而如果現在就給我們的老闆或者客戶演示,他們會嘲笑我們的簡陋。在一個潮流的應用程序中採用ListBox作爲導航條並不是一個最好的選擇,用戶希望擁有一個和當前技術發展水平相當的UI外表。Developer提供的ExpressNavBar控件提供十多種不同的顯示風格,將讓你的應用程序擁有一個全新時髦的外表。
NavBar非常容易使用,可以幫助你在設計期內簡化控件設計工作,但是不幸的是,主模塊不知道我們要引入到系統的其它模塊的細節(而且我們希望只是簡單地增加/移除一行代碼就可控制模塊),所以我們必須放棄使用IDE拖拉控件這種設計習慣,改用代碼來完成所有事情。
首先,我們得給應用程序框架引入新的特性。NavBar控件是以類別(categories)來區分的。所以,我們必須在我們的模塊註冊類中引入類別(categories)。此外,爲了徹底改善應用程序的外觀,我們還想在Navbar控件表示的菜單項和羣組中加入圖片。所以我們還必須在註冊類中引入 Image屬性。
下面是我們必須在Modules.pas單元引入的改變:
增加TCategoryInfo類
//類別包含的信息
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;
爲ModuleInfo類增加Category和ImageIndex屬性。 這樣做我們能夠保存category所屬的模塊的信息,同時還保存NavBar控件顯示圖片對應的索引(index), 下面是TModuleInfo類改變的地方:
//模塊中包含的信息
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;
增加CategoryCount,Categories屬性和AddCategory,GetCategoryByName方法到TModuleInfoManager類中,這樣我們能夠將類別(categories)增加到我們的框架中,並在需要的時候找回它們。下一步我們會用到這個功能。
//管理模塊信息的類
TModuleInfoManager = class
private
…
FCategoryList: TList;
function GetCategoryCount: Integer;
function GetCategory(Index: Integer): TCategoryInfo;
public
//增加新的類別
procedure AddCategory(const AName: string; ImageIndex: Integer);
通過名字返回 CategoryInfo 對象
function GetCategoryByName(const Name: string): TCategoryInfo;
//註冊模塊到管理器中
procedure RegisterModule(const AName: string; AModuleClass: TfrmCustomModuleClass;
ACategory: TCategoryInfo = nil; AImageIndex: Integer = -1);
//返回類別的數量
property CategoryCount: Integer read GetCategoryCount;
property Categories[Index: Integer]: TCategoryInfo read GetCategory;
end;
下一步要做的就是在我們的應用程序框架中通過模塊的註冊來創建NavBar控件組別(groups),條目(items)和鏈接(links)。我們必須改變主窗體單元的RegisterModules方法。
procedure TfrmMain.RegisterModules;
var
I: Integer;
ANavBarGroup: TdxNavBarGroup;
ANavBarItem: TdxNavBarItem;
begin
//遍歷所有的類別
for I := 0 to ModuleInfoManager.CategoryCount - 1 do
begin
增加NavBar組別
ANavBarGroup := NavBar.Groups.Add;
//設置NavBar組別的標題
ANavBarGroup.Caption := ModuleInfoManager.Categories[I].Name;
//設置NavBar組別德圖片
ANavBarGroup.LargeImageIndex := ModuleInfoManager.Categories[I].ImageIndex;
//顯示圖片到NavBar組別中
ANavBarGroup.UseSmallImages := False;
end;
//遍歷所有模塊
for I := 0 to ModuleInfoManager.Count - 1 do
begin
//增加新的項目到navBar
ANavBarItem := NavBar.Items.Add;
//設置NavBar項目的標題
ANavBarItem.Caption := ModuleInfoManager[I].Name;
//設置NavBar項目的圖片索引
ANavBarItem.SmallImageIndex := ModuleInfoManager[I].ImageIndex;
使用tag來標示模塊
ANavBarItem.Tag := I;
// 增加項目到對應的navBar組別中
NavBar.Groups[ModuleInfoManager[I].Category.Index].CreateLink(ANavBarItem);
end;
end;
下面是Navbar控件對應單擊事件的處理代碼:
procedure TfrmMain.NavBarLinkClick(Sender: TObject;
ALink: TdxNavBarItemLink);
begin
//顯示指定模塊
ShowModule(ModuleInfoManager.Items[ALink.Item.Tag].Name);
end;
最後一步是註冊Categories 到應用程序框架中。可以這樣做,比如,在主窗口的initialzation部分註冊:
initialization
ModuleInfoManager.AddCategory('Category 1', 0);
摘要
使用了Developer Express NavBar控件作爲導航條後,我們的程序看起來比以前美觀多了。
1. 增加Developer Express Bars庫
現在是用其它東西來替代老式的標準菜單和工具欄的時候了。因爲我們定義了Actions層,所以這不是什麼大問題。不管我們選擇哪個控件,我們只需要修改住窗體即可。下面我將展示如何移植使用Developer Express的 ExpressBars控件。
現在的框架中使用了VCL Actions技術,所以,在改用其它的菜單、工具欄系統之前,你必須檢查新的控件是否支持VCL Actions 技術。ExpressBars 支持VCL Actions。
拖放一個TdxBarManager控件到主窗口上,使用TdxBarConverter來將原來的標準主菜單代替成ExpressBars的主菜單。
模塊之間使用TdxBarListItem類來互相溝通。我們需要修改RegisterModules方法。
procedure TfrmMain.RegisterModules;
var
I: Integer;
begin
//遍歷所有模塊
for I := 0 to ModuleInfoManager.Count - 1 do
begin
//增加項目到Bar列表中
barListItem.Items.Add(ModuleInfoManager[I].Name)
end;
end;
下面是BarListItemClick事件的代碼:
procedure TfrmMain.barListItemClick(Sender: TObject);
begin
//顯示代碼
ShowModule(ModuleInfoManager.Items[barListItem.ItemIndex]);
end;
最後一步是根據我們的需要來創建工具欄和上面的按鈕。在工具欄上放置按鈕以及在設計環境中綁定到相應的VCL Actions。過程就是這樣。
摘要
如您所見,工作很簡單,我們只是修改了主窗體模塊中的代碼就完成了需要的改動。你在自己程序中是如何移植菜單和工具欄到其它風格的控件呢?我猜這肯定是一個痛苦的過程。
1. 創建Developer Express Grid 模塊
我們通過採用XtraNavBar 和 XtraBars來替換標準控件的方法,大大改善了我們應用程序的外觀。現在該是我們考慮根據模塊內容來改進框架的時候了。在你的應用程序裏面,你的“最終”模塊不一定是直接從CustomerModule繼承。大部分的程序一般都會有幾個包含很多對象,記錄的列表。 通常我們用網格(grid)來表示這些記錄, 而這部分可能是程序中重要的組成部分。你必須編寫Grid的處理代碼,舉例來說:根據定製窗體的需要顯示或隱藏相應的Grid欄目, 等等。當然,如果要對每個包含grid的模塊都編寫這樣的代碼,那太沒意思了。我們將創建一個CustomGridModule模塊。 它將包含Developer Express ExpressQuantumGrid。 就像grid Action一樣,我們將引入一個導出Action。 儘管我們在基模塊就加入了導出action的支持,但這些action在默認狀態是被禁止的。所以,實際上繼承的Grid模塊必須覆蓋重寫這些方法來重新使它們變爲可用。
爲了引入Grid模塊,我們需要在主窗體,Action數據模塊中作些修改,並且創建一個新的模塊: CustomGridModule它將繼承自 CustomModule。
我們必須修改主窗體和Action數據模塊,給它們增加Actions, ExpressBars項目,並且鏈接 Action到對應的ExpressBars項目,就如我們之前做的。過程幾乎完全一樣。
一件更有趣的任務是給CustomModule增加導出Actions的支持。導出Actions對所有的模塊是可見的,但默認是禁用的,爲了使它們可用,繼承的模塊必須覆寫兩個方法: SupportedExportTypes 和 DoExport。下面是在CustomModule實現導出Action支持的代碼:
TExportType = (etHTML, etXML, etXLS, etText);
TExportTypes = set of TExportType;
TfrmCustomModule = class(TFrame)
protected
//爲了註冊actions支持,子類必須覆蓋這個方法
procedure RegisterActions; virtual;
//根據需要導出的類型做對應的導出
procedure DoExport(AExportType: TExportType; const AFileName: string); virtual;
//返回相應支持的導出類型
function SupportedExportTypes: TExportTypes; virtual;
end;
在CustomGridModule中實現導出Actions是相當簡單的:
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;
爲了實現Grid Actions,將使用TcxGridOperationHelper類,你能在隨產品發佈的cxGridUIHelper.pas文件中找到它。它爲不同的視圖實現了標準的網格操作。
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;
摘要
在這步,我們在應用程序框架中創建一個基礎列表模塊
它包含了從父網格模塊繼承的額外模塊。你必須在你的開發環境安裝Developer Express 的ExpressNavBar控件, ExpressBars 和 ExpressQuantumGrid支持庫才能正常的編譯和運行這個程序。
1. 使用ExpressPrinting 來增加打印功能
最後一個要加到我們的應用程序框架的是打印功能。我們編寫導出Actions一樣,我們給基模塊引入和實現打印Actions。
在增加打印支持Actions到CustomModule後,我們多了三個額外的虛擬保護方法: HasPrinting, DoPrint 和 DoPreview。
這些方法將在CustomGridModule中被覆蓋以增加打印ExpressQuantumGrid的功能。從ExpressPrinting面板拖一個TdxComponentPrinter控件到CustomGridModule,然後爲在模塊中的Grid創建一個報表鏈接。有了這個控件,給增加CustomGridModule打印支持是一個相當簡單的任務。
//如果模塊支持打印就返回true
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;
摘要
在這一步,我們給應用程序框架引入了打印支持並且在Grid模塊的基類實現。你必須在你的工作環境中安裝Developer Express ExpressNavBar控件、ExpresPrinting、 ExpressBars 和 ExpressQuantumGrid支持庫纔可以編譯和運行這個演示程序(demo)。