Delphi 组件未绘制
我有一个组件(TPanel 的后代),我在其中实现了 Transparency 和 BrushStyle(使用 TImage)属性。
当我在表单上有这种类型的一个组件时,一切都可以。 当我在表单上双关更多这种类型的组件时,仅绘制第一个可见组件。 当移动表单并且第一个组件位于其他窗口下或桌面外部时,将绘制下一个组件。
unit TransparentPanel;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, stdctrls;
type
TTransparentPanel = class(TPanel)
private
FTransparent: Boolean;
FBrushStyle: TBrushStyle;
FImage: TImage;
procedure SetTransparent(const Value: Boolean);
procedure SetBrushStyle(const Value: TBrushStyle);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Transparent: Boolean read FTransparent write SetTransparent default
True;
property BrushStyle: TBrushStyle read FBrushStyle write SetBrushStyle default
bsBDiagonal;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('TransparentPanel', [TTransparentPanel]);
end;
constructor TTransparentPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FTransparent := True;
FBrushStyle := bsBDiagonal;
FImage := TImage.Create(Self);
FImage.Align := alClient;
FImage.Parent := Self;
FImage.Transparent := FTransparent;
end;
procedure TTransparentPanel.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if ((not (csDesigning in ComponentState)) and FTransparent) then
Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT;
end;
destructor TTransparentPanel.Destroy;
begin
if Assigned(FImage) then
FreeAndNil(FImage);
inherited Destroy;
end;
procedure TTransparentPanel.Paint;
var
XBitMap,
BitmapBrush: TBitmap;
XOldDC: HDC;
XRect: TRect;
ParentCanvas: TCanvas;
begin
{This panel will be transparent only in Run Time}
if (csDesigning in ComponentState) or (not FTransparent) or (FBrushStyle in [bsClear, bsSolid]) then
inherited Paint
else
begin
XRect := ClientRect;
XOldDC := Canvas.Handle;
XBitMap := TBitmap.Create;
BitmapBrush := TBitmap.Create;
try
XBitMap.Height := Height;
XBitMap.Width := Width;
Canvas.Handle := XBitMap.Canvas.Handle;
inherited Paint;
RedrawWindow(Parent.Handle, @XRect, 0,
RDW_ERASE or RDW_INVALIDATE or
RDW_NOCHILDREN or RDW_UPDATENOW);
BitmapBrush.Width := FImage.Width;
BitmapBrush.Height := FImage.Height;
BitmapBrush.Canvas.Brush.Color := clBlack;
BitmapBrush.Canvas.Brush.Style := FBrushStyle;
SetBkColor(BitmapBrush.Canvas.Handle, clWhite);
BitmapBrush.Canvas.FillRect(BitmapBrush.Canvas.ClipRect);
FImage.Canvas.Draw(0, 0, BitmapBrush);
finally
Canvas.Handle := XOldDC;
Canvas.BrushCopy(XRect, XBitMap, XRect, Color);
XBitMap.Free;
BitmapBrush.Free;
end;
end;
end;
procedure TTransparentPanel.SetBrushStyle(const Value: TBrushStyle);
begin
if (FBrushStyle <> Value) then
begin
FBrushStyle := Value;
Invalidate;
end
end;
procedure TTransparentPanel.SetTransparent(const Value: Boolean);
begin
if (FTransparent <> Value) then
begin
FTransparent := Value;
FImage.Transparent := Value;
Invalidate;
end;
end;
end.
怎么了?
I have component (descendat of TPanel) where I implemented Transparency and BrushStyle (using TImage) properties.
All it's ok when I have one component of this type on the form. Bun when I pun on the form more components of this type only first visible component is painted. When form is moved and first component is under other window or outside desktop next component is painted.
unit TransparentPanel;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, stdctrls;
type
TTransparentPanel = class(TPanel)
private
FTransparent: Boolean;
FBrushStyle: TBrushStyle;
FImage: TImage;
procedure SetTransparent(const Value: Boolean);
procedure SetBrushStyle(const Value: TBrushStyle);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Transparent: Boolean read FTransparent write SetTransparent default
True;
property BrushStyle: TBrushStyle read FBrushStyle write SetBrushStyle default
bsBDiagonal;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('TransparentPanel', [TTransparentPanel]);
end;
constructor TTransparentPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FTransparent := True;
FBrushStyle := bsBDiagonal;
FImage := TImage.Create(Self);
FImage.Align := alClient;
FImage.Parent := Self;
FImage.Transparent := FTransparent;
end;
procedure TTransparentPanel.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if ((not (csDesigning in ComponentState)) and FTransparent) then
Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT;
end;
destructor TTransparentPanel.Destroy;
begin
if Assigned(FImage) then
FreeAndNil(FImage);
inherited Destroy;
end;
procedure TTransparentPanel.Paint;
var
XBitMap,
BitmapBrush: TBitmap;
XOldDC: HDC;
XRect: TRect;
ParentCanvas: TCanvas;
begin
{This panel will be transparent only in Run Time}
if (csDesigning in ComponentState) or (not FTransparent) or (FBrushStyle in [bsClear, bsSolid]) then
inherited Paint
else
begin
XRect := ClientRect;
XOldDC := Canvas.Handle;
XBitMap := TBitmap.Create;
BitmapBrush := TBitmap.Create;
try
XBitMap.Height := Height;
XBitMap.Width := Width;
Canvas.Handle := XBitMap.Canvas.Handle;
inherited Paint;
RedrawWindow(Parent.Handle, @XRect, 0,
RDW_ERASE or RDW_INVALIDATE or
RDW_NOCHILDREN or RDW_UPDATENOW);
BitmapBrush.Width := FImage.Width;
BitmapBrush.Height := FImage.Height;
BitmapBrush.Canvas.Brush.Color := clBlack;
BitmapBrush.Canvas.Brush.Style := FBrushStyle;
SetBkColor(BitmapBrush.Canvas.Handle, clWhite);
BitmapBrush.Canvas.FillRect(BitmapBrush.Canvas.ClipRect);
FImage.Canvas.Draw(0, 0, BitmapBrush);
finally
Canvas.Handle := XOldDC;
Canvas.BrushCopy(XRect, XBitMap, XRect, Color);
XBitMap.Free;
BitmapBrush.Free;
end;
end;
end;
procedure TTransparentPanel.SetBrushStyle(const Value: TBrushStyle);
begin
if (FBrushStyle <> Value) then
begin
FBrushStyle := Value;
Invalidate;
end
end;
procedure TTransparentPanel.SetTransparent(const Value: Boolean);
begin
if (FTransparent <> Value) then
begin
FTransparent := Value;
FImage.Transparent := Value;
Invalidate;
end;
end;
end.
What is wrong?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
好的,一些提示:
仅绘制一个组件,因为在绘制过程中控件的工作区再次失效,因此您创建了无限的 WM_PAINT 消息流,而第二个组件永远不会被绘制。 直到第一个变得不可见,正如您所描述的。 您可以从 CPU 负载中看到这一点,表单上的某个组件使用了我系统上一个核心的 100%(Delphi 2007,在运行时创建的组件)。
您应该尝试删除您绘制的位图,并改用 DoubleBuffered 属性。
您
FImage 实际用途是什么?
如果根据Transparent属性的值修改创建参数,则在属性更改时需要重新创建窗口句柄。
也许你可以完全摆脱该组件,并使用 TPaintBox 代替? 只要你不自己画背景,它就是透明的。 但我无法从你的代码中看出你真正想要实现什么,所以很难说。
OK, a few tips:
Only one component is drawn, because during painting the client area of the control is invalidated again, so you create an infinite stream of WM_PAINT messages, and the second component never gets drawn. Until the first one is made invisible, as you describe. You can see this from the CPU load, having one of your components on a form uses 100% of one core on my system (Delphi 2007, component created at runtime).
You should try to remove the bitmap you draw into, and make use of the DoubleBuffered property instead.
What is FImage actually used for?
If you modify the create parameters depending on the value of the Transparent property, then you need to recreate the window handle when the property changes.
Maybe you can get rid of the component completely, and use a TPaintBox instead? It is transparent as long as you don't paint the background yourself. But I can't tell from your code what you actually want to achieve, so it's hard to say.
我认为您想要一个可以包含其他控件的控件 - 就像
TPanel
可以做的那样 - 以及一个可以显示其下面的窗口内容的控件 - 就像TImage
可以做的那样它的Transparent
属性已设置。 您似乎有一种错误的印象,即如果将一个控件放在另一个控件之上,您将获得两者的行为组合。 这就是问题。您应该做的第一件事是摆脱
TImage
控件。 这只会让事情变得比需要的更加复杂。 当需要在面板上绘制画笔图案时,直接将其绘制到面板上。接下来,认识到
ws_ex_Transparent
窗口样式控制是否首先绘制窗口的兄弟。 这并没有说明窗口的父窗口是否会被重新绘制。 如果您的面板的父级设置了ws_ClipChildren
样式,那么它不会将自己绘制在您的面板所在的位置下方。 如果面板控件的父级设置了ws_ex_Composited
样式,看起来会对您有所帮助,但作为组件编写者,您无法控制控件的父级。TImage
能够显示透明,因为它不是窗口控件。 它没有窗口句柄,因此操作系统有关绘画和剪切的规则不适用于它。 从 Windows 的角度来看,TImage
根本不存在。 我们在 Delphi 世界中所感知的TImage
绘画本身实际上是父窗口,它遵循一个单独的子例程来绘制父窗口的某个区域。 因此,TImage
绘画代码根本无法在父级的某些区域上进行绘画。如果我这样做,我会问自己带有画笔图案的控件是否真的需要成为容器控件。 我可以只使用普通的
TImage
并在其上绘制重复的画笔图案吗? 其他控件仍然可以位于其之上,但它们不会被视为模式控件的子控件。I think you want a control that can contain other controls — like
TPanel
can do — and a control that can display the contents of the window underneath it — likeTImage
can do when itsTransparent
property is set. It appears you are under the mistaken impression that if you put one control on top of another, you'll get the behavior of both combined. That's what's wrong.First thing you should do is get rid of the
TImage
control. That's just making things more complicated than they need to be. When you need to draw a brush pattern on the panel, draw it directly onto the panel.Next, realize that the
ws_ex_Transparent
window style controls whether siblings of the window are painted first. That says nothing about whether the parent of the window gets repainted. If the parent of your panel has thews_ClipChildren
style set, then it will not paint itself underneath where your panel supposedly is. It looks like it would help you if the parent of your panel control had thews_ex_Composited
style set, but as a component writer, you don't get control over your controls' parents.TImage
is able to appear transparent because it is not a windowed control. It has no window handle, so the OS rules about painting and clipping don't apply to it. From Windows' point of view,TImage
doesn't exist at all. What we in the Delphi world perceive as theTImage
painting itself is really the parent window deferring to a separate subroutine to paint a certain region of the parent window. Because of that, theTImage
painting code can simply not paint over some of the parent's area.If I were doing this, I'd ask myself whether the control with the brush pattern really needed to be a container control. Could I instead just use an ordinary
TImage
with a repeating brush pattern drawn on it? Other controls can still go on top of it, but they won't be considered children of the pattern control.尝试看看 Graphics32 库:它非常擅长绘制东西并且工作很棒< /em> 带位图和透明度
Try to look at the Graphics32 library : it's very good at drawing things and works great with Bitmaps and Transparency
如果您希望面板是透明的,您需要做的就是覆盖 Paint 并且不执行任何操作(或者例如绘制透明图像),并且还捕获 WM_ERASEBKGND 消息并且在这里也不执行任何操作。 这可以确保面板根本不会自行喷漆。
还要确保从 ControlStyle 中排除 csOpaque 标志,以便父级知道它应该在面板下方绘制自己。
顺便说一下,Paint 中的东西绝对是可怕的(我指的是 RedrawWindow 的东西)。 摆脱它。 WS_EX_TRANSPARENT 仅适用于顶层窗口,不适用于控件。
If you want the panel to be transparent, all you need to do is override Paint and do nothing (or paint a transparent image, for example), and also catch the WM_ERASEBKGND message and do nothing here as well. This ensures the panel doesn't paint itself at all.
Make sure also to exclude the csOpaque flag from ControlStyle, so the parent knows it should paint itself underneath the panel.
The stuff you have in Paint is absolutely horrible, by the way (I mean the RedrawWindow thing). Get rid of it. And WS_EX_TRANSPARENT is meant for toplevel windows only, not for controls.