【问题标题】:Logging MenuItem OnClick Event记录 MenuItem OnClick 事件
【发布时间】:2016-03-09 01:45:17
【问题描述】:

我有一个项目(Delphi 10 Seattle,win32),其中包含许多菜单和这些菜单中的许多项目。有些菜单项是在设计时创建的,有些是在运行时创建的。

我要做的是在触发 OnClick 事件时记录有关 TMenuItem 的一些信息,例如名称/标题、时间戳等。

我可以简单地在分配给 TMenuItem OnClick 事件的每个函数的开头添加一个过程调用,但我想知道是否有更优雅的解决方案。

还要注意,我尝试过Embarcadero's AppAnalytics,但我发现它没有提供我想要的信息或灵活性,而且价格相当昂贵。

编辑:我将添加更多信息,详细说明我考虑过的选项(我可能一开始就应该这样做)。

向我要记录的每个菜单项单击添加一个简单的功能,这意味着要为很多功能执行此操作,并且必须将其添加到添加的每个新菜单项中。

procedure TSomeForm.SomeMenuItem1Click(Sender: TObject);
var
    item : TMenuItem;
begin
    item := Sender as TMenuItem;
    LogMenuItem(item);  // Simple log function added to the start of each menuitem click
end;

通过“更优雅的解决方案”,我的意思是可以添加一个“挂钩”,以便所有 TMenuItem OnClick 事件在调用分配给 OnClick 事件的过程之前触发另一个过程(它将执行日志记录)。

或者我考虑的另一个选项是创建一个从 TMenuItem 继承的类,该类将覆盖 TMenuItem.Click 并在生成 OnClick 事件之前进行日志记录。但是后来我不知道如果没有大量工作重新制作菜单,这将如何用于设计时菜单项。

【问题讨论】:

  • 小提示:停止使用onlick事件,改用Actions。
  • 将所有点击汇集到同一个事件,使用 Sender 评估并记录每个点击,然后将每个点击路由到自己的事件。
  • @whosrdaddy 您能否概述一下使用 Actions 而非 OnClick 的好处
  • @JohnEasley 您将如何确定将路由到哪个事件?从可维护性的角度来看,一个简单的 if 或 case 语句似乎很难阅读并且很痛苦
  • 你检查发送者的名字,然后调用一个同名的过程..这根本不难维护......

标签: delphi logging analytics delphi-10-seattle


【解决方案1】:

我绝对同意操作是要走的路,但为了完整起见,以及那些您想使用旧式菜单快速调试应用程序的情况,这里有一个可以与菜单项一起使用的单元。如果菜单项具有链接到它们的操作,它甚至可以工作,但它不适用于具有像TActionMainMenuBar 这样的操作的任何其他控件。所有调试代码都在这个单元中,以使您的正常代码保持整洁。只需将单元添加到 uses 子句并使用任何适用的组件调用 StartMenuLogging,例如一个菜单组件、表单组件甚至Application!它下面的树中的任何菜单项都将被挂钩。因此,您可以在生产代码中仅使用这两行来调试所有形式的所有菜单点击。您可以使用StopMenuLogging 停止,但它是可选的。 警告:这个单元没有经过适当的测试 - 我拿了一个我写的旧调试单元并为此清理了它,只是进行了表面测试。

unit LogMenuClicks;


interface

uses
  Classes;

procedure StartMenuLogging(AComponent: TComponent);
procedure StopMenuLogging(AComponent: TComponent);
procedure StopAllMenuLogging;


implementation

uses
  SysUtils,
  Menus;


type
  PLoggedItem = ^TLoggedItem;
  TLoggedItem = record
    Item: TMenuItem;
    OldClickEvent: TNotifyEvent;
  end;

  TLogManager = class(TComponent)
  private
    FList: TList;
    FLog: TFileStream;

    procedure Delete(Index: Integer);
    function FindControl(AItem: TMenuItem): Integer;
    procedure LogClick(Sender: TObject);
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure AddControl(AItem: TMenuItem);
    procedure RemoveControl(AItem: TMenuItem);
  end;

  var
    LogMan: TLogManager = nil;

{ TLogManager }

constructor TLogManager.Create(AOwner: TComponent);
begin
  inherited;

  FLog := TFileStream.Create(ChangeFileExt(ParamStr(0), '.log'), fmCreate or fmShareDenyWrite);
  FList := TList.Create;
end;

destructor TLogManager.Destroy;
var
  i: Integer;
