<连载>Protel二次开发从入门到精通
7. 交互作用的事件处理系统
7.1 在PCB编辑器中的交互事件处理角色
在某些确定的过程执行期间(如放置线、测量距离等等),PCB进入一个交互模式,所有的鼠标和键盘被管理,并且通过PCB编辑器解释,一个十字光标通常出现在鼠标光标位置,用户能够交互地(和图形化的)编辑一个PCB对象,在PCB编辑器内部,一个事件处理对象是形成交互方式的原因,并且被通过PCB API如一个“TPCBEventHandler”类来暴露,使用此对象,第三方程序设计者能容易地在任何PCB文档上“撤消一个光标(drop a cursor)”并且执行任何他想要的交互的行为。
7.2 交互事件处理系统
TPCBEventHandler对象暴露内部PCB对象,是形成交互模式的原因,此对象捕捉所有的用户输入(鼠标移动、键盘按下等等),并且处理它们来驱动交互进程(一个鼠标的移动事件通常更新正在被编辑的PCB对象的位置,如果这样,位置为一个十字光标的位置等等),此对象的基本行为如下面所描述。
下图显示了TPCBEventHandler对象的基本行为。
当TPCBEventHandler被创建好并运行时,在循环中重复相同的动作,它首先得到用户考虑好的输入(框1),任何涉到的对象通过此输入被给了一个来从屏幕中删除的机会(框2),用户输入是进程 (框3),并且通过用户输入涉到的对象能被在屏幕上绘制出(框4),如果有目的的停止活动或交互处理被取消(框5),TPCBEventhandler停止此循环。

