Link to Gordian's home page The Black Sea of Technology
Link to New TechnologyLink to White Papers


White Papers - Articles

"Creating Synergistic Components with Delphi"

James Callan - KOG Webzine (Page 9)

Macro Recording
Figures 3 contains the code for MACROREC.PAS. To simplify the example, memory try-fail blocks and other typical error handling have been eliminated. Examining the code, you will find that the constants MacroSep and EmptyMacro are defined to separate individual user click events and to define an empty macro respectively. Next, the class TEventSaver, containing two fields FHandle and FOnEvent, is defined to save client control window handles and event handlers respectively. Finally, the TMacroRecorder class is defined as a descendant of TComponent.

 

{ MACROREC - Component permits simplified macro handling for buttons
PURPOSE - Demonstrates dynamic overriding of event handlers in order to create a more synergistic component.
ASSUMES = For simplicity this component assumes that controls are not dynamically added and deleted to the form. }
unit Macrorec;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
const
MacroSep = ':';{ Macro command separator }
EmptyMacro = '';{ Empty macro }
type
TEventSaver = class { Create a type to save and restore original }
FHandle: HWnd; { OnClick event. The window handle is saved }
FOnEvent: TNotifyEvent; { in order to re-broadcast window messages. }
end;
TMacroRecorder = class(TComponent)
private
FRecording: Boolean; { Is a macro currently being recorded? }
FEventList: TStringList; { List of saved event handlers }
FMacro: String; { Displayable storage for short macros }
FOwnerCreate: TNotifyEvent; { Override form's OnCreate event }
protected
procedure RecordEvent(Sender: TObject); dynamic;
procedure DoCreate(Sender: TObject); dynamic;
public
constructorCreate(AOwner: TComponent); override;
destructor Destroy; override;
procedure BeginMacro;
procedure EndMacro;
procedure Playback;
published
property Recording: Boolean read FRecording write FRecording;
property Macro: String read FMacro;
end;
procedure Register;
implementation
constructor TMacroRecorder.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FMacro := EmptyMacro; { Initialize the properties }
FRecording := False;
FEventList := TStringList.Create;
if not (csDesigning in ComponentState) and (AOwner is TForm) then
begin
FOwnerCreate := TForm(AOwner).OnCreate; { Override event handler }
TForm(AOwner).OnCreate := DoCreate;
end;
end;
destructor TMacroRecorder.Destroy;
var
EventIdx: Integer; { Event handler index }
begin
for EventIdx:=FEventList.Count-1downto 0 do
begin
TEventSaver(FEventList.Objects[EventIdx]).Free;
FEventList.Delete(EventIdx);
end;
FEventList.Free;
inherited Destroy;
end;
{ DoCreate - Saves away existing event handlers for all button components }
{ and calls the original user OnCreate event handler }
{ NOTE - Load contains a key technique for dynamic overriding events }
procedure TMacroRecorder.DoCreate(Sender: TObject);
var
CompIdx: Integer; { The index of the current component on the form }
SavedEvent: TEventSaver; { Local pointer to SavedEvent }
Comp: TComponent;
begin
if Owner is TForm then { Limit the example to form wide macros }
begin
for CompIdx := 0 to Owner.ComponentCount-1 do
begin
Comp := Owner.Components[CompIdx];
{ Event overriding is method specific, thus type specific }
if (Comp is TButton) or
(Comp is TCheckBox) then { Limit macros to controls }
begin
SavedEvent := TEventSaver.Create;
with SavedEvent do
begin
if Comp is TButton then
with TButton(Comp) do { Upcast component }
begin
FOnEvent := OnClick; { Save old event }
FHandle := Handle; { Save windows handle }
OnClick := RecordEvent { Override event }
end;
if Comp is TCheckBox then
with TCheckBox(Comp) do
begin
FOnEvent := OnClick;
FHandle := Handle;
OnClick := RecordEvent
end;
end; { end saving event }
{ Now index the event handler by name for retrieval later }
FEventList.AddObject(Comp.Name, SavedEvent)
end { end limiting macros to controls }
end { end loop for each form component }
end; { end limiting macros to forms }
if Assigned(FOwnerCreate) then FOwnerCreate(Sender);
end;
procedure TMacroRecorder.BeginMacro;
begin
FMacro := EmptyMacro;
FRecording := True
end;
procedure TMacroRecorder.EndMacro;
begin
FRecording := False
end;
{ Plays back the events as recorded by triggering the original event as }
{ if the user initiated the message. }
procedure TMacroRecorder.Playback;
var
MacroIdx: Integer; { Index into macro string }
EventIdx: Integer; { Recorded index of the event to playback }
MacroCopy: String; { Local copy of macro }
MacroCmd: String; { Current macro command }
wParm: Word; { parameters for windows API calls }
LParm: LongInt; { to send simulated mouse clicks }
begin
wParm := 0;
LParm := 0;
MacroCopy := FMacro;
repeat { Process all macro commands }
MacroIdx := Pos(MacroSep, MacroCopy);
if MacroIdx > 0 then
begin
MacroCmd := Copy(MacroCopy, 1, MacroIdx-1);
Delete(MacroCopy, 1, MacroIdx);
EventIdx := FEventList.Indexof(MacroCmd); { Just in case we playback }
if FRecording then FRecording := False; { a button that begins a }
{ macro, turn off recording }
PostMessage(TEventSaver(FEventList.Objects[EventIdx]).FHandle,
WM_LBUTTONDOWN, wParm, LParm); { Send a mouse click event }
PostMessage(TEventSaver(FEventList.Objects[EventIdx]).FHandle,
WM_LBUTTONUP, wParm, LParm); { to the control }
Application.ProcessMessages; { Allow Windows to process the queue }
end
until MacroIdx < 0 {end processing }
end;
procedure TMacroRecorder.RecordEvent(Sender: TObject);
var
EventIdx: Integer; { Index of event to save in macro }
begin
EventIdx := FEventList.Indexof(TControl(Sender).Name);
if EventIdx -1 then
begin { Add event to macro, then call original handler if appropriate }
if FRecording then FMacro := FMacro+TControl(Sender).Name+MacroSep;
if Assigned(TEventSaver(FEventList.Objects[EventIdx]).FOnEvent) then
TEventSaver(FEventList.Objects[EventIdx]).FOnEvent(Sender);
end;
end;
procedure Register;
begin
RegisterComponents('Informant', [TMacroRecorder]);
end;
end.

Figure 3


TMacroRecorder maintains a state variable called FRecording that determines whether or not a macro is being recorded. A tri-state variable is unnecessary because playbacks continue uninterrupted. Figure 4 depicts the FEventList as it will appear at run-time. This linked list is used to maintain the original event handlers for all TMacroRecorder’s clients. The actual macros will be stored in the FMacro string variable. When the component gets created it does not yet know which controls are potential clients. To create this list, TMacroRecorder overrides its owning form’s OnCreate event and saves the form’s original OnCreate event handler in the FOnOwnerCreate variable. Component writers will recognize the familiar TNotifyEvent as being the standard method pointer that most Delphi standard event handlers use.



The protected methods RecordEvent and DoCreate will be explained later in more depth, but for overview they are the default event handlers used to replace client OnClick handling and the owning form’s OnCreate events respectively. The public constructor initializes the component. And, the destructor method frees its memory. The BeginMacro method empties the macro string and sets FRecording to True. The EndMacro method simply resets FRecording back to False. The Playback method sequentially parses the macro string, extracting the name of client controls. For each, it then indexes the client control’s window handle and original event handler in FEventList. It next uses the Windows API to post mouse down and mouse up messages to the control. This simulates actual button pressing for the control. It also assists the form in tracking focus when macros are running. Finally, Playback calls the control’s original event handler in order to complete the playback event. The last to be defined, published properties Recording and Macro expose the FRecording and FMacro variables respectively.

Previous PageNext Page

 

Signature Block of Gordian Solutions