begin
  i := FList.Count - 1;
  while i >= 0 do
    Delete(i);
  FList.Free;

  FLog.Free;

  inherited;
end;

procedure TLogManager.Notification(AComponent: TComponent; Operation: TOperation);
begin
  if Operation = opRemove then
    RemoveControl(TMenuItem(AComponent));

  inherited;
end;

procedure TLogManager.Delete(Index: Integer);
var
  li: PLoggedItem;
begin
  li := FList[Index];

  with li^ do
  begin
    Item.RemoveFreeNotification(Self);
    Item.OnClick := OldClickEvent;
  end;

  Dispose(li);
  FList.Delete(Index);
end;

function TLogManager.FindControl(AItem: TMenuItem): Integer;
begin
  Result := FList.Count - 1;
  while (Result >= 0) and (PLoggedItem(FList[Result]).Item <> AItem) do
    Dec(Result);
end;

procedure TLogManager.AddControl(AItem: TMenuItem);
var
  li: PLoggedItem;
begin
  if not Assigned(AItem) then
    Exit;

  if FindControl(AItem) >= 0 then
    Exit;

  New(li);
  li.Item := AItem;
  li.OldClickEvent := AItem.OnClick;
  AItem.OnClick := LogClick;
  FList.Add(li);

  AItem.FreeNotification(Self);
end;

procedure TLogManager.RemoveControl(AItem: TMenuItem);
var
  i: Integer;
begin
  if Assigned(AItem) then
  begin
    i := FindControl(AItem);
    if i >= 0 then
      Delete(i);
  end;
end;

procedure TLogManager.LogClick(Sender: TObject);
var
  s: string;
begin
  s := Format('%s: %s' + sLineBreak, [TComponent(Sender).Name, FormatDateTime('', Now)]);
  FLog.WriteBuffer(s[1], Length(s));
  PLoggedItem(FList[FindControl(TMenuItem(Sender))]).OldClickEvent(Sender);
end;


procedure StartMenuLogging(AComponent: TComponent);

  procedure CheckControls(Comp: TComponent);
  var
    i: Integer;
  begin
    if Comp is TMenuItem then
      LogMan.AddControl(TMenuItem(Comp))
    else
      for i := 0 to Comp.ComponentCount - 1 do
        CheckControls(Comp.Components[i]);
  end;

begin
  if not Assigned(LogMan) then
    LogMan := TLogManager.Create(nil);

  CheckControls(AComponent);
end;

procedure StopMenuLogging(AComponent: TComponent);

  procedure CheckControls(Comp: TComponent);
  var
    i: Integer;
  begin
    if Comp is TMenuItem then
      LogMan.RemoveControl(TMenuItem(Comp))
    else
      for i := 0 to Comp.ComponentCount - 1 do
        CheckControls(Comp.Components[i]);
  end;

begin
  if Assigned(LogMan) then
    CheckControls(AComponent);
end;

procedure StopAllMenuLogging;
begin
  LogMan.Free;
end;


initialization

finalization
  if Assigned(LogMan) then
     LogMan.Free;

end.

【讨论】:

  • 感谢 Jannie,这绝对可以让我做我想做的事,而无需更改所有菜单项以使用操作,但我将花时间将它们全部更改为使用操作,因为它是一般的一致认为行动更好
  • @Clayton,是的,如果可能,请采取行动,当您在寻找错误时没有时间更改大型项目时,请保留我的单位以应对紧急情况!
  • 珍妮,死是'n goeie idee en dit werk。我花了几个小时试图找到一种通用的方法来挂钩任何菜单和弹出项(在一个非常大的项目上)以确定用户点击的内容。我使用 FindDragTarget 来捕获 GUI 上任何形式的大多数控件,但它不会捕获 Menu 和 Popup Menu 项。要获取菜单项,您必须拦截 WMMENUSELECT 但您必须为每个表单执行此操作。丹基珍妮!
【解决方案2】:

使用动作来实现这一点要容易得多。这样做的好处是您可以拾取由菜单以外的 UI 元素(例如工具栏、按钮等)调用的操作。

根据需要使用action listaction manager。例如,对于动作列表,动作列表对象有一个OnExecute 事件,该事件在执行任何动作时触发。您可以侦听该事件并记录正在执行的操作的详细信息。

【讨论】:

  • 感谢@David,使用操作列表的 OnExecute 事件正是我一直在寻找的东西,简单、容易并且不需要大量工作来更改代码即可使用的
猜你喜欢
  • 1970-01-01
  • 2012-12-01
  • 2012-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多