1. Technology
Send to a Friend via Email
How to dynamically set event handling procedures to controls NOT being inherited from a common ancestor
If a control that can be moved or resized at run-time has event handlers that handle the OnClick event and/or general mouse events such as OnMouseDown, etc., the code that moves or resizes the control needs to temporarily replace those handlers with "special" methods and restore the original handlers when the move or resize operation is complete.
 More of this Feature
• Download Project Source

• Part 1
• Part 2
 Join the Discussion
"Post your views, comments, questions and doubts to this article."
Discuss!
 Related Resources
• Delphi's RTTI
• Using Delphi components
• Owner vs. Parent
• Mouse events
• Dragging and dropping

This is the last part of the "How to move and resize Delphi form controls at run-time" series. In the first part you learned how to enable dragging and resizing controls with the mouse, while the application is running.
The second part described how to add eight sizing handles to mimic the resizing of the control in the IDE (design-time), at run-time.

Storing and swapping event-handling procedures...
While the code provided in the first two parts does the job of allowing a user to drag and resize controls at run time it has some side effects and can be hard to manage.
Firstly, the code is attached to the OnMouseDown, OnMouseMove, and OnMouseUp events for a control. Any code not related to run-time movement needs to be mixed with the "ControlMouseDown/Move/Up" procedures.
Secondly, if for example the TPanel object is allowed to be moved at run-time the OnClick event handling procedure *will* get executed if panel reposition is taking place.

To overcome those issues, it would be best to pull out the "run-time movement" code into a separate unit (defining a custom component). This brings us to the custom "TMover" component:

type
  TMover = class(TComponent)
  public
  procedure Add(Control : TControl);
  ...
    property MovableControls : TComponentList
    property Enabled : boolean
    ...
  end;

The TMover class defines a property MovableControls of type TComponentList. A public procedure Add is used to add a control to the list of controls that are allowed to be moved (and resized) at run time.

Moving and resizing at Run-Time

Given the Delphi form (from the last two parts), the OnCreate event handler might look like:

var
  mover : TMover;
...
procedure TForm1.FormCreate(Sender: TObject);
begin
  mover := TMover.Create(self);

  mover.Add(Button1);
  mover.Add(Edit1);
  mover.Add(Panel1);
  mover.Add(Button2);
  mover.Add(ListBox1);
end; (*FormCreate*)

The TMover's Enabled property specifies if controls added to the MovableControls list can be moved and resized, or not. This is where the fun begins...

For each of the controls in the MovableControls list we have to:

1. Remeber (store in some list) any existing mouse related event handlers,
2. Attach custom handling procedures,
3. When run-time moving and sizing is finished (TMover.Enabled := false) restore original event handling procedures to mouse related events.

Now, one could say that this task is easy. You simply use an assignment like:

// no-go code
TWinControl(MovableControls[i]).OnMouseUp := ControlMouseUp

Unfortunately, the TWinControl does not provide the "OnMouse???" events as published.

RTTI, help!
In short, Runtime Type Information provides information about an object's data type that is set into memory at run-time.

Using Delphi' RTTI methods GetMethodProp and SetMethodProp we can read and assign event handling procedure to a set of controls that are not inherited from the same ancestor.

Here's what happens when you set the Enabled property of the TMover component (simplified version):

procedure TMover.SetEnabled(const Value: boolean);
var
  idx : integer;
  oldMethod : TMethod;
  newMethod : TMethod;
  ctrl : TControl;
begin
  if value = FEnabled then Exit;
  FEnabled := Value;

  if Enabled then
  begin
    MouseDownMethods := nil; //clear

    SetLength(MouseDownMethods,MovableControls.Count);

    //store mouse related event handlers
    for idx := 0 to -1 + MovableControls.Count do
    begin
      ctrl := TControl(MovableControls[idx]);

      oldMethod := GetMethodProp(ctrl, 'OnMouseDown');
      MouseDownMethods[idx].Code := oldMethod.Code;
      MouseDownMethods[idx].Data := oldMethod.Data;
      newMethod.Code := MethodAddress('ControlMouseDown');
      newMethod.Data := Pointer(self);
      SetMethodProp(ctrl, 'OnMouseDown', newMethod);
    end;
  end
  else //disabled
  begin
    //restore default Mouse related event handler
    for idx := 0 to -1 + MovableControls.Count do
    begin
      ctrl := TControl(MovableControls[idx]);

      oldMethod.Code := MouseDownMethods[idx].Code;
      oldMethod.Data := MouseDownMethods[idx].Data;
      SetMethodProp(ctrl, 'OnMouseDown', oldMethod);
    end;
  end;
end; (*SetEnabled*)

The MouseDownMethods is a dynamic array of TMethod records - and is used to store original event handling procedures.
For each control in the MovableControls, the RTTI's GetMethodProp method returns a value of type TMethod. This value (Control's OnMouseDown event handling method!) is stored in the MouseDownMethods array. Using the SetMethodProp RTTI method, the ControlMouseDown procedure is assigned to handle the OnMouseDown Event.
When Enabled is set to false, we simply revert to the original event handling procedures!

While this might sound like magic, it works like a charm! Download the sample application and try for yourself.

If you need any kind of help at this point, please post to the Delphi Programming Forum where all the questions are answered and beginners are treated as experts.

First part > Move and resize at run-time > Part 1, 2, 3

©2014 About.com. All rights reserved.