Stop event propagation in Delphi 7

delphi, delphi-7, event-handling, mouseevent

Solution

Please note, the following does not explicitly answer the question here. It's more a proposal to the concept re-design (redirect OnClick events instead of adding extra OnMouseUp). It's about how to redirect OnClick event handler (if assigned some) of all components (might be filtered, if needed) to another (common) OnClick event handler. It includes also a method for restoring them to the original state.

In the following example I'll try to show you how to replace and then optionally restore the OnClick event handlers (if the component has written some) by the specific one. This is done to all components having the OnClick event published, so you don't need to know in advance if the component class has OnClick event available or not (but it can very simply be modified to use only a specific class).

The code consists from the following:

OnSpecialClick - it is the event handler to what all OnClick events will be binded when you call the ReplaceOnClickEvents procedure, notice that it must be published to be visible for RTTI !!!

Button1Click - represents here the old event handler which should be replaced, it is binded to the Button1.OnClick event at design time

ReplaceOnClickEvents - method, which iterates through all components on the form and checks if the currently iterated one has the OnClick event handler assigned; if so, it stores it into a backup collection and replace this event handler by the OnSpecialClick

RestoreOnClickEvents - method, which restores the original OnClick event handlers; it iterates through the backup collection and assign the event methods to its stored component instances

CheckBox1Click - this check box click event is meant to be the switch between the common and a special mode (CheckBox1 checked state means to be the special mode), only this OnClick event is not replaced by the ReplaceOnClickEvents call (because you wouldn't be able to restore the mode back to normal)

And here it is:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, TypInfo, StdCtrls, Contnrs;

type
  TEventBackup = class
    Component: TComponent;
    OnClickMethod: TMethod;
  end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    CheckBox1: TCheckBox;
    procedure Button1Click(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    procedure ReplaceOnClickEvents;
    procedure RestoreOnClickEvents;
  published
    procedure OnSpecialClick(Sender: TObject);
  end;

var
  Form1: TForm1;
  EventBackupList: TObjectList;

implementation

{$R *.dfm}

procedure TForm1.OnSpecialClick(Sender: TObject);
begin
  ShowMessage('Hi, I''m an OnSpecialClick event message!');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage('Hi, I''m just that boring original OnClick event message!');
end;

procedure TForm1.ReplaceOnClickEvents;
var
  I: Integer;
  Component: TComponent;
  EventMethod: TMethod;
  EventBackup: TEventBackup;
begin
  for I := 0 to ComponentCount - 1 do
  begin
    Component := Components[I];
    if Component = CheckBox1 then
      Continue;

    if IsPublishedProp(Component, 'OnClick') then
    begin
      EventMethod := GetMethodProp(Component, 'OnClick');
      if Assigned(EventMethod.Code) and Assigned(EventMethod.Data) then
      begin
        EventBackup := TEventBackup.Create;
        EventBackup.Component := Component;
        EventBackup.OnClickMethod := EventMethod;
        EventBackupList.Add(EventBackup);
        EventMethod.Code := MethodAddress('OnSpecialClick');
        EventMethod.Data := Pointer(Self);
        SetMethodProp(Component, 'OnClick', EventMethod);
      end;
    end;
  end;
end;

procedure TForm1.RestoreOnClickEvents;
var
  I: Integer;
  EventBackup: TEventBackup;
begin
  for I := 0 to EventBackupList.Count - 1 do
  begin
    EventBackup := TEventBackup(EventBackupList[I]);
    SetMethodProp(EventBackup.Component, 'OnClick', EventBackup.OnClickMethod);
  end;
  EventBackupList.Clear;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    ReplaceOnClickEvents
  else
    RestoreOnClickEvents;
end;

initialization
  EventBackupList := TObjectList.Create;
  EventBackupList.OwnsObjects := True;

finalization
  EventBackupList.Free;

end.

Problem

I'm stuck on a problem in Delphi 7 about event propagation (due to my ignorance). I am asked to dynamically attach an `OnMouseUp` event handler on some controls on a form (and I'm fine with that thing), but if the `OnMouseUp` is present, the `OnClick` event on that control must not be processed. Background If you are asking the reason behind this, well, I'm in charge to modify an old production monitoring application (sigh) that from now on must accommodate a conditional behaviour for some controls, in direct response to a former click on a special function button. Some of those controls have an `OnClick` event handler already; the first solution the team came up with was to punctually intervene on each `OnClick` handler and manage out the contextual actions in relation to the special function button status. I suggested to take advantage of the Object-Oriented design already in place for the application forms: they all inherit from the same custom ancestor object, so I planned to insert an initialization method there to dynamically attach `OnMouseUp` events to the controls that are declared to support it in subclasses. The need I'm not hereby asking a validation or questioning on the (possible lack of) design goodness about all this (by the way, after a lot of thinking and reasoning it seemed to be the path we can walk with less pain); my problem is that for such design to take place I'd have to let dynamically-attached `OnMouseUp` event handlers stop event propagation to the pre-existent `OnClick` events on those controls. Is it possible with Delphi 7?

Original source

Related problems