解决方法
>组件可能自定义的Action属性,可由Action组件分配
> Action组件本身.
动作属性
每个TControl后代都有一个Action属性,默认情况下执行链接到鼠标左键单击.此链接由ActionLink管理.默认的ActionLink属于TControlActionLink类型,它负责Action和控件的标题,提示,启用状态等的同步.如果这个基本功能就是您想要的,那么只需在组件类型声明中发布Action属性,Delphi框架就可以完成所有操作,如Serg和LU RD已经回答.
如果您希望将自己的Action属性链接到某个其他条件或事件(即Click之外),或者如果要为组件的特定子元素(不是TControl后代)实现Action属性,则您可以通过定义和实现自定义ActionLink类来实现自己的自定义Action属性.
假设您的组件是某种具有列的网格,并且您希望每个列都具有应在用户单击列标题时调用的操作属性.由于此类列可能是TCollectionItem类型,因此默认情况下列类型没有action属性.所以你必须自己实现一个.考虑下一个将操作的标题链接到列标题的示例,将操作的启用状态与列的readonly属性反向链接,等等……:
unit Unit1; interface uses Classes,ActnList,SysUtils; type TColumn = class; TColumnActionLink = class(TActionLink) protected FClient: TColumn; procedure AssignClient(AClient: TObject); override; function IsCaptionLinked: Boolean; override; function IsEnabledLinked: Boolean; override; function IsOnExecuteLinked: Boolean; override; function IsVisibleLinked: Boolean; override; procedure SetCaption(const Value: String); override; procedure SetEnabled(Value: Boolean); override; procedure SetOnExecute(Value: TNotifyEvent); override; procedure SetVisible(Value: Boolean); override; end; TColumnActionLinkClass = class of TColumnActionLink; TColumn = class(TCollectionItem) private FActionLink: TColumnActionLink; FGrid: TComponent; FOnTitleClick: TNotifyEvent; FReadOnly: Boolean; FTitle: String; FVisible: Boolean; function DefaultTitleCaption: String; procedure DoActionChange(Sender: TObject); function GetAction: TBasicAction; function IsOnTitleClickStored: Boolean; function IsReadOnlyStored: Boolean; function IsVisibleStored: Boolean; procedure SetAction(Value: TBasicAction); protected procedure ActionChanged(Sender: TObject; CheckDefaults: Boolean); dynamic; procedure DoTitleClick; virtual; function GetActionLinkClass: TColumnActionLinkClass; virtual; property ActionLink: TColumnActionLink read FActionLink write FActionLink; public destructor Destroy; override; procedure InitiateAction; virtual; published property Action: TBasicAction read GetAction write SetAction; property OnTitleClick: TNotifyEvent read FOnTitleClick write FOnTitleClick stored IsOnTitleClickStored; property ReadOnly: Boolean read FReadOnly write FReadOnly stored IsReadOnlyStored; property Title: String read FTitle write FTitle; property Visible: Boolean read FVisible write FVisible stored IsVisibleStored; end; implementation { TColumnActionLink } procedure TColumnActionLink.AssignClient(AClient: TObject); begin FClient := TColumn(AClient); end; function TColumnActionLink.IsCaptionLinked: Boolean; begin Result := inherited IsCaptionLinked and (Action is TCustomAction) and (FClient.Title = TCustomAction(Action).Caption); end; function TColumnActionLink.IsEnabledLinked: Boolean; begin Result := inherited IsEnabledLinked and (Action is TCustomAction) and (FClient.ReadOnly <> TCustomAction(Action).Enabled); end; function TColumnActionLink.IsOnExecuteLinked: Boolean; begin Result := inherited IsOnExecuteLinked and (@FClient.OnTitleClick = @Action.OnExecute); end; function TColumnActionLink.IsVisibleLinked: Boolean; begin Result := inherited IsVisibleLinked and (Action is TCustomAction) and (FClient.Visible = TCustomAction(Action).Visible); end; procedure TColumnActionLink.SetCaption(const Value: string); begin if IsCaptionLinked then FClient.Title := Value; end; procedure TColumnActionLink.SetEnabled(Value: Boolean); begin if IsEnabledLinked then FClient.ReadOnly := not Value; end; procedure TColumnActionLink.SetOnExecute(Value: TNotifyEvent); begin if IsOnExecuteLinked then FClient.OnTitleClick := Value; end; procedure TColumnActionLink.SetVisible(Value: Boolean); begin if IsVisibleLinked then FClient.Visible := Value; end; { TColumn } procedure TColumn.ActionChanged(Sender: TObject; CheckDefaults: Boolean); begin if Sender is TCustomAction then with TCustomAction(Sender) do begin if not CheckDefaults or (Caption = DefaultTitleCaption) then FTitle := Caption; if not CheckDefaults or (not ReadOnly) then ReadOnly := not Enabled; if not CheckDefaults or not Assigned(FOnTitleClick) then FOnTitleClick := OnExecute; if not CheckDefaults or (Self.Visible = True) then Self.Visible := Visible; Changed(False); end; end; function TColumn.DefaultTitleCaption: String; begin Result := 'Column' + IntToStr(Index); end; destructor TColumn.Destroy; begin FreeAndNil(FActionLink); inherited Destroy; end; procedure TColumn.DoActionChange(Sender: TObject); begin if Sender = Action then ActionChanged(Sender,False); end; procedure TColumn.DoTitleClick; begin if Assigned(FOnTitleClick) then if (Action <> nil) and (@FOnTitleClick <> @Action.OnExecute) then FOnTitleClick(Self) else if FActionLink = nil then FOnTitleClick(Self) else if FActionLink <> nil then if (FGrid <> nil) and not (csDesigning in FGrid.ComponentState) then begin if not FActionLink.Execute(FGrid) then FOnTitleClick(Self); end else if not FActionLink.Execute(nil) then FOnTitleClick(Self); end; function TColumn.GetAction: TBasicAction; begin if FActionLink <> nil then Result := FActionLink.Action else Result := nil; end; function TColumn.GetActionLinkClass: TColumnActionLinkClass; begin Result := TColumnActionLink; end; procedure TColumn.InitiateAction; begin if FActionLink <> nil then FActionLink.Update; end; function TColumn.IsOnTitleClickStored: Boolean; begin Result := (FActionLink = nil) or not ActionLink.IsOnExecuteLinked; end; function TColumn.IsReadOnlyStored: Boolean; begin Result := (FActionLink = nil) or not FActionLink.IsEnabledLinked; if Result then Result := FReadOnly; end; function TColumn.IsVisibleStored: Boolean; begin Result := (FActionLink = nil) or not FActionLink.IsVisibleLinked; if Result then Result := not Visible; end; procedure TColumn.SetAction(Value: TBasicAction); begin if Value = nil then FreeAndNil(FActionLink) else begin if FActionLink = nil then FActionLink := GetActionLinkClass.Create(Self); FActionLink.Action := Value; FActionLink.OnChange := DoActionChange; ActionChanged(Value,csLoading in Value.ComponentState); if FGrid <> nil then Value.FreeNotification(FGrid); end; Changed(False); end; end.
请注意,此代码仅剥离适用的操作部分.
资料来源:www.nldelphi.com.
一个动作组件
动作组件可分配给任意组件的动作属性.但是,由于解释编写此类动作组件所涉及的所有内容非常全面,因此我将在下面提供示例时轻松自己.
假设您想要创建一个提供缩放功能的控件,并且您还需要可以分配给工具栏按钮的相应ZoomIn和ZoomOut操作.
unit Zoomer; interface uses Classes,Controls,Forms,Menus,Windows; type TZoomer = class; TZoomAction = class(TCustomAction) private FZoomer: TZoomer; procedure SetZoomer(Value: TZoomer); protected function GetZoomer(Target: TObject): TZoomer; procedure Notification(AComponent: TComponent; Operation: TOperation); override; public destructor Destroy; override; function HandlesTarget(Target: TObject): Boolean; override; procedure UpdateTarget(Target: TObject); override; published property Caption; property Enabled; property HelpContext; property HelpKeyword; property HelpType; property Hint; property ImageIndex; property ShortCut; property SecondaryShortCuts; property Visible; property OnExecute; { This property could be omitted. But if you want to be able to override the default behavior of this action (zooming in on a TZoomer component),then you need to assign this event. From within the event handler you could invoke the default behavior manually. } property OnHint; property OnUpdate; property Zoomer: TZoomer read FZoomer write SetZoomer; end; TZoomInAction = class(TZoomAction) public constructor Create(AOwner: TComponent); override; procedure ExecuteTarget(Target: TObject); override; end; TZoomer = class(TCustomControl) public procedure ZoomIn; end; procedure Register; implementation procedure Register; begin RegisterComponents('RoyMKlever',[TZoomer]); RegisterActions('Zoomer',[TZoomInAction],nil); end; { TZoomAction } destructor TZoomAction.Destroy; begin if FZoomer <> nil then FZoomer.RemoveFreeNotification(Self); inherited Destroy; end; function TZoomAction.GetZoomer(Target: TObject): TZoomer; begin if FZoomer <> nil then Result := FZoomer else if (Target is TZoomer) and TZoomer(Target).Focused then Result := TZoomer(Target) else if Screen.ActiveControl is TZoomer then Result := TZoomer(Screen.ActiveControl) else { This should not happen! HandlesTarget is called before ExecuteTarget,or the action is disabled } Result := nil; end; function TZoomAction.HandlesTarget(Target: TObject): Boolean; begin Result := ((FZoomer <> nil) and FZoomer.Enabled) or ((FZoomer = nil) and (Target is TZoomer) and TZoomer(Target).Focused) or ((Screen.ActiveControl is TZoomer) and Screen.ActiveControl.Enabled); end; procedure TZoomAction.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent,Operation); if (Operation = opRemove) and (AComponent = FZoomer) then FZoomer := nil; end; procedure TZoomAction.SetZoomer(Value: TZoomer); begin if FZoomer <> Value then begin if FZoomer <> nil then FZoomer.RemoveFreeNotification(Self); FZoomer := Value; if FZoomer <> nil then FZoomer.FreeNotification(Self); end; end; procedure TZoomAction.UpdateTarget(Target: TObject); begin Enabled := HandlesTarget(Target); end; { TZoomInAction } constructor TZoomInAction.Create(AOwner: TComponent); begin inherited Create(AOwner); Caption := 'Zoom in'; Hint := 'Zoom in|Zooms in on the selected zoomer control'; ShortCut := Menus.ShortCut(VK_ADD,[ssCtrl]); end; procedure TZoomInAction.ExecuteTarget(Target: TObject); begin GetZoomer(Target).ZoomIn; { For safety,you cóuld check if GetZoomer <> nil. See remark in GetZoomer. } end; { TZoomer } procedure TZoomer.ZoomIn; begin { implementation of zooming in } end; end.
激活此操作(单击工具栏按钮或选择菜单项)将调用以下优先级:ZoomIn例程:
>如果已执行此操作,则手动在操作的关联属性中设置的Zoomer控件,如果已启用操作,则:否则:
>由应用程序请求Target,但仅当该目标是焦点Zoomer控件时,或者:
>整个应用程序中的活动控件,但前提是它是一个启用的Zoomer控件.
随后,只需添加ZoomOut操作:
type TZoomOutAction = class(TZoomAction) public constructor Create(AOwner: TComponent); override; procedure ExecuteTarget(Target: TObject); override; end; { TZoomOutAction } constructor TZoomOutAction.Create(AOwner: TComponent); begin inherited Create(AOwner); Caption := 'Zoom out'; Hint := 'Zoom out|Zooms out on the selected zoomer control'; ShortCut := Menus.ShortCut(VK_SUBTRACT,[ssCtrl]); end; procedure TZoomOutAction.ExecuteTarget(Target: TObject); begin GetZoomer(Target).ZoomOut; end;
请注意,操作组件需要在IDE中注册才能使用它们的设计时间.
在Delphi帮助中适用的阅读食物:
> Writing action components,
> How actions find their targets,
> Registering actions,
> What happens when an action fires,
> Updating actions,
> Setting up action lists
资料来源:www.nldelphi.com.