【发布时间】:2011-09-15 21:02:53
【问题描述】:
我想我可以把它扔在那里然后问:我已经看到在图形效果方面完美无缺的 Delphi 控件。含义:无闪烁、分段更新(仅重绘控件中标记为脏的部分)和平滑滚动。
这些年来我编写了很多图形控件,所以我知道双缓冲、dib、bitblts 和所有“常见”的东西(如果可能,我总是使用 dib 来绘制所有内容,但有开销)。还了解 InvalidateRect 并检查 TCanvas.ClipRect 以获取需要更新的实际矩形。尽管有所有这些典型的解决方案,但我发现创建与开发人员 Express 或 Razed 组件相同质量的组件非常困难。如果图形是平滑的,你可以打赌滚动条(本机)闪烁,如果滚动条和框架是平滑的,你可以发誓在滚动过程中背景闪烁。
是否有标准的代码设置来处理这个问题?一种确保整个控件(包括控件的非客户区)顺利重绘的最佳实践?
例如,这里有一个“裸骨”控件,它采用高度进行分段更新(仅重绘需要的部分)。如果您在表单上创建它,请尝试在其上移动一个窗口,并观察它用颜色替换部分(参见绘制方法)。
是否有类似的基类可以处理非客户区重绘而不闪烁?
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;
更新
我只是想补充一点,诀窍是结合了以下几点:
- 绘制非客户区时ExcludeClipRect(),这样就不会与客户区的图形重叠
-
捕获 WMNCCalcSize 消息,而不是仅使用边框大小进行测量。我还必须为边缘尺寸取高度:
XEdge := GetSystemMetrics(SM_CXEDGE); YEdge := GetSystemMetrics(SM_CYEDGE); -
只要滚动条移动或调整大小,就使用以下标志调用 RedrawWindow():
mRect:=ClientRect; mFlags:=rdw_Invalidate or RDW_NOERASE or RDW_FRAME or RDW_INTERNALPAINT or RDW_NOCHILDREN; RedrawWindow(windowhandle,@mRect,0,mFlags); -
在 Paint() 方法期间更新背景时,请避免绘制可能的子对象,如下所示(参见上面提到的 RDW_NOCHILDREN):
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;
感谢大家的帮助!
【问题讨论】:
-
我也写了很多可视化控件。在许多视觉效果有限的情况下,它可以完美地与剪辑矩形的巧妙使用配合使用,例如我的breadcrumb bar control。然而,在其他一些视觉反馈和动画很重要的情况下,我依赖双缓冲,这对我来说总是完美无缺。您可能想要手动响应
WM_ERASEBKGND消息,甚至可能想要手动处理双缓冲,方法是绘制到TBitmap,然后在适合您时绘制到BitBlt。 -
然而,没有适用于所有场景的“神奇”论坛,所以我怀疑这个问题会有很多有用的答案。
-
试试privat.rejbrand.se/asbutton.exe 来证明非常简单的 GDI 和双缓冲可以产生非常好的结果。
-
我想提一下,如果用户在远程桌面(即终端会话、远程会话)中运行,您不应该执行 双缓冲*/*blits,因为真的会减慢绘图速度。您想要在屏幕上绘制原始操作;发送 GDI 绘图命令比通过宽带发送位图要快得多。如果你真的关心你的用户,你也会禁用背景图片、渐变和动画。
-
我认为您的意思是“Raize”组件,而不是“Razed”。