delphidelphi-xeflickergroupboxtpagecontrol

TLabel and TGroupbox Captions Flicker on Resize


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?


Solution

  • 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.