delphi delphi-xe flicker groupbox tpagecontrol

delphi - Los títulos de TLabel y TGroupbox parpadean al cambiar el tamaño



delphi-xe flicker (4)

  • Entonces, tengo una aplicación que carga diferentes complementos y crea una nueva pestaña en un TPageControl para cada uno.
  • Cada DLL tiene un TForm asociado.
  • Los formularios se crean con su padre hWnd como el nuevo TTabSheet.
  • Como las TTabSheets no son un elemento primario del formulario en lo que respecta a VCL ( no quise usar RTL dinámico y complementos hechos en otros idiomas ), tengo que manejar los cambios de tamaño manualmente. Lo hago como a continuación:

    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;

Ahora, mi problema es que cuando se cambia el tamaño de la aplicación, todos los TGroupBoxes y TLabels dentro de los TGroupBoxes parpadean. Los TLabels que no están dentro de TGroupboxes están bien y no parpadean.

Cosas que he intentado:

  • WM_SETREDRAW seguido de una RedrawWindow
  • ParentBackground en TGroupBoxes y TLabels establecido en False
  • DoubleBuffer: = True
  • LockWindowUpdate ( Sí, aunque sé que está muy muy mal )
  • Transparente: = Falso ( incluso anulando crear para editar ControlState )

¿Algunas ideas?


Coloque encima de su formulario ( interfaz ) o póngalo todo en una nueva unidad para incluir:

TLabel = class( stdCtrls.TLabel ) protected procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND; end;

Pon esto en la parte de implementación

procedure TLabel.WMEraseBkgnd(var Message: TWmEraseBkgnd); begin Message.Result:=1; // Fake erase end;

repita este paso para TGroupBox


Esta es la solución que uso con éxito en mi proyecto en varias formas. Está un poco sucio porque usa funciones winapi. En comparación con la respuesta de David, no incluye la penalización de rendimiento. El punto es sobrescribir el manejador de mensajes para el mensaje WM_ERASEBKGND para el formulario y todas sus ventanas secundarias.

typedef LRESULT CALLBACK(*PWndProc)(HWND, UINT, WPARAM, LPARAM); void SetNonFlickeringWndProc(TWinControl &control, std::map<HWND,PWndProc> &list, PWndProc new_proc) { if (control.Handle == 0) { return; } PWndProc oldWndProc = (PWndProc)SetWindowLong(control.Handle, GWL_WNDPROC, (LONG)new_proc); list[control.Handle] = oldWndProc; int count = control.ControlCount; for (int i = 0; i < count; i++) { TControl *child_control = control.Controls[i]; TWinControl *child_wnd_control = dynamic_cast<TWinControl*>(child_control); if (child_wnd_control == NULL) { continue; } SetNonFlickeringWndProc(*child_wnd_control, list, new_proc); } } void RestoreWndProc(std::map<HWND,PWndProc> &old_wnd_proc) { std::map<HWND,PWndProc>::iterator it; for (it = old_wnd_proc.begin(); it != old_wnd_proc.end(); it++) { LONG res = SetWindowLong(it->first, GWL_WNDPROC, (LONG)it->second); } old_wnd_proc.clear(); } std::map<HWND,PWndProc> oldwndproc; // addresses for window procedures for all components in form LRESULT CALLBACK NonFlickeringWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_ERASEBKGND) { return 1; } return ((PWndProc)oldwndproc[hwnd])(hwnd, uMsg, wParam, lParam); } void __fastcall TForm1::FormShow(TObject *Sender) { oldwndproc.clear(); SetNonFlickeringWndProc(*this, oldwndproc, &NonFlickeringWndProc); } void __fastcall TForm1::FormClose(TObject* Sender, TCloseAction& Action) { RestoreWndProc(oldwndproc_etype); }

Nota importante: la propiedad DoubleBufferd for form debe activarse si no desea ver rayas negras en los lados.


Lo único que he encontrado que funciona bien es usar el estilo de ventana WS_EX_COMPOSITED . Este es un cerdo de rendimiento, así que solo lo habilito cuando estoy en un ciclo de tamaño. Según mi experiencia, con los controles integrados, en mi aplicación, el parpadeo solo se produce al cambiar el tamaño de los formularios.

Primero debe realizar una prueba rápida para ver si este enfoque lo ayudará simplemente agregando el estilo de ventana WS_EX_COMPOSITED a todos sus controles con ventana. Si eso funciona, puede considerar el enfoque más avanzado a continuación:

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;

Llámalo, por ejemplo, en OnShow para tu TForm , pasando la instancia del formulario. Si eso ayuda, entonces deberías implementarlo de manera más perspicaz. Te doy los extractos relevantes de mi código para ilustrar cómo lo hice.

Código completo

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;

Esto no compilará para usted, pero debe contener algunas ideas útiles. ControlEnumerator es mi utilidad para convertir una caminata recursiva de los controles secundarios en un bucle plano. Tenga en cuenta que también utilizo un divisor personalizado que llama BeginSizing / EndSizing cuando está activo.

Otro truco útil es usar TStaticText lugar de TLabel que de vez en cuando debes hacer cuando tienes un anidamiento profundo de controles y paneles de página.

Utilicé este código para hacer que mi aplicación fuera 100% libre de parpadeo, pero me llevó siglos y años experimentar para tener todo en su lugar. Con suerte, otros pueden encontrar algo útil aquí.


Use el VCL Fix Pack de Andreas Hausladen .

Además: no especifique el indicador SWP_NOCOPYBITS y establezca DoubleBuffered del PageControl:

uses VCLFixPack; procedure TForm1.FormCreate(Sender: TObject); begin PageControl1.DoubleBuffered := True; //Setup test conditions: FForm2 := TForm2.Create(Self); FForm2.BorderStyle := bsNone; FForm2.BoundsRect := TabSheet1.ClientRect; Windows.SetParent(FForm2.Handle, TabSheet1.Handle); FForm2.Show; PageControl1.Anchors := [akLeft, akTop, akRight, akBottom]; PageControl1.OnResize := PageControl1Resize; end; procedure TForm1.PageControl1Resize(Sender: TObject); begin SetWindowPos(FForm2.Handle, 0, 0, 0, TabSheet1.ClientWidth, TabSheet1.ClientHeight, SWP_NOZORDER + SWP_NOACTIVATE); end;