Logging MenuItem OnClick Event

У меня есть проект (Delphi 10 Seattle, win32) с множеством меню и множеством пунктов в этих меню. Некоторые элементы меню создаются во время разработки, некоторые - во время выполнения.

Я хочу записать некоторую информацию о TMenuItem, такую ​​как имя / заголовок, временная метка и т. Д., Когда запускается событие OnClick.

Я мог бы просто добавить вызов процедуры в начало каждой функции, которая назначена событию TMenuItem OnClick, но мне было интересно, есть ли более элегантное решение.

Также следует отметить, что я пробовал AppAnalytics от Embarcadero, но обнаружил, что он не предоставил мне информацию или гибкость, которую я хотел, и было довольно дорого.

Изменить: я добавлю дополнительную информацию о том, какие варианты я рассмотрел (что, вероятно, мне следовало сделать для начала).

Простое добавление функции к каждому элементу меню, щелчок по которому я хочу записать, что означало бы выполнение этого для множества функций и необходимость добавления ее к каждому новому добавленному элементу меню.

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. Но тогда я не знал, как это будет работать с элементами меню времени разработки без большой работы по переделке меню.


person Clayton Johnson    schedule 04.12.2015    source источник
comment
Небольшой совет: прекратите использовать события onlick и используйте вместо этого Actions.   -  person whosrdaddy    schedule 04.12.2015
comment
направлять все клики к одному и тому же событию, использовать Sender для оценки и регистрации каждого из них, а затем направлять каждый к своему собственному событию.   -  person John Easley    schedule 04.12.2015
comment
@whosrdaddy, не могли бы вы рассказать о преимуществах использования Actions над OnClick?   -  person Clayton Johnson    schedule 04.12.2015
comment
@JohnEasley, как бы вы определить, на какое событие будет направлено сообщение? Кажется, что простое выражение if или case будет трудно читать и будет неудобно с точки зрения ремонтопригодности.   -  person Clayton Johnson    schedule 04.12.2015
comment
Вы проверяете имя отправителя и вызываете процедуру с тем же именем ... это совсем несложно поддерживать ...   -  person John Easley    schedule 04.12.2015
comment
@JohnEasley OK звучит как возможное решение, не могли бы вы написать полный ответ с примером, потому что я не уверен, как вы это сделаете.   -  person Clayton Johnson    schedule 04.12.2015
comment
В более сложных сценариях графического интерфейса, где можно иметь элементы панели инструментов, пункты меню, всплывающие меню и т. Д., Выполняющие одно и то же, легче поддерживать действия, чем назначать обработчики onclick для каждого отдельного элемента графического интерфейса.   -  person whosrdaddy    schedule 04.12.2015
comment
Вот хорошая статья о действиях и их использовании ...   -  person whosrdaddy    schedule 04.12.2015
comment
Кстати, вы можете направить AppAnalytics на свой собственный сервер, а также улучшить его, чтобы регистрировать больше   -  person Sir Rufo    schedule 04.12.2015


Ответы (2)


Этого намного проще добиться с помощью действий. Это дает преимущество в том, что вы выбираете действия, вызываемые элементами пользовательского интерфейса, отличными от меню, например панелями инструментов, кнопками и т. Д.

Используйте список действий или диспетчер действий, как вам удобнее. Например, со списком действий объект списка действий имеет _1 _ событие, которое срабатывает при выполнении любого действия. Вы можете прослушивать это событие и записывать подробности выполняемого действия.

person David Heffernan    schedule 04.12.2015
comment
Спасибо @David, использование события OnExecute списка действий - это именно то, что я искал, простой, легкий и не требует много работы, чтобы изменить код, чтобы использовать - person Clayton Johnson; 04.12.2015

Я абсолютно согласен с тем, что действия - это лучший способ, но для полноты картины и тех случаев, когда вы хотите быстро отладить приложение с использованием меню старого стиля, вот модуль, который вы можете использовать с элементами меню. Он будет работать даже в том случае, если с элементами меню связаны действия, но не будет работать с любыми другими элементами управления с такими действиями, как 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.
person Jannie Gerber    schedule 04.12.2015
comment
Спасибо, Дженни, это определенно позволило бы мне делать то, что я хотел, не меняя все элементы меню для использования действий, но я собираюсь потратить время и изменить их все, чтобы использовать действия, поскольку по общему мнению, действия лучше - person Clayton Johnson; 06.12.2015
comment
@Clayton, да, действуйте, если возможно, и держите мое подразделение под рукой на случай чрезвычайных ситуаций, когда у вас нет времени менять большой проект при поиске ошибки! - person Jannie Gerber; 07.12.2015
comment
Дженни, умри, это крутая идея и работа. Я потратил несколько часов, пытаясь найти общий способ привязать любое меню и всплывающее окно (в очень большом проекте), чтобы определить, что пользователь щелкнул. Я использую FindDragTarget для захвата большинства элементов управления в графическом интерфейсе для любой формы, но он не захватывает элементы меню и всплывающего меню. Чтобы получить элементы меню, вам нужно перехватить WMMENUSELECT, но затем вы должны сделать это для каждой формы. Данки Дженни! - person Frank Pedro; 31.05.2021