Since the TTabSheets aren't a parent of the form as far as VCL is concerned (didn't want to use dynamic RTL, and plugins made in other languages) I have to handle resizes manually. I do this like below:
var ChildHandle : DWORD; begin If Assigned(pcMain.ActivePage) Then begin ChildHandle := FindWindowEx(pcMain.ActivePage.Handle, 0, 'TfrmPluginForm', nil); If ChildHandle > 0 Then begin SetWindowPos(ChildHandle, 0, 0, 0, pcMain.ActivePage.Width, pcMain.ActivePage.Height, SWP_NOZORDER + SWP_NOACTIVATE + SWP_NOCOPYBITS); end; end;
Now, my problem is that when the application is resized, all the TGroupBoxes and the TLabels inside the TGroupBoxes flicker. The TLabels that are not inside TGroupboxes are fine and don't flicker.
Things I've tried:
Any ideas?
The only thing I have found to work well is to use the WS_EX_COMPOSITED
window style. This is a performance hog so I only enable it when in a sizing loop. It is my experience that, with the built-in controls, in my app, flickering only occurs when resizing forms.
You should first perform a quick test to see if this approach will help you by simply adding the WS_EX_COMPOSITED
window style to all your windowed controls. If that works you can consider the more advanced approach below:
Quick hack
procedure EnableComposited(WinControl: TWinControl); var i: Integer; NewExStyle: DWORD; begin NewExStyle := GetWindowLong(WinControl.Handle, GWL_EXSTYLE) or WS_EX_COMPOSITED; SetWindowLong(WinControl.Handle, GWL_EXSTYLE, NewExStyle); for i := 0 to WinControl.ControlCount-1 do if WinControl.Controls[i] is TWinControl then EnableComposited(TWinControl(WinControl.Controls[i])); end;
Call this, for example, in the OnShow
for your TForm
, passing the form instance. If that helps then you really should implement it more discerningly. I give you the relevant extracts from my code to illustrate how I did that.
Full code
procedure TMyForm.WMEnterSizeMove(var Message: TMessage); begin inherited; BeginSizing; end; procedure TMyForm.WMExitSizeMove(var Message: TMessage); begin EndSizing; inherited; end; procedure SetComposited(WinControl: TWinControl; Value: Boolean); var ExStyle, NewExStyle: DWORD; begin ExStyle := GetWindowLong(WinControl.Handle, GWL_EXSTYLE); if Value then begin NewExStyle := ExStyle or WS_EX_COMPOSITED; end else begin NewExStyle := ExStyle and not WS_EX_COMPOSITED; end; if NewExStyle<>ExStyle then begin SetWindowLong(WinControl.Handle, GWL_EXSTYLE, NewExStyle); end; end; function TMyForm.SizingCompositionIsPerformed: Boolean; begin //see The Old New Thing, Taxes: Remote Desktop Connection and painting Result := not InRemoteSession; end; procedure TMyForm.BeginSizing; var UseCompositedWindowStyleExclusively: Boolean; Control: TControl; WinControl: TWinControl; begin if SizingCompositionIsPerformed then begin UseCompositedWindowStyleExclusively := Win32MajorVersion>=6;//XP can't handle too many windows with WS_EX_COMPOSITED for Control in ControlEnumerator(TWinControl) do begin WinControl := TWinControl(Control); if UseCompositedWindowStyleExclusively then begin SetComposited(WinControl, True); end else begin if WinControl is TPanel then begin TPanel(WinControl).FullRepaint := False; end; if (WinControl is TCustomGroupBox) or (WinControl is TCustomRadioGroup) or (WinControl is TCustomGrid) then begin //can't find another way to make these awkward customers stop flickering SetComposited(WinControl, True); end else if ControlSupportsDoubleBuffered(WinControl) then begin WinControl.DoubleBuffered := True; end; end; end; end; end; procedure TMyForm.EndSizing; var Control: TControl; WinControl: TWinControl; begin if SizingCompositionIsPerformed then begin for Control in ControlEnumerator(TWinControl) do begin WinControl := TWinControl(Control); if WinControl is TPanel then begin TPanel(WinControl).FullRepaint := True; end; UpdateDoubleBuffered(WinControl); SetComposited(WinControl, False); end; end; end; function TMyForm.ControlSupportsDoubleBuffered(Control: TWinControl): Boolean; const NotSupportedClasses: array [0..1] of TControlClass = ( TCustomForm,//general policy is not to double buffer forms TCustomRichEdit//simply fails to draw if double buffered ); var i: Integer; begin for i := low(NotSupportedClasses) to high(NotSupportedClasses) do begin if Control is NotSupportedClasses[i] then begin Result := False; exit; end; end; Result := True; end; procedure TMyForm.UpdateDoubleBuffered(Control: TWinControl); function ControlIsDoubleBuffered: Boolean; const DoubleBufferedClasses: array [0..2] of TControlClass = ( TMyCustomGrid,//flickers when updating TCustomListView,//flickers when updating TCustomStatusBar//drawing infidelities , e.g. my main form status bar during file loading ); var i: Integer; begin if not InRemoteSession then begin //see The Old New Thing, Taxes: Remote Desktop Connection and painting for i := low(DoubleBufferedClasses) to high(DoubleBufferedClasses) do begin if Control is DoubleBufferedClasses[i] then begin Result := True; exit; end; end; end; Result := False; end; var DoubleBuffered: Boolean; begin if ControlSupportsDoubleBuffered(Control) then begin DoubleBuffered := ControlIsDoubleBuffered; end else begin DoubleBuffered := False; end; Control.DoubleBuffered := DoubleBuffered; end; procedure TMyForm.UpdateDoubleBuffered; var Control: TControl; begin for Control in ControlEnumerator(TWinControl) do begin UpdateDoubleBuffered(TWinControl(Control)); end; end;
This won't compile for you, but it should contain some useful ideas. ControlEnumerator
is my utility to turn a recursive walk of the child controls into a flat for
loop. Note that I also use a custom splitter that calls BeginSizing/EndSizing when it is active.
Another useful trick is to use TStaticText
instead of TLabel
which you occasionally need to do when you have deep nesting of page controls and panels.
I've used this code to make my app 100% flicker free but it took me ages and ages of experimenting to get it all in place. Hopefully others can find something of use in here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With