Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TLabel and TGroupbox Captions Flicker on Resize

Tags:

  • So, I have an application that loads different plugins and creates a new tab on a TPageControl for each one.
  • Each DLL has a TForm associated with it.
  • The forms are created with their parent hWnd as the new TTabSheet.
  • 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:

  • WM_SETREDRAW followed by a RedrawWindow
  • ParentBackground on the TGroupBoxes and TLabels set to False
  • DoubleBuffer := True
  • LockWindowUpdate (Yes, even though I know it's very very wrong)
  • Transparent := False (even overriding create to edit ControlState)

Any ideas?

like image 215
ThievingSix Avatar asked Nov 08 '11 23:11

ThievingSix


1 Answers

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.

like image 167
David Heffernan Avatar answered Oct 17 '22 16:10

David Heffernan