. 一个文本编辑器服务器
3.1 服务器功能
我们在Protel中可以新建一个文本文件,系统提供对文本的简单编辑功能,可是不能输入中文,我们能否建立一个类似原理图或印制板服务器一样的文本编辑器,本小节详细分析DemoText服务器,DemoText服务器建立一个文本编辑服务器,虽然简单,但是基本功能都具有了。
请见代码例子\SAMPLES\NO7\Servers\DemoText。
3.2 代码分析
3.2.1 main单元
这个main单元中有一点复杂,定义了TDemoTextServerModule类,此类从TServerModule类继承。此类实现CreateServer和DestroyServer方法,在CreateServer中,创建类的实例并加载命令表。
Procedure CreateServer;
Begin
ServerModule := TDemoTextServerModule.Create;
LoadCommandLauncherTable;
End;
{....................................................................................}
DestroyServer方法析构类的实例。
{....................................................................................}
Procedure DestroyServer;
Begin
If ServerModule <> Nil ThenServerModule.Free;
End;
{....................................................................................}
GetState_ModuleName设置服务器的名称为“DemoText”,即此文本编辑器服务器名称为“DemoText”。
{....................................................................................}
Function TDemoTextServerModule.GetState_ModuleName : String;
Begin
Result := 'DemoText';//服务器名称。
End;
{....................................................................................}
此类除了定义了以上一般服务器都有的两个方法和一个函数外,还定义了如下几个过程或函数:
Procedure CreatePanels;OverRide;
FunctionReturnWindowKindForFile (FileName: String) : String;OverRide;
FunctionReturnNewWindow(WindowKind : String) : TServerWindow; OverRide;
Procedure ReturnIconsAndBitmaps(WindowKind: String;
Var StandardBitmap: HBitmap;
Var TopLevelBitmap: HBitmap;
Var MainInstanceBitmap : HBitmap;
Var DocumentIcon : HIcon ); OverRide;
这几个过程或函数是起什么作用呢?
·CreatePanels
我们先看一看过程CreatePanels。创建CreateDemoTextPanel对象。
Procedure TDemoTextServerModule.CreatePanels;
Begin
CreateDemoTextPanel;
End;
此过程调用过程CreateDemoTextPanel,CreateDemoTextPanel是做什么呢?下面再分析。
·ReturnWindowKindForFile
//注意:此函数将根据参数FileName返回您想要加载的文件的类型。
Function TDemoTextServerModule.ReturnWindowKindForFile(FileName : String) : String;
Begin
Result := '';
If Pos('TXT',UpperCase(ExtractFileExtFromPath(FileName))) <> 0 ThenResult := 'DEMOTEXT';
//如果文件的扩展名称是TXT,则返回DEMOTEXT,否则返回空。
End;
·ReturnNewWindow
//建立一个从TServerWindow类继承的TDemoTextWindow类对象的实例。
Function TDemoTextServerModule.ReturnNewWindow(WindowKind : String) : TServerWindow;
Var
DemoTextWindow : TDemoTextWindow;
Begin
Result := Nil;
If StringsEqual(WindowKind ,'DEMOTEXT') Then
Begin
DemoTextWindow := TDemoTextWindow.Create(Application);
If DemoTextWindow <> Nil Then
Begin
DemoTextWindow.Show; //把窗体显示出来。
Result := DemoTextWindow; //返回实例句柄。
End;
End;
End;
ReturnNewWindow函数乍看不起眼,实际上,这就是整个服务器的核心入口,正是在此函数中,建立一个文本编辑器服务器类TDemoTextWindow的实例,此类是带有窗体的,所以使用TDemoTextWindow.Create(Application)来创建实例,DemoTextWindow.Show把窗体显示出来。具体TDemoTextWindow类是如何定义的,其中提供什么方法,我们下面再详细分析。
·ReturnIconsAndBitmaps
{注意:覆盖默认的图标和位图,如果您不这样做,TServerModule将给客户端提供默认的图标和位图。}
//返回图标和位图。
Procedure TDemoTextServerModule.ReturnIconsAndBitmaps(WindowKind: String;
Var StandardBitmap: HBitmap;
Var TopLevelBitmap: HBitmap;
Var MainInstanceBitmap : HBitmap;
Var DocumentIcon: HIcon);
Begin
If WindowKind = 'DEMOTEXT' Then
Begin
StandardBitmap:= DefaultStandardBitMap;
TopLevelBitmap:= DefaultTopLevelBitmap;
MainInstanceBitmap := DefaultMainInstanceBitmap;
DocumentIcon:= DefaultDocumentIcon;
End;
End;
DefaultStandardBitMap、DefaultTopLevelBitmap、DefaultMainInstanceBitmap、DefaultDocumentIcon是在TServerModule类中定义的表示图标对象。
ReturnIconsAndBitmaps过程为“DEMOTEXT”服务器设置图标和位图。
3.2.2增加进程到命令表
在TDemoTextServerModule类的CreateServer方法中,调用了LoadCommandLauncherTable过程。LoadCommandLauncherTable过程定义在单元ComTable中,过程增加了多个进程到命令表中,代码实现如下:
Procedure LoadCommandLauncherTable;
Begin
CreateCommandLauncherTable;
CommandLauncherTable_State_AddCommand('Clear', @Command_Clear);
CommandLauncherTable_State_AddCommand('Copy',@Command_Copy);
CommandLauncherTable_State_AddCommand('CrossProbeChoose', @Command_CrossProbeChoose);
CommandLauncherTable_State_AddCommand('CrossProbeNotify', @Command_CrossProbeNotify);
CommandLauncherTable_State_AddCommand('Cut', @Command_Cut);
CommandLauncherTable_State_AddCommand('Paste', @Command_Paste);
CommandLauncherTable_State_AddCommand('PrintDocument', @Command_PrintDocument);
CommandLauncherTable_State_AddCommand('Redo', @Command_Redo);
CommandLauncherTable_State_AddCommand('SetupPreferences', @Command_SetupPreferences);
CommandLauncherTable_State_AddCommand('SetupPrinter', @Command_SetupPrinter);
CommandLauncherTable_State_AddCommand('Undo', @Command_Undo);
SortCommandLauncherTable;
End;
此服务器定义了很多进程,如Copy、Paste、Cut等等,这个命令可以在客户端调用,如同在客户端调用原理图编辑器或印制板编辑器命令一样。
3.2.3 Commands单元
服务器提供的进程如Clear、Copy、Paste、Cut等都实现在Commands单元中,我们打开Commands单元,看一看如果实现,但是很奇怪,此单元中这样方法都未具体实现,如Clear方法:
Procedure Command_Clear(Window : TAbstractServerWindow; Parameters : PChar);
Begin
If Window <> Nil Then Window.Clear(Parameters);
End;
但我们能看到有一个参数是TAbstractServerWindow类,此类实例不为空时,执行Clear,说明此类是一个表示编辑器窗体的类,OK,有了此点认识,让我们来找一找此类在何处定义,很容易就能找到,此类在Abstract单元中定义。
3.2.4 Abstract单元
在Abstract单元中,TAbstractServerWindow定义如下:
Type
TAbstractServerWindow = Class(TServerWindow)
Private
Public
Procedure Clear(Parameters : PChar);Virtual;
Procedure Copy(Parameters : PChar);Virtual;
Procedure CrossProbeChoose(Parameters : PChar);Virtual;
Procedure CrossProbeNotify(Parameters : PChar);Virtual;
Procedure Cut(Parameters : PChar);Virtual;
Procedure Paste(Parameters : PChar);Virtual;
Procedure PrintDocument(Parameters : PChar);Virtual;
Procedure Redo(Parameters : PChar); Virtual;
Procedure SetupPreferences(Parameters : PChar);Virtual;
Procedure SetupPrinter(Parameters : PChar);Virtual;
Procedure Undo(Parameters : PChar);Virtual;
End;
//TAbstractServerWindow类是从TServerWindow继承的,在此类中定义的如clear和copy等命令都未具体实现,
//只是显示一个提示说命令没有实现。
让我们看一看,这些方法如Clear是如何实现的?
Procedure TAbstractServerWindow.Clear(Parameters : PChar);
Begin
DisplayNotImplementedMessage('Clear','Delete all selections from the current document');
//DisplayNotImplementedMessage过程显示一个消息框框信息,用户指定的进程依然没有实现,Protel SDK自动代码生成器为所有进程插入此函数。
//参数类型描述
//ProcessIdTString指定进程名称。
//ProcessDescriptionTString为此进程指定一个描述。
End;
比较遗憾,方法还是没有具体实现,那么,方法究竟在何在实现呢?
3.2.5编辑器窗体
让我们回头看一下Main单元中ReturnNewWindow函数,其创建了一个TDemoTextWindow类的实例,此实例看起来象是一个窗体。让我们来找一下TDemoTextWindow类在哪一个单元中定义,是Editor1单元,同时,此单元还定义了一个窗体,如图7-5所示。

图7-5 编辑器窗体
3.2.5.1 TDemoTextWindow类定义
Type
TDemoTextWindow = Class(TCommonWindow)
PrintDialog: TPrintDialog;
PrinterSetupDialog : TPrinterSetupDialog;
Memo: TMemo;
FontDialog: TFontDialog;
procedure MemoChange(Sender: TObject);
procedure FormActivate(Sender: TObject);
Private
Public
DestructorDestroy;OverRide;
ProcedurePrepareForDrawing;OverRide;
FunctionEnableOrDisableMenuItems(MenuHandle : HMenu) : Boolean;OverRide;
FunctionPerformAutoZoom: Boolean;OverRide;
FunctionReturnPanelHandle: HWnd;OverRide;
FunctionFileLoad: Boolean;OverRide;
FunctionFileSave(Format : String) : Boolean;OverRide;
FunctionGetState_ChildWindowCount : Integer;OverRide;
FunctionGetState_ChildWindowName(Index : Integer) : String;OverRide;
ProcedureUpdateEnvironment;OverRide;
ProcedureClear(Parameters : PChar);OverRide;
ProcedureCopy(Parameters : PChar);OverRide;
ProcedureCrossProbeChoose(Parameters : PChar);OverRide;
ProcedureCrossProbeNotify(Parameters : PChar);OverRide;
ProcedureCut(Parameters : PChar);OverRide;
ProcedurePaste(Parameters : PChar);OverRide;
ProcedurePrintDocument(Parameters : PChar);OverRide;
ProcedureRedo(Parameters : PChar);OverRide;
ProcedureSetupPreferences(Parameters : PChar);OverRide;
ProcedureSetupPrinter(Parameters : PChar);OverRide;
ProcedureUndo(Parameters : PChar);OverRide;
End;
//TDemoTextWindow类从TCommonWindow类继承,与父类相比,此类增加了两个过程,MemoChange和FormActivate。另外,此类有一个窗体,其中放置了TPrintDialog组件PrintDialog、TPrinterSetupDialog组件PrinterSetupDialog,TMemo控件Memo和TFontDialog组件FontDialog。组件PrintDialog用于执行弹出打印机对话框功能,组件PrinterSetupDialog用于执行设置打印机,弹出一个打印机设置对话框,组件FontDialog用于设置控件Memo中文字的字体,控件Memo用于编写文字。
//此类把其父类中未实现的过程和函数进行具体实现。
3.2.5.2 方法实现
看一下类中的方法,好象很熟悉,比在Commands单元中定义的方法多出几个。所有的方法已经有具体的实现了。
Destructor TDemoTextWindow.Destroy;
Begin
Inherited Destroy;
//继承父类析构。
End;
{....................................................................................}
{注意:可以在此函数中编写代码来实现把一个文档的内容加载到此文档窗体中,要加载的文件名称使用函数GetState_DocumentName来检索,GetState_DocumentName在TServerWindow类中定义,此函数应在Fileload方法被调用前设置。如果函数被调用成功返回值为True。}
Function TDemoTextWindow.FileLoad : Boolean;
Begin
If FileExists(DocumentName) Then
//如果文件存在,把其加载到控制Memo中。DocumentName是TServerWindow类的属性,存储此文档的文件名称。
Begin
//加载文件。
Memo.Lines.LoadFromFile(DocumentName);
FileLoad := True;//加载成功,返回True。
End
Else
Begin
FileLoad := False;
//不能加载,返回False。
End;
End;
{....................................................................................}
{注意:可以在此函数中编写代码来实现窗体中文件的内容。要保存的文件名称能使用函数GetState_DocumentName来检索,GetState_DocumentName在TServerWindow类中定义,此函数应在FileSave方法被调用前设置。}
Function TDemoTextWindow.FileSave(Format : String) : Boolean;
Begin
Memo.Lines.SaveToFile(DocumentName);//把控制Memo中内容保存到文件中。
FileSave := True;
End;
{....................................................................................}
{注意:在此函数中您将返回一个与此窗体类型相关的面板的窗体句柄}
Function TDemoTextWindow.ReturnPanelHandle : HWnd;
Begin
ReturnPanelHandle := GetState_DemoTextPanelHandle;
//GetState_DemoTextPanelHandle返回TDemoTextPanel类的实例的句柄。
End;
{....................................................................................}
Function TDemoTextWindow.GetState_ChildWindowCount : Integer;
Begin
GetState_ChildWindowCount := 0;
//得到子窗体总数。
End;
{....................................................................................}
{注意:返回此文档的第n个的子文档名称。}
Function TDemoTextWindow.GetState_ChildWindowName(Index : Integer) : String;
Begin
GetState_ChildWindowName := '';
End;
{....................................................................................}
{注意:在editor和frame窗体已被创建但还没有连接到一起后调用,可以在此过程中编写一些代码,来实现初始化文档结构的功能。}
Procedure TDemoTextWindow.PrepareForDrawing;
Begin
//没有实现,只是定义了过程的名称。
End;
{....................................................................................}
{注意:在编辑器editor获得焦点后调用,可编写代码来更新面板panel。}
Procedure TDemoTextWindow.UpdateEnvironment;
Begin
SetState_DemoTextPanelCurrentWindow(Self);
//SetState_DemoTextPanelCurrentWindow定义在Panel1单元中。此过程把当前窗体的父窗体设置为DemoTextPanel.CurrentWindow。
End;
{....................................................................................}
{注意:调用ServerModule方法来抑制或激活特定的命令。}
{例子:MessageRouter_SetState_MenuGray('TextEdit:TextEditorDoUndo',MenuHandle)}
{ MessageRouter_SetState_MenuUnGray ('TextEdit:TextEditorDoUndo',MenuHandle);}
{ MessageRouter_SetState_MenuCheck('TextEdit:TextEditorDoUndo',MenuHandle);}
{MessageRouter_SetState_MenuUnCheck('TextEdit:TextEditorDoUndo',MenuHandle); }
{....................................................................................}
Function TDemoTextWindow.EnableOrDisableMenuItems(MenuHandle : HMenu) : Boolean;
Begin
EnableOrDisableMenuItems := True;
End;
{....................................................................................}
Function TDemoTextWindow.PerformAutoZoom : Boolean;
Begin
PerformAutoZoom := False;
//自动缩放。
End;
{....................................................................................}
Procedure TDemoTextWindow.Clear(Parameters : PChar);
Begin
Memo.ClearSelection;//清除Memo中文本内容。
End;
{....................................................................................}
Procedure TDemoTextWindow.Copy(Parameters : PChar);
Begin
Memo.CopyToClipBoard;//Memo中文本内容复制到粘贴板。
End;
{....................................................................................}
Procedure TDemoTextWindow.CrossProbeChoose(Parameters : PChar);
Begin
SetState_Parameter(Parameters, 'Kind','Text');
SetState_Parameter(Parameters, 'Id', Memo.SelText);
//设置参数,Kind为Text,Id为在Memo中选择的文字。
End;
{....................................................................................}
Procedure TDemoTextWindow.CrossProbeNotify(Parameters : PChar);
Var
Kind: String;
Id: String;
Handle: String;
FocusWindow : String;
Begin
Kind:= 'Unknown';
Id:= 'Unknown';
Handle:= 'Unknown';
FocusWindow := 'Unknown';
//设置为未知类型。
If GetState_Parameter(Parameters, 'Kind' , Kind) Then Kind := UpperCase(Kind);
If GetState_Parameter(Parameters, 'Id' , Id) Then Id := UpperCase(Id);
If GetState_Parameter(Parameters,'Handle',Handle) Then Handle:= UpperCase(Handle);
If GetState_Parameter(Parameters,'FocusWindows',FocusWindow) Then FocusWindow := UpperCase(FocusWindow);
//如果参数中已明确设置,则把参数读出来转换成大写,并放置到相应字符串中。
Memo.Lines.Add('Kind:'+Kind);
Memo.Lines.Add('Id:'+Id);
Memo.Lines.Add('Handle:'+Handle);
Memo.Lines.Add('FocusWindows:'+FocusWindow);
//把参数增加到Memo中。
End;
{....................................................................................}
Procedure TDemoTextWindow.Cut(Parameters : PChar);
Begin
Memo.CutToClipBoard; //Memo中文本内容剪切到粘贴板。
End;
{....................................................................................}
Procedure TDemoTextWindow.Paste(Parameters : PChar);
Begin
Memo.PasteFromClipBoard; //从粘贴板中把内容粘贴过来。
End;
{....................................................................................}
//把Memo中内容输出到打印机。
Procedure TDemoTextWindow.PrintDocument(Parameters : PChar);
Var
F : TextFile;
i : LongInt;
Begin
If PrintDialog.Execute Then
Begin
AssignPrn(F);
Rewrite(F);
Printer.Canvas.Font := Memo.Font;
For i := 0 to Memo.Lines.Count-1 Do
WriteLn(F,Memo.Lines.Strings[i]);
CloseFile(F);
End;
End;
{....................................................................................}
Procedure TDemoTextWindow.Redo(Parameters : PChar);
Begin
Inherited Redo(Parameters);//redo功能。
ShowInfo('Redo没有实现');
End;
{....................................................................................}
//设置Memo控件中的字体。
Procedure TDemoTextWindow.SetupPreferences(Parameters : PChar);
Begin
FontDialog.Font := Memo.Font;
If FontDialog.Execute Then
Begin
Memo.Font := FontDialog.Font;
Memo.RePaint;
End;
End;
{....................................................................................}
Procedure TDemoTextWindow.SetupPrinter(Parameters : PChar);
Begin
PrinterSetupDialog.Execute;//设置打印机。
End;
{....................................................................................}
Procedure TDemoTextWindow.Undo(Parameters : PChar);
Begin
Inherited Undo(Parameters);//undo功能。
ShowInfo('Undo没有实现');
End;
{....................................................................................}
Procedure TDemoTextWindow.MemoChange(Sender: TObject);
Begin
DocumentHasChanged := True;//如果Memo中文字修改了,DocumentHasChanged为真。
End;
{....................................................................................}
//窗体激活事件。
Procedure TDemoTextWindow.FormActivate(Sender: TObject);
Begin
Memo.SetFocus; //memo控件获得焦点。
UpDateEnvironment;//更新环境。
End;
方法中Undo和Redo都没有实现,您可以进一步改进来实现,我是简单弹出一个对话框,说明没有实现。
我们上面说过,TDemoTextWindow类中定义的方法和Command中所说方法很象,只是方法多一点,那么此类和TAbstractServerWindow类有什么关系呢?
我们看到此类是从TCommonWindow类继承的。TCommonWindow类在单元Common定义,恰好,TCommonWindow类从TAbstractServerWindow继承。
至此,类的继承关系图就明确了,从TServerWindow类开始继承,共四层,分别为。
TServerWindow
|TAbstractServerWindow
|TCommonWindow
|TDemoTextWindow
TCommonWindow类中所有的方法也没有具体实现,TCommonWindow类从TAbstractServerWindow类继承,TCommonWindow类不再是抽象类,但此类对父类中抽象的方法只是简单地继承,并没有实现。与父类相比,此类增加了Destroy、PrepareForDrawing、EnableOrDisableMenuItems、PerformAutoZoom、FileLoad、FileSave、GetState_ChildWindowCount、GetState_ChildWindowName和UpdateEnvironment等过程或函数。
3.2.6编辑器窗体面板
让我们回头看一下Main单元中CreatePanels过程,其中调用CreateDemoTextPanel过程,CreateDemoTextPanel实现了什么功能呢?打开Panel1,此时会同时打开一个窗体,如图7-6所示。在图中,我们可以看到有三个按扭,打开单元,可以看到每一个按扭都定义了OnClick事件。

图7-6 编辑器面板
3.2.6.1 按扭的Onclick事件
·Preference按扭OnClick事件
Procedure TDemoTextPanel.PreferenceClick(Sender: TObject);
Begin
MessageRouter_sendCommandTOModule('DemoText:SetupPreferences',Nil,0,
CurrentWindow.Handle);
//Preference按扭的OnClick事件,用来执行DemoText服务器中的SetupPreferences进程。
End;
·SetupPrinter按扭OnClick事件
Procedure TDemoTextPanel.SetupPrinterClick(Sender: TObject);
Begin
MessageRouter_sendCommandTOModule('DemoText:SetupPrinter',Nil,0,
CurrentWindow.Handle);
//SetupPrinter按扭的OnClick事件,用来执行DemoText服务器中的SetupPrinter进程,即设置打印机。
End;
·Print按扭OnClick事件
Procedure TDemoTextPanel.PrintClick(Sender: TObject);
Begin
MessageRouter_sendCommandTOModule('DemoText:PrintDocument',Nil,0,
CurrentWindow.Handle);
//Print按扭的OnClick事件,用来执行DemoText服务器中的PrintDocument进程,打印文档。
End;
3.2.6.2 CreateDemoTextPanel过程
CreateDemoTextPanel过程就是构造一个TDemoTextPanel类的实例。代码实现如下:
//构造过程,对象构建完成后,设CurrentWindow为nil。
Procedure CreateDemoTextPanel;
Begin
DemoTextPanel := TDemoTextPanel.Create(Application);
If DemoTextPanel <> Nil Then
Begin
DemoTextPanel.CurrentWindow := Nil;
End;
End;
此至,主要代码都详细说明完成了,我们把服务器安装到Protel中,象安装一般的服务器一样,再定义一菜单来调用服务器中的命令,如Clear,执行一下,看有什么发生,实际上,什么都没有发生,真是奇怪!(e-works)