Chinaunix首页 | 论坛 | 博客
  • 博客访问: 522023
  • 博文数量: 252
  • 博客积分: 6057
  • 博客等级: 准将
  • 技术积分: 1635
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-21 10:17
文章分类

全部博文(252)

文章存档

2013年(1)

2012年(1)

2011年(32)

2010年(212)

2009年(6)

分类:

2010-07-28 09:32:08

A Simple VCL SDI Application Framework
试图实现以下功能:
模块Module与表现UI分离的工程框架
只用一行代码来添加或者移除模块
模块功能的复用
统一控制菜单/工具栏


这是第一部分.
转载注明作者Daniel(写得还是挺辛苦的),抛砖引玉,写得不通的地方,请多多指教
需要文中源码,可以发送邮件至liyangyao@gmail.com

------------------------------------------------------------------------
从这里开始
------------------------------------------------------------------------

建立CustomModule.pas单元,所有的模块都继承自该单元的TfrmCustomModule
File -> New Frame,Frame的Name为TfrmCustomModule
我们为所有的模块添加一个Destroy事件
TfrmCustomModule = class(TFrame)
private
FOnDestroy: TNotifyEvent;
public
destructor Destroy; override;
property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy;
end;
TfrmCustomModuleClass = class of TfrmCustomModule;


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


------------------------------------------------------------------------
管理模块
------------------------------------------------------------------------

我们再创建一个模块管理单元modules.pas,其中包含了2个类:TModuleInfo模块信息类和TModuleManger模块管理类.

TModuleInfo类包含模块的名称,属性,可以在需要显示的时候,通过字符串的名称来创建模块.
TModuleManager类包含已注册的模块列表.其中,RegisterModule方法注册一个新模块,ShowModule来显示模块在一个特定的Windows控件上Count和Items可以用来访问当前拥有的所有的模块

在该单元中,还有一个ModuleInfoManager的全局函数来控制对一个TModuleManger对象实例的访问
//模块信息类
TModuleInfo = class
private
FModuleClass: TfrmCustomModuleClass;
FModule: TfrmCustomModule;
FName: string;
function GetActive: Boolean;
procedure DoModuleDestroy(Sender: TObject);
protected
//创建和销毁一个模块实例
procedure CreateModule;
procedure DestroyModule;
public
constructor Create(const AName: string; AModuleClass: TfrmCustomModuleClass);
destructor Destroy; override;
//显示和隐藏模块
procedure Hide;
procedure Show(AParent: TWinControl);
//模块是否处于激活状态
property Active: Boolean read GetActive;
property Module: TfrmCustomModule read FModule;
property Name: string read FName;
end;


//模块信息类的实现
constructor TModuleInfo.Create(const AName: string;
AModuleClass: TfrmCustomModuleClass);
begin
FName := AName;
FModuleClass := AModuleClass;
end;

procedure TModuleInfo.CreateModule;
begin
FModule := FModuleClass.Create(nil);
FModule.OnDestroy := DoModuleDestroy;
end;

destructor TModuleInfo.Destroy;
begin
DestroyModule;
inherited Destroy;
end;

procedure TModuleInfo.DestroyModule;
begin
if (FModule <> nil) and not (csDestroying in Module.ComponentState) then
begin
    FModule.Free;
    FModule := nil;
end;
end;

procedure TModuleInfo.DoModuleDestroy(Sender: TObject);
begin
FModule := nil;
end;

function TModuleInfo.GetActive: Boolean;
begin
Result := ModuleInfoManager.ActiveModuleInfo = Self;
end;

procedure TModuleInfo.Hide;
begin
FModule.Hide;
end;

procedure TModuleInfo.Show(AParent: TWinControl);
begin
if Module = nil then
    CreateModule;
Module.Parent := AParent;
Module.Align := alClient;
Module.Show;
end;


//Module管理类的定义部分
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;

//Module管理类的 实现部分
constructor TModuleInfoManager.Create;
begin
FModuleList := TList.Create;
end;

destructor TModuleInfoManager.Destroy;
begin
FModuleList.Free;
inherited;
end;

function TModuleInfoManager.GetCount: Integer;
begin
Result := FModuleList.Count;
end;

function TModuleInfoManager.GetItem(Index: Integer): TModuleInfo;
begin
Result := TModuleInfo(FModuleList[Index]);
end;

