FMX issue – inability to safely process paint messages immediately

There are times when you want to repaint a control immediately, typically to give an indication to the user that something is actually happening during a lengthy bit of processing. In the VCL, this is covered by a troika of methods on TControl:

  • Invalidate posts a message to the event queue saying the control needs to be repainted.
  • Update forces processing of any paint messages pending for the control.
  • Repaint calls Invalidate and Update in succession.

That said, sometimes it isn’t even necessary to call one of these methods if the control wraps a native API widget, and the native API does something special behind the scenes. For example, try this:

  1. Create a new VCL application
  2. Add a TButton and a TProgressBar to the form
  3. Add the following handler for the button’s OnClick event:
uses
  System.Diagnostics;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  Stopwatch: TStopwatch;
begin
  ProgressBar1.Position := ProgressBar1.Min;
  for I := ProgressBar1.Min + 1 to ProgressBar1.Max do
  begin
    Stopwatch := TStopwatch.StartNew;
    repeat
    until Stopwatch.ElapsedMilliseconds >= 50;
    ProgressBar1.Position := I;
  end;
end;

When I run this code, I find the progress bar still updating, if not entirely smoothly. Adding an Update call fixes that:

    ProgressBar1.Position := I;
    ProgressBar1.Update; //!!!added
  end;
end;

Turn to FireMonkey however, and this core functionality is missing:

  • While the FMX TControl has a Repaint method, it corresponds to the VCL Invalidate, not the VCL Repaint. (Oddly, the FMX TControl also has a InvalidateRect method to invalidate just a certain area of the control; given that, why confuse matters with the Repaint naming…?)
  • Since FMX to a large extent reimplements native widgets instead of wrapping them like the VCL prefers to do, niceties like the VCL progress bar doing its own updating don’t happen with the FMX equivalent.

To see this, try reimplementing the VCL demo presented above. In doing so the event handler will need to be tweaked a bit, mainly because the FMX TProgressBar uses Single not Integer values for its progress values:

uses
  System.Diagnostics;

procedure TForm2.Button1Click(Sender: TObject);
var
  Stopwatch: TStopwatch;
begin
  ProgressBar1.Value := ProgressBar1.Min;
  while ProgressBar1.Value < ProgressBar1.Max do
  begin
    Stopwatch := TStopwatch.StartNew;
    repeat
    until Stopwatch.ElapsedMilliseconds >= 50;
    ProgressBar1.Value := ProgressBar1.Value + 1;
  end;
end;

Run it, and you will find the progress bar visually updates in one big leap at the end. Adding a Repaint call doesn’t help:

    ProgressBar1.Value := ProgressBar1.Value + 1;
    ProgressBar1.Repaint; //!!!added
  end;
end;

What does ‘work’ is adding a call to Application.ProcessMessages instead. Is this a good idea however? Hardly: Application.ProcessMessages, as its name implies, processes all messages currently outstanding (paint and otherwise) for all controls (and if there are any other message-handling primitives in the application, those too), which can soon lead to serious re-entrancy issues.

Nonetheless, on Windows it is possible to process paint messages for a FMX form using something like the following code:

uses
  WinApi.Windows, FMX.Platform.Win;

procedure ProcessPaintMessages(const Form: TCustomForm);
begin
  UpdateWindow(WindowHandleToPlatform(Form.Handle).Wnd);
end;

This is XE4/5; for XE3 use the following (a stable API isn’t one of FMX’s strengths!):

uses
  WinApi.Windows, FMX.Platform.Win;

procedure ProcessPaintMessages(const Form: TCustomForm);
begin
  UpdateWindow(FmxHandleToHWND(Form.Handle));
end;

You have to do things at the form level since a FMX control on Windows isn’t backed by its own HWND. Further, on OS X, trying something similar appears to confuse the FMX styling system, so all in all… the situation isn’t great. For a recent QC report on the matter, see here: http://qc.embarcadero.com/wc/qcmain.aspx?d=119083. Unfortunately the person who mans QC hasn’t understood the point of it, and I wouldn’t be surprised if there are other similar reports on the system.

Advertisements

7 thoughts on “FMX issue – inability to safely process paint messages immediately

  1. I’ve voted it up on QC and I suggest any other Delphi developers who agree do so too.

    It’s incredibly frustrating to run into issues with FM like this; the cross-platform principle is a great idea but the actual implementation is seriously lacking at present. 😦

  2. I would add, to anyone considering using FireMonkey to create anything much beyond a “Goodbye Cruel World” App (the FireMonkey version of the classic “Hello World” Application)… I have one word for you. DON’T.

    I just spent four months going down that rabbit hole and ultimately had to scrap the entire project.

  3. I have posted my experience (a rambling at best) of such on my blog and referenced your post as a better read. I am new to all this blogging thing, so thought I should share 🙂
    http://fmxprogramming.blogspot.com/2013/09/rambling-fmx-control-repaint-menace.html

    As for your solution, it is indeed better than fiddling with the FMX provided methods but I have found it to be inefficient for congested or control heavy forms (as was expected). With FMX, I kind of started to build layered GUI designs rather than multiple form based GUI structures. Which as understood by some will congest the code with all the functionality but I have a very formatted coding style which minimizes such and also makes it more easier to deal with.

  4. I found a solution to this, but which actually makes a sort of a command queue and sends out repaint messages for every object in a list every interval of time. One has to add the object requiring repaint in the list at every instance (Yes, its cumbersome). All my paint problems have been resolved, here is the link: http://stackoverflow.com/a/8424750/1388291

    For code clarity and after weighing the call overhead and the open array management overhead that I restrained myself from changing the InvalidateControl method to accept an open array of TControl rather than a single one but they do gobble up a whole lot of lines…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s