J'ai pensé que je pouvais juste jeter un coup d'œil et demander : j'ai vu des contrôles Delphi qui sont impeccables en termes d'effets graphiques. C'est-à-dire : pas de scintillement, mises à jour par section (ne redessiner que la section d'un contrôle qui est marquée comme sale) et défilement fluide.
J'ai codé de nombreux contrôles graphiques au fil des ans, je connais donc le double buffering, les dibs, les bitblts et tous les trucs "communs" (j'utilise toujours les dibs pour tout dessiner si possible, mais il y a une surcharge). Je connais également InvalidateRect et je vérifie TCanvas.ClipRect pour connaître le rect réel qui doit être mis à jour. Malgré toutes ces solutions typiques, je trouve qu'il est très difficile de créer des composants de la même qualité que, par exemple, Developer Express ou Razed Components. Si les graphiques sont lisses, vous pouvez parier que les barres de défilement (natives) scintillent, et si les barres de défilement et le cadre sont lisses, vous pouvez jurer que le fond scintille pendant le défilement.
Existe-t-il une configuration standard de code pour gérer cela ? Une sorte de meilleures pratiques garantissant des redessinages fluides de l'ensemble du contrôle - y compris la zone non cliente d'un contrôle ?
Par exemple, voici un contrôle "bare bone" qui prend de la hauteur pour des mises à jour segmentées (ne redessine que ce qui est nécessaire). Si vous le créez sur un formulaire, essayez de déplacer une fenêtre dessus, et regardez-le remplacer les parties par des couleurs (voir la méthode de peinture).
Est-ce que quelqu'un a une classe de base similaire qui peut gérer les redessinages de la zone non client sans scintillement ?
type
TMyControl = Class(TCustomControl)
private
(* TWinControl: Erase background prior to client-area paint *)
procedure WMEraseBkgnd(var Message: TWmEraseBkgnd);message WM_ERASEBKGND;
Protected
(* TCustomControl: Overrides client-area paint mechanism *)
Procedure Paint;Override;
(* TWinControl: Adjust Win32 parameters for CreateWindow *)
procedure CreateParams(var Params: TCreateParams);override;
public
Constructor Create(AOwner:TComponent);override;
End;
{ TMyControl }
Constructor TMyControl.Create(AOwner:TComponent);
Begin
inherited Create(Aowner);
ControlStyle:=ControlStyle - [csOpaque];
end;
procedure TMyControl.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
(* When a window has this style set, any areas that its
child windows occupy are excluded from the update region. *)
params.ExStyle:=params.ExStyle + WS_CLIPCHILDREN;
(* Exclude VREDRAW & HREDRAW *)
with Params.WindowClass do
Begin
(* When a window class has either of these two styles set,
the window contents will be completely redrawn every time it is
resized either vertically or horizontally (or both) *)
style:=style - CS_VREDRAW;
style:=style - CS_HREDRAW;
end;
end;
procedure TMyControl.Paint;
(* Inline proc: check if a rectangle is "empty" *)
function isEmptyRect(const aRect:TRect):Boolean;
Begin
result:=(arect.Right=aRect.Left) and (aRect.Bottom=aRect.Top);
end;
(* Inline proc: Compare two rectangles *)
function isSameRect(const aFirstRect:TRect;const aSecondRect:TRect):Boolean;
Begin
result:=sysutils.CompareMem(@aFirstRect,@aSecondRect,SizeOf(TRect))
end;
(* Inline proc: This fills the background completely *)
Procedure FullRepaint;
var
mRect:TRect;
Begin
mRect:=getClientRect;
AdjustClientRect(mRect);
Canvas.Brush.Color:=clWhite;
Canvas.Brush.Style:=bsSolid;
Canvas.FillRect(mRect);
end;
begin
(* A full redraw is only issed if:
1. the cliprect is empty
2. the cliprect = clientrect *)
if isEmptyRect(Canvas.ClipRect)
or isSameRect(Canvas.ClipRect,Clientrect) then
FullRepaint else
Begin
(* Randomize a color *)
Randomize;
Canvas.Brush.Color:=RGB(random(255),random(255),random(255));
(* fill "dirty rectangle" *)
Canvas.Brush.Style:=bsSolid;
Canvas.FillRect(canvas.ClipRect);
end;
end;
procedure TMyControl.WMEraseBkgnd(var Message: TWmEraseBkgnd);
begin
message.Result:=-1;
end;
Mise à jour de
Je voulais juste ajouter que ce qui a marché, c'est une combinaison de :
-
ExcludeClipRect() lors du dessin de la zone non cliente, afin d'éviter tout chevauchement avec les graphiques de la zone cliente.
-
Capturer le message WMNCCalcSize plutôt que d'utiliser simplement la taille des bordures pour les mesures. J'ai également dû prendre la hauteur pour les tailles des bordures :
XEdge := GetSystemMetrics(SM_CXEDGE); YEdge := GetSystemMetrics(SM_CYEDGE);
-
Appeler RedrawWindow() avec les drapeaux suivants chaque fois que vous avez des barres de défilement qui se sont déplacées ou un redimensionnement :
mRect:=ClientRect; mFlags:=rdw_Invalidate or RDW_NOERASE or RDW_FRAME or RDW_INTERNALPAINT or RDW_NOCHILDREN; RedrawWindow(windowhandle,@mRect,0,mFlags);
-
Lorsque vous mettez à jour l'arrière-plan pendant la méthode Paint(), évitez de dessiner sur d'éventuels objets enfants, comme ceci (voir le RDW_NOCHILDREN mentionné ci-dessus) :
for x := 1 to ControlCount do begin mCtrl:=Controls[x-1]; if mCtrl.Visible then Begin mRect:=mCtrl.BoundsRect; ExcludeClipRect(Canvas.Handle, mRect.Left,mRect.Top, mRect.Right,mRect.Bottom); end; end;
Merci pour votre aide !