function TModuleInfoManager.GetModuleInfoByName(
const AName: string): TModuleInfo;
var
I: Integer;
begin
Result := nil;
for I := 0 to FModuleList.Count - 1 do
    if (CompareText(Items[I].Name, AName) = 0) then
    begin
      Result := Items[I];
      break;
    end;
end;

procedure TModuleInfoManager.RegisterModule(const AName: string;
AModuleClass: TfrmCustomModuleClass);
var
AModuleInfo: TModuleInfo;
begin
//检查模块名称是否已存在
if GetModuleInfoByName(AName) <> nil then
    raise Exception.CreateFmt('名称为"%s"的模块已经存在', [AName]);
AModuleInfo := TModuleInfo.Create(AName, AModuleClass);
FModuleList.Add(AModuleInfo)
end;

procedure TModuleInfoManager.ShowModule(const AName: string;
AParent: TWinControl);
var
AModuleInfo: TModuleInfo;
begin
AModuleInfo := GetModuleInfoByName(AName);
//检查模块,不存在则抛出异常
if AModuleInfo = nil then
    raise Exception.CreateFmt('模块"%s"不存在', [AName]);
if AModuleInfo <> ActiveModuleInfo then
begin
    if ActiveModuleInfo <> nil then
      ActiveModuleInfo.Hide;
    AModuleInfo.Show(AParent);
    FActiveModuleInfo := AModuleInfo;
end;
end;

//定义全局函数 ModuleInfoManager
//返回 TModuleInfoManager 对象全局实例
function ModuleInfoManager: TModuleInfoManager;


//全局函数 ModuleInfoManager实现
var
FModuleInfoManager: TModuleInfoManager = nil;

function ModuleInfoManager: TModuleInfoManager;
begin
if FModuleInfoManager = nil then
    FModuleInfoManager := TModuleInfoManager.Create;
Result := FModuleInfoManager;
end;

initialization
finalization
FreeAndNil(FModuleInfoManager);
end.


------------------------------------------------------------------------
随便搞2个测试模块
------------------------------------------------------------------------

在工程中创建2个用来示例的简单的模块并注册,TfrmModule1,TfrmModule2
File->New->Others,选中工程名的标签卡,继承自TFrmCustomModule
为了展示2个Module的不同,我们可以选点不同的背景颜色或者加些控件
并引用Modules单元,初使化单元时注册模块,比如
initialization
ModuleInfoManager.RegisterModule('Module1', TfrmModule1);
------------------------------------------------------------------------
终于开始写主窗体的程序了
------------------------------------------------------------------------

最后,万事具备,只欠东风了,在主窗体中来看看效果吧
添加一个ListBox控件来导航lblNavigation,添加一个菜单"查看"mView,预备通过其子菜可选择要查看的模块,再添加一个Panel来作为显示区域pnlWorkingArea

引用Modules单元,会使用到全局函数ModuleInfoManager
1.初使化时遍历所有的模块,添加到ListBox和Menu中,并使用Tag标识它们,Menu的查看子菜单需要赋值mViewClick事件
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;
    AMenuItem.Tag := I;
    AMenuItem.OnClick := mViewClick;
end;


2.根据模块名称显示相应的模块
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;


3.单击ListBox导航时
if lblNavigation.ItemIndex < 0 then exit;
ShowModule(lblNavigation.Items[lblNavigation.ItemIndex]);


4.View子菜单的单击事件 mViewClick
ShowModule(lblNavigation.Items[TMenuItem(Sender).Tag]);

------------------------------------------------------------------------
总结
------------------------------------------------------------------------

按下F9运行,正如你看到的,我们通过不多的代码,实现了Module与UI的分离
当然这个Framework并没有解决所有的细节问题,比如模块访问权限控制,需要的话,我们可通过注册模块时加以限制.
在添加模块具体功能时,只要记住一点,在基本模块中添加的功能,会自动继承到其子模块中,比如,CustomModule -> CustomDBModule -> CustomGridModule,从而最大可能实现代码复用.


还有什么问题吗?
1.UI太丑
2.添加工具栏后,Module对工具栏的控制,能否做到与UI的分离

第一部分结束
Daniel 2008-8-10 周日凌晨
阅读(381) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~