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:
- Create a new VCL application
- Add a TButton and a TProgressBar to the form
- 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.