图4-17 TPCBEventHandler对象的基本的行为
7.3 写一个自定义的事件处理
通过从TPCBEventhandler类继承并且重载它的方法, 一个第三方的开发者能自定义基本的行为。下面描述TPCBEventhandler要被重载的方法。
|
字段 |
描述 |
|
ObjectHandle |
表示一个印制板对象的句柄。 |
|
方法 |
描述 |
|
Create |
创建一个事件处理对象。 |
|
Destroy |
销毁一个事件处理对象。 |
|
PerformanceIsPoor |
在PCB编辑器中检测事件处理系统的性能。 |
|
Handle_F1 |
当F1键被按下时取消。 |
|
Handle_Plus |
当Plus键被按下时取消。 |
|
Handle_X |
当X键被按下时取消。 |
|
Handle_Y |
当Y键被按下时取消。 |
|
Handle_L |
当L键被按下时取消。 |
|
Handle_Minus |
当- 键被按下时取消。 |
|
Handle_Multiply |
当+ 键被按下时取消。 |
|
Handle_BackSpace |
当BackSpace键被按下时取消。 |
|
Handle_Tab |
当Tab键被按下时取消。 |
|
Handle_KeyPressed |
当按键事件发生时调用。 |
|
Handle_Space |
当空格键被按下时调用。 |
|
Handle_Escape |
当Escape键被按下时调用。 |
|
Handle_Return |
当Return键被按下时调用。 |
|
Handle_Insert |
当Insert键被按下时调用。 |
|
Handle_Delete |
当Delete键被按下时调用。 |
|
Handle_ReleasedLB |
当鼠标左键被释放时调用。 |
|
Handle_ReleasedRB |
当鼠标右键被释放时调用。 |
|
Handle_MouseMove |
当鼠标移动被探测到时调用。 |
|
Handle_PaintMsg |
当绘制消息被探测到时调用。 |
|
Handle_RightArrow |
当右箭头键被按下时调用。 |
|
Handle_LeftArrow |
当左箭头键被按下时调用。 |
|
Handle_UpArrow |
当Up键被按下时调用。 |
|
Handle_DownArrow |
当Down键被按下时调用。 |
|
Handle_PageUp |
当Page Up键被按下时调用。 |
|
Handle_PageDown |
当Page Down键被按下时调用。 |
|
Handle_Home |
当Home键被按下时调用。 |
|
Handle_End |
当End键被按下时调用。 |
|
Handle_MouseMovedOutsideClient |
当鼠标移到到客户端PCB文档窗体外部时调用。 |
|
DrawInverted |
当对象被Drawn On或OFF时调用。 |
|
UpdateObject |
当任何Handle_XXX 函数后调用。 |
|
OnSuccess |
在事件处理已成功完成它的任务后调用。 |
|
OnCancel |
当事件处理被取消后调用。 |
|
OnShow |
当事件处理显示图示的内容后调用。 |
|
OnHide |
当事件处理隐藏图示的内容后调用。 |
|
OnTerminate |
当事件处理过程结束时调用。 |
|
UpdateBoundingRectangle |
当在印制板上的边界矩形被更新时调用,常用于当边界矩形没有被更新但尺寸已被更新的情形。 |
|
DrawInvertedBoundingRectangle |
当边界矩形被绘制成反向的颜色时调用。 |
|
Abort |
调用来测试,是否停止循环(异常终止状态)。 |
|
GoalAchieved |
调用来测试,是否停止循环(成功状态)。 |
|
Run |
句柄被设置和处理时是否调用。 |
|
ShiftIsPressed |
Shift键被按下时是否调用。 |
|
ControlIsPressed |
Control键被按下时是否调用。 |
让我们设想,一个事件处理在一个印制板的点(例如(100,100))和当前鼠标位置之间绘制一个线(Track),此事件处理对象从TPCBEventHandler类继承,并且包含一个附加的TPCBTrack类型的字段(线对象被绘制在(100,100)到鼠标光标之间)。
Handle_MouseMove方法将需要被重载来更新TPCBTrack对象的坐标,Handle_Minus, Handle_Plus, Handle_Multiply能被重载来在绘制时更新线对象的层。UpdateObject方法能被重载来更新PCB数据库(调用PCBTrack.QueryDatabase(eSetState)),UpdateObject方法有最后的分配到TPCBTrack对象字段的值,DrawInverted方法需要被重载,来隐藏并且显示线对象(通过调用PCBApi_DrawInverted)。
7.4 如何使用一个事件处理?
此代码例子示范了如何在PCB编辑器中使用一个事件处理,此代码例子为您示范发一个事件处理服务器的简单实现。
请见SDK例子\SAMPLES\NO4\API\PCB\Event handler。
{....................................................................................}
Implementation
Type
TEventCapture = Class(TPCBEventHandler)//从TPCBEventHandler继承。
SourceObject : TPCBTrack;
Constructor Create(ABoardHandle, APrimitiveHandle : TObjectHandle);
DestructorDestroy; Override;
ProcedureHandle_X; Override;
End;
{................................................................................}
Var
EventCapture : TEventCapture;
{................................................................................}
Function CapturePCBEvents : Boolean;
Var
Handle : TObjectHandle;
BoardHandle: TObjectHandle;
Begin
SetAllPCBProcAddresses;
//SetAllPCBProcAddresses过程初始化并且设置所有PCB编辑器调用。即取得所有AdvPcb.dll中输出的函数和过程的地址指针。
If MessageRouter_GetState_WindowKind(PCBAPI_GetCurrentEditorWindow) <> 'PCB' Then Exit;
//PcbApi_GetCurrentEditorWindow函数返回文档对象窗体句柄。
//MessageRouter_GetState_WindowKind函数使用编辑器窗体句柄返回表示编辑器窗体类型的字符串值。例如,Advanced PCB 编辑器返回“PCB”字符串,并且TextEdit服务器返回“TEXT” 字符串。
BoardHandle := PcbApi_GetCurrentBoardHandle;
//PcbApi_GetCurrentBoardHandle函数返回当前在设计资源管理器中的PCB文档的句柄,如果印制板没有找到则返回值是空(nil)。
Handle := PcbApi_GetObjectAtCursor(BoardHandle,//印制板句柄。
[eTrackObject], //线对象类型。
AllLayers,//所有层。
'选择一个线对象(Choose a Track)'); //状态条提示。
//PcbApi_GetObjectAtCursor函数返回所点击的对象的句柄,当此函数调动激活时,一个光标出现在印制板上来提示用户点击任何PCB对象,所能点击的PCB对象依赖于参数提供,此函数调用将返回所选择的PCB对象句柄。此函数依赖于底层函数PcbApi_GetObjectAtXYAskUserIfAmbiguous支持来提取指定的PCB对象。
While Handle <> 0 Do
Begin
EventCapture := TEventCapture.Create(BoardHandle, Handle);//创建EventCapture对象。
EventCapture.Run;//运行。
EventCapture.Handle_X;//在循环过程中处理X键按下事件。
EventCapture.Free;//释放。
Handle := PcbApi_GetObjectAtCursor(BoardHandle,//印制板句柄。
[eTrackObject], //线对象类型。
AllLayers,//所有层。
'选择一个线对象(Choose a Track)');//状态条提示
//提示选择下一个对象。
End;
End;
{................................................................................}
//TEventCapture的构造过程。
Constructor TEventCapture.Create(ABoardHandle, APrimitiveHandle : TObjectHandle);
Begin
Inherited Create(ABoardHandle);//继承父类对象的构造函数。
SourceObject := TPCBTrack.Create(APrimitiveHandle);//创建线对象。
SourceObject.QueryDatabase(eGetState);//同步数据。
End;
{................................................................................}
DestructorTEventCapture.Destroy;
Begin
SourceObject.Free; //释放线对象
Inherited Destroy; //继承父类对象的释放过程
End;
{................................................................................}
Procedure TEventCapture.Handle_X; //对X键进行处理,X键扩展线对象的宽度。
Begin
Inherited Handle_X;
SourceObject.Width := MilsToCoord(100);//线对象宽度。
SourceObject.QueryDatabase(eSetState);//用外部服务器数据同步内部服务器数据。
PcbApi_QueryObjectGraphicallyInvalidate(SourceObject.ObjectHandle);
//PcbApi_QueryObjectGraphicallyInvalidate使用给定的对象句柄请求重绘一个PCB对象。
End;
{................................................................................}
Procedure Command_ExpandTracks(Window : TServerWindow; Parameters : PChar);
Begin
CapturePCBEvents;
End;
{................................................................................}
End.
为有效运行事件处理例子,您需要注意下列少许的步骤,当运行此例子时,设计资源管理器状态将提示一个“选择一个线对象(Choose a Track)” 字符串,您接下来点击一个线对象,此线对象句柄被通过事件处理获得,您接着按下“X”键来扩展此线对象的宽度,您可以按下“Escape”或点击鼠标右键来从当前模式中退出,但事件处理仍然在活动,状态栏提示您“选择一个线对象(Choose a Track)”的消息。
为退出事件处理系统,您需再次点击鼠标右键或按下“Escape”键。
8. 友情提示
8.1 检查编辑器窗体是否为PCB文档类型
然而如果PCB编辑器服务没有安装在设计资源管理器中,并且当一个自定义服务器尝试从PCB编辑器中来请求服务时,设计资源管理器应用程序将会有问题。因而,这里有两个代码片段,您应该在调用任何PCB API前把其插入到您的代码中。
·第一个代码片段检查当前窗体是否是一个PCB文档。
If MessageRouter_GetState_WindowKind(PCBAPI_GetCurrentEditorWindow) <> 'PCB' Then Exit;
//检查当前文档是否是一个印制板文档。
·第二个代码检查当前窗体是否是一个PCB库文档类型。
If MessageRouter_GetState_WindowKind(PCBAPI_GetCurrentEditorWindow) <> 'PCBLIB' Then Exit;
//检查当前文档是否是一个库文档。PcbApi_GetCurrentEditorWindow函数返回文档对象窗体句柄。
·第三个代码片段使用PCB API调用来做一些处理。
If PcbApi_GetCurrentEditorWindow = 0 Then
Begin
ShowMessage('没有PCB文档被在当前设计资源管理器中打开。(No PCB document is currently open in Design Explorer.)');
Exit;
End;
If PcbApi_GetCurrentBoardHandle = 0 Then
Begin
ShowMessage('没有印制板被在当前设计资源管理器中打开。(No PCB is currently open in Design Explorer.)');
Exit;
End;
8.2 产生一个文本文档并且从PCB文档中输出结果
此代码片段使用当前印制板文件名称产生了一个文本文档,把文档扩展名称改为“TXT”,此文本文档报告在PCB文档中查找到的焊盘的数量。
请见SDK例子\SAMPLES\NO4\API\PCB\Generate a Text Document。
{....................................................................................}
Procedure Repopulate;
Var
EntityHandle: TObjectHandle;
BinderHandle: TObjectHandle;
Begin
//刷新设计树和容器。
EntityHandle :=
ClientAPI_FindEntityByDataHandle(MessageRouter_GetState_CurrentEditorWindow);
//MessageRouter_GetState_CurrentEditorWindow函数返回在项层的文档的窗体句柄。
//ClientApi_FindEntityByDataHandle 函数使用一个编辑器窗体句柄来返回查找返回一个实体的句柄。此实体能表现为设计资源管理器编辑器窗体内的一个文档。您能通过使用ClientAPI_QueryEntity 或ClientAPI_QueryDocumentEntity 查询此实体来检查文档类型,名称等等。
If EntityHandle = 0 Then Exit; //无对象则退出。
BinderHandle := ClientApi_GetDocumentOwnerBinder(EntityHandle);
//ClientApi_GetDocumentOwnerBinder函数获得在其内部存有文档的封装对象的句柄,一个封装对象表现为包含实体的容器,一个实体可为一个设计文档或文件来。
ClientApi_RepopulateDocumentEntity(BinderHandle,False);
//ClientApi_RepopulateDocumentEntity 过程在封装对象中刷新文档实体的内容依赖于Recursive参数值,如果参数为false,当前实体被刷新,否则实体的封装对象被刷新。它在当文档被增加到一个封装对象中您需要刷新封装对象来让设计资源管理器知道有新的文档时情况下很有用。
End;
{....................................................................................}
Procedure WriteAndEmbedTextFileIntoDDB;
Var
EntityHandle : TObjectHandle;
BinderHandle : TObjectHandle;
BoardHandle: TObjectHandle;
PadHandle: TObjectHandle;
IteratorHandle : TObjectHandle;
PadNumber: Integer;
F: TextFile;
S: TString;
Board: TPCBBoard;
Begin
SetAllPCBProcAddresses;
//SetAllPCBProcAddresses过程初始化并且设置所有PCB编辑器调用。即取得所有AdvPcb.dll中输出的函数和过程的地址指针。
SetAllClientApiProcedures;
//SetAllClientApiProcedures过程初始化并且设置所有客户端API调用。
BoardHandle := PcbApi_GetCurrentBoardHandle;
//PcbApi_GetCurrentBoardHandle函数返回当前在设计资源管理器中的PCB文档的句柄,如果印制板没有找到则返回值是空(nil)。
If BoardHandle = 0 Then
Begin
ShowInfo('没有印制板文档存在…(No PCB document exists...)');
Exit;
End;
PadNumber:= 0;//焊盘数初始值为0
IteratorHandle := PcbApi_CreateIterator(BoardHandle,//印制板句柄。
ePadObject, //要基于的焊盘对象。
eProcessAll,//处理所有层。
eIgnoreLayer);
//PcbApi_CreateIterator函数返回到一个迭代程序对象的对象句柄。创建一个迭代程序对象,在当前PCB文档中处理所有元件对象。
PadHandle := PcbApi_GetFirstObject(IteratorHandle);
//PcbApi_GetFirstObject函数使用给定的迭代程序(iterator)句柄返回第一个发现的PCB对象句柄。此函数在PcbApi_CreateIterator函数后和PcbApi_GetNextObject函数被使用前被立即调用。
While(PadHandle <> 0) Do
Begin
PadNumber := PadNumber + 1;// //查找到的焊盘计数
PadHandle := PcbApi_GetNextObject(IteratorHandle);
//PcbApi_GetNextObject函数使用给定的迭代程序句柄返回下一个查找到的PCB对象的句柄。
End;
PcbApi_DestroyIterator(IteratorHandle);
//PcbApi_DestroyIterator销毁此迭代程序对象。
//以下代码目的是获取当前PCB对象的名称
Board := TPCBBoard.Create(BoardHandle);//创建印制板对象
Board.QueryDatabase(eGetState);//同步数据
//一个印制板实体的抽象的地址扩展名称改变…
S := ForceFileNameExtension(Board.FileName, 'TXT');
//ForceFileNameExtension函数用新的扩展名称替代已存在的文件扩展名称。
Board.Free;
//产生并且放入文件到当前DDB中
AssignDDB(F,S,'Admin');
Rewrite(F);
Writeln(F,'印制板报表(Board Report...)');
Writeln(F,'===========================');
Writeln(F,'');
Writeln(F,'此印制板焊盘数量为(Number of pads on this pcb Board is): ' + IntToStr(PadNumber));
CloseFile(F);
Repopulate;//刷新实体对象
End;
更多有关AssignDDB函数的明细信息请参考客户端API和RTL参考,迭代程序请参见有关迭代程序工作机理章节内容。
8.3 打开想要的文档的程序
如果您想要打开一个指定的文档,但您仅知道设计文档名称,您可以运行这个小规模的附加的服务器,例子在“\SAMPLES\NO2\API\Client\SearchAndOpenDocuments”文件夹下。在您读完客户端API章节后,您将能了解此附加的服务器是如何工作的。
为了使用这个“SearchAndOpen”附加的服务,您只需列出参数“DocumentName1=Value1 | DocumentName2 = Value
8.4 作用域不兼容性
如果您象使用印制板API一样使用客户端API,编译器或许抱怨有不兼容的类型。例如,当您试图在您的源代码中使用客户端API函数“DocumentEntity.QueryDatabase(eGetState)”时,您尝试进行编译,编译器在此误窗体中提示说‘Incompatible types(不兼容的类型).’。
解决方案是使用“DocumentEntity.QueryDatabase(ClientTypes.eGetState);”,这样,编译器就能知道如何解决类型问题。(e-works)