首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在创建/还原表单时,重叠的TCustomControl对象会按顺序绘制

在创建/还原表单时,重叠的TCustomControl对象会按顺序绘制
EN

Stack Overflow用户
提问于 2017-09-01 18:38:43
回答 1查看 1.1K关注 0票数 8

在Delphi2007中,我很难获得透明的TCustomControl。我目前已经将问题简化为下面的代码。问题是,在最初创建窗体时,控件以相反的顺序绘制,然后将其添加到窗体中。当调整窗体的大小时,它们将按照正确的顺序绘制。我做错了什么?排除第三方解决方案是否有更合适的路径可走?

下面是我在Delphi 2007中演示这个问题的示例项目。

代码语言:javascript
运行
复制
unit Main;

interface

uses
  Forms, Classes, Controls, StdCtrls, Messages,
  ExtCtrls;

type
  // Example of a TWinControl derived control
  TMyCustomControl = class(TCustomControl)
  protected
    procedure CreateParams(var params: TCreateParams); override;
    procedure WMEraseBkGnd(var msg: TWMEraseBkGnd);
      message WM_ERASEBKGND;
    procedure Paint; override;
  end;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormPaint(Sender: TObject);
  private
    YellowBox: TMyCustomControl;
    GreenBox: TMyCustomControl;
  end;

var
  Form1: TForm1;

implementation

uses
  Windows, Graphics;

{$R *.dfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  self.OnPaint := FormPaint;

  GreenBox := TMyCustomControl.Create(self);
  GreenBox.Parent := self;
  GreenBox.SetBounds(10,10,200,200);
  GreenBox.color := clGreen;

  YellowBox := TMyCustomControl.Create(self);
  YellowBox.Parent := self;
  YellowBox.SetBounds(100,100,200,200);
  YellowBox.color := clYellow;

end;

// Paint bars on form background
procedure TForm1.FormPaint(Sender: TObject);
var
  Idx: Integer;
begin
  for Idx := 0 to ClientHeight div 8 do
  begin
    if Odd(Idx) then
      Canvas.Brush.Color := clWhite
    else
      Canvas.Brush.Color := clSilver;  // pale yellow
    Canvas.FillRect(Rect(0, Idx * 8, ClientWidth, Idx * 8 + 8));
  end;
end;

{ TMyCustomControl }

procedure TMyCustomControl.CreateParams(var params: TCreateParams);
begin
  inherited;
  params.ExStyle := params.ExStyle or WS_EX_TRANSPARENT;
end;

procedure TMyCustomControl.WMEraseBkGnd(var msg: TWMEraseBkGnd);
begin
  SetBkMode (msg.DC, TRANSPARENT);
  msg.result := 1;
end;

procedure TMyCustomControl.Paint;
begin
  Canvas.Brush.Color := color;
  Canvas.RoundRect(0,0,width,height,50,50);
end;



end.
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-09-05 02:12:23

什么是错误的是你期望的顺序画你的控制。接收WM_PAINT消息的控件的顺序被记录为实际上是完全相反的顺序,最高的控件首先接收消息。稍后会有更多关于文档的介绍,因为有WS_EX_TRANSPARENT样式的兄弟姐妹会让我们处于没有文档记录的领域。正如您已经注意到的,在这种情况下,接收WM_PAINT消息的控件的顺序不是确定性的--当调整窗口大小时,顺序会发生变化。

我修改了一下你的复制箱看看发生了什么。这些修改包括两个面板和一个在接收WM_PAINT时的调试输出。

代码语言:javascript
运行
复制
unit Unit1;

interface

uses
  Forms, Classes, Controls, StdCtrls, Messages, ExtCtrls;

type
  TMyCustomControl = class(TCustomControl)
  protected
    procedure CreateParams(var params: TCreateParams); override;
    procedure WMEraseBkGnd(var msg: TWMEraseBkGnd);
      message WM_ERASEBKGND;
    procedure Paint; override;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  end;

  TPanel = class(extctrls.TPanel)
  protected
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  end;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormPaint(Sender: TObject);
  private
    YellowBox: TMyCustomControl;
    GreenBox: TMyCustomControl;
    Panel1, Panel2: TPanel;
  end;

var
  Form1: TForm1;

implementation

uses
  sysutils, windows, graphics;

{$R *.dfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  Width := 590;
  Height := 270;
  OnPaint := FormPaint;

  GreenBox := TMyCustomControl.Create(self);
  GreenBox.Parent := self;
  GreenBox.SetBounds(20, 20, 140, 140);
  GreenBox.color := clGreen;
  GreenBox.Name := 'GreenBox';
//{
  Panel1 := TPanel.Create(Self);
  Panel1.Parent := Self;
  Panel1.SetBounds(240, 40, 140, 140);
  Panel1.ParentBackground := False;
  Panel1.Color := clMoneyGreen;
  Panel1.Name := 'Panel1';

  Panel2 := TPanel.Create(Self);
  Panel2.Parent := Self;
  Panel2.SetBounds(260, 60, 140, 140);
  Panel2.ParentBackground := False;
  Panel2.Color := clCream;
  Panel2.Name := 'Panel2';
//}
  YellowBox := TMyCustomControl.Create(self);
  YellowBox.Parent := self;
  YellowBox.SetBounds(80, 80, 140, 140);
  YellowBox.color := clYellow;
  YellowBox.Name := 'YellowBox';
  YellowBox.BringToFront;
end;

// Paint bars on form background
procedure TForm1.FormPaint(Sender: TObject);
var
  Idx: Integer;
begin
  for Idx := 0 to ClientHeight div 8 do
  begin
    if Odd(Idx) then
      Canvas.Brush.Color := clWhite
    else
      Canvas.Brush.Color := clSilver;  // pale yellow
    Canvas.FillRect(Rect(0, Idx * 8, ClientWidth, Idx * 8 + 8));
  end;
end;

{ TPanel }

procedure TPanel.WMPaint(var Message: TWMPaint);
begin
  OutputDebugString(PChar(Format(' %s painting..', [Name])));
  inherited;
end;

{ TMyCustomControl }

procedure TMyCustomControl.CreateParams(var params: TCreateParams);
begin
  inherited;
  params.ExStyle := params.ExStyle or WS_EX_TRANSPARENT;
end;

procedure TMyCustomControl.WMEraseBkGnd(var msg: TWMEraseBkGnd);
begin
  msg.Result := 1;
end;

procedure TMyCustomControl.WMPaint(var Message: TWMPaint);
begin
  OutputDebugString(PChar(Format(' %s painting..', [Name])));
  inherited;
end;

procedure TMyCustomControl.Paint;
begin
  Canvas.Brush.Color := Color;
  Canvas.RoundRect(0, 0, Width, Height, 50, 50);
end;

end.

它产生了以下表格:

根据创造的顺序,z顺序是,从下到上,

  1. GreenBox,
  2. Panel1,
  3. Panel2,
  4. YellowBox。

WM_PAINT消息的调试输出如下:

调试输出: Panel2绘图。ProcessProject1.exe (12548)调试输出: Panel1绘图。ProcessProject1.exe (12548)调试输出: YellowBox绘图。ProcessProject1.exe (12548)调试输出: GreenBox绘图。进程项目1.exe (12548)

在这个顺序中有两件事值得注意。

First,Panel2在Panel1之前接收画图消息,尽管Panel2按z顺序更高.

那么,当我们看到Panel2作为一个整体,但我们看到的只是Panel1的一部分,尽管它是稍后绘制的,这是怎么回事?这就是更新区域发挥作用的地方。控件中的WS_CLIPSIBLINGS样式标志告诉操作系统,z阶较高的同级控件所占用的控件的一部分将不会被绘制。

Clips子窗口之间的相对关系;也就是说,当特定的子窗口接收到WM_PAINT消息时,样式将所有其他重叠的子窗口剪辑出要更新的子窗口区域。

让我们深入了解一下WM_PAINT的Panel1处理程序,看看OS的更新区域是什么样的。

代码语言:javascript
运行
复制
{ TPanel }

// not declared in D2007
function GetRandomRgn(hdc: HDC; hrgn: HRGN; iNum: Integer): Integer; stdcall;
    external gdi32;
const
  SYSRGN = 4;

procedure TPanel.WMPaint(var Message: TWMPaint);
var
  PS: TPaintStruct;
  Rgn: HRGN;

  TestDC: HDC;
begin
  OutputDebugString(PChar(Format(' %s painting..', [Name])));

  Message.DC := BeginPaint(Handle, PS);
  Rgn := CreateRectRgn(0, 0, 0, 0);
  if (Name = 'Panel1') and (GetRandomRgn(Message.DC, Rgn, SYSRGN) = 1) then begin
    OffsetRgn(Rgn, - Form1.ClientOrigin.X + Width + 40, - Form1.ClientOrigin.Y);
    TestDC := GetDC(Form1.Handle);
    SelectObject(TestDC, GetStockObject(BLACK_BRUSH));
    PaintRgn(TestDC, Rgn);
    ReleaseDC(Form1.Handle, TestDC);
    DeleteObject(Rgn);
  end;
  inherited;
  EndPaint(Handle, PS);
end;

BeginPaint将用系统更新区域剪辑更新区域,然后可以使用GetRandomRgn检索更新区域。我已经将剪裁后的更新区域转储到表单的右侧。不要介意Form1引用或缺少错误检查,我们只是在调试。无论如何,这将生成以下表单:

因此,无论您在Panel1的客户区域中绘制什么,它都会被裁剪成黑色,因此它不能在视觉上出现在Panel2前面。

第二个,记住绿色框是先创建的,然后是面板,最后是黄色。那么,为什么这两个透明的控件是在两个面板之后涂上的呢?

首先,记住控件是自上而下绘制的。现在,一个透明的控制怎么可能画在后面画的东西上呢?显然,这是不可能的。所以整个绘画算法必须改变。没有关于这方面的文档,我发现最好的解释是来自博客条目 of Raymond:

..。WS_EX_TRANSPARENT扩展窗口样式更改绘制算法如下:如果需要绘制WS_EX_TRANSPARENT窗口,并且有任何非WS_EX_TRANSPARENT窗口兄弟姐妹(属于同一进程)也需要绘制,那么窗口管理器将首先绘制非WS_EX_TRANSPARENT窗口。

当你有透明的控制时,从上到下的绘画顺序使它变得很困难。还有重叠的透明控制--哪一种比另一种更透明?只要接受这样一个事实,即重叠的透明控件会产生未确定的行为。

如果研究上述测试用例中透明框的系统更新区域,就会发现两者都是正方形。

让我们把面板移到盒子中间。

代码语言:javascript
运行
复制
procedure TForm1.FormCreate(Sender: TObject);
begin
  Width := 590;
  Height := 270;
  OnPaint := FormPaint;

  GreenBox := TMyCustomControl.Create(self);
  GreenBox.Parent := self;
  GreenBox.SetBounds(20, 20, 140, 140);
  GreenBox.color := clGreen;
  GreenBox.Name := 'GreenBox';
//{
  Panel1 := TPanel.Create(Self);
  Panel1.Parent := Self;
  Panel1.SetBounds(40, 40, 140, 140);
  Panel1.ParentBackground := False;
  Panel1.Color := clMoneyGreen;
  Panel1.Name := 'Panel1';

  Panel2 := TPanel.Create(Self);
  Panel2.Parent := Self;
  Panel2.SetBounds(60, 60, 140, 140);
  Panel2.ParentBackground := False;
  Panel2.Color := clCream;
  Panel2.Name := 'Panel2';
//}
  YellowBox := TMyCustomControl.Create(self);
  YellowBox.Parent := self;
  YellowBox.SetBounds(80, 80, 140, 140);
  YellowBox.color := clYellow;
  YellowBox.Name := 'YellowBox';
  YellowBox.BringToFront;
end;

 ...

procedure TMyCustomControl.WMPaint(var Message: TWMPaint);
var
  PS: TPaintStruct;
  Rgn: HRGN;

  TestDC: HDC;
begin
  OutputDebugString(PChar(Format(' %s painting..', [Name])));

  Message.DC := BeginPaint(Handle, PS);
  Rgn := CreateRectRgn(0, 0, 0, 0);
  if (Name = 'GreenBox') and (GetRandomRgn(Message.DC, Rgn, SYSRGN) = 1) then begin
    OffsetRgn(Rgn, - Form1.ClientOrigin.X + Width + 260, - Form1.ClientOrigin.Y);
    TestDC := GetDC(Form1.Handle);
    SelectObject(TestDC, GetStockObject(BLACK_BRUSH));
    PaintRgn(TestDC, Rgn);
    ReleaseDC(Form1.Handle, TestDC);
    DeleteObject(Rgn);
  end;
  inherited;
  EndPaint(Handle, PS);
end;

最右边的黑色形状是GreenBox的系统更新区域。毕竟,系统可以将裁剪应用于透明控件。我认为,只要有一堆透明的控件,就可以得出这样的结论:绘制算法并不完美。

正如承诺的那样,文档引用了WM_PAINT订单。其中一个原因是它包含了一个可能的解决方案(当然,我们已经找到了一个解决方案,在您的透明控件之间散布一些不透明的控件):

..。如果组合了父链中的窗口(带有WX_EX_COMPOSITED的窗口),则兄弟窗口以Z顺序的相反顺序接收WM_PAINT消息。这样,Z顺序中最高的窗口(顶部)接收到它的WM_PAINT消息,反之亦然。如果父链中的窗口未组合,兄弟窗口将按Z顺序接收WM_PAINT消息。

就像我测试的那样,在父窗体上设置WS_EX_COMPOSITED似乎是可行的。但我不知道它是否适用于你的情况。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46006230

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档