9

Background

I've created a GUI using some FireMonkey controls.

  • Some controls are animated and their appearance updates automatically.
  • Some controls only update in response to user interaction (sliders etc).

Problem

Interaction with the user controls prevents updates to the animated controls, resulting in jerky discontinuous animation.

Video of glitchy animation

The animated control in the video above is driven by a TTimer component. The problem persists when using FireMonkey's animation components.

Investigation

The slider controls call Repaint() when adjusted. Smoothly adjusting a slider will generate a dense stream of Repaint() calls which block other controls from being updated.

What To Do?

Freezing animations while one control is continuously updated is not appropriate for my application. My first thought is to swap the Repaint() calls for something similar to the VCL Invalidate() method, but FireMonkey doesn't have anything comparable AFAIK.

Is there a good workaround for this problem?

Shannon Matthews
  • 9,649
  • 7
  • 44
  • 75
  • Have you tried to replace TTimer with some TAnimation descendants? – Torbins Dec 07 '11 at 10:52
  • @Torbins: Yes, I tried with a TFloatAnimation component. It was no better. Looking at the FMX source, the TAnimation components are triggered by a global TTimer object as well. – Shannon Matthews Dec 07 '11 at 11:20
  • Then maybe a separate thread? But I am not sure whether FireMonkey is threadsafe or not. Probably not. – Torbins Dec 07 '11 at 11:43
  • 3
    So the only thing left: wait until they fix this issue among lots of others. Some people even call FireMonkey a "technology preview". – Torbins Dec 07 '11 at 11:46
  • @Torbins The FireMonkey code is not protected by critical sections AFAIK, so I suspect it is not thread-safe. Just like the VCL. What about overriding RePaint and use your own dedicated timer-based method? – Arnaud Bouchez Dec 07 '11 at 12:54
  • AFAICS the FMX Repaint method actually is similar to VCL's Invalidate. – Giel Dec 07 '11 at 13:23
  • @Giel: As I understand it, FMX Repaint forces the the control to be repainted immediately. The VCL Invalidate method is more like adding the control to a repaint queue. – Shannon Matthews Dec 08 '11 at 07:14

1 Answers1

4

I've created a timer based repaint method as Arnaud Bouchez suggested in the comments above. So far it seems to work.

Code

unit FmxInvalidateHack;

interface

uses
  Fmx.Types;

procedure InvalidateControl(aControl : TControl);


implementation

uses
  Contnrs;

type
  TInvalidator = class
  private
  protected
    Timer : TTimer;
    List  : TObjectList;
    procedure Step(Sender : TObject);
  public
    constructor Create;
    destructor Destroy; override;

    procedure AddToQueue(aControl : TControl);
  end;

var
  GlobalInvalidator : TInvalidator;

procedure InvalidateControl(aControl : TControl);
begin
  if not assigned(GlobalInvalidator) then
  begin
    GlobalInvalidator := TInvalidator.Create;
  end;
  GlobalInvalidator.AddToQueue(aControl);
end;


{ TInvalidator }

constructor TInvalidator.Create;
const
  FrameRate = 30;
begin
  List  := TObjectList.Create;
  List.OwnsObjects := false;

  Timer := TTimer.Create(nil);
  Timer.OnTimer  := Step;
  Timer.Interval := round(1000 / FrameRate);
  Timer.Enabled  := true;
end;

destructor TInvalidator.Destroy;
begin
  Timer.Free;
  List.Free;
  inherited;
end;

procedure TInvalidator.AddToQueue(aControl: TControl);
begin
  if List.IndexOf(aControl) = -1 then
  begin
    List.Add(aControl);
  end;
end;

procedure TInvalidator.Step(Sender: TObject);
var
  c1: Integer;
begin
  for c1 := 0 to List.Count-1 do
  begin
    (List[c1] as TControl).Repaint;
  end;
  List.Clear;
end;


initialization

finalization
  if assigned(GlobalInvalidator) then GlobalInvalidator.Free;

end.

==

Usage

A control can be repainted by calling:

InvalidateControl(MyControl);

The InvalidateControl() procedure doesn't repaint the control immediately. Instead it adds the control to a list. A global timer later checks the list, calls Repaint() and removes the control from the list. Using this method, a control can be invalidated as needed but will not block other controls from being updated, as rapid Repaint() calls do.

Shannon Matthews
  • 9,649
  • 7
  • 44
  • 75