Компонент Delphi не окрашен

У меня есть компонент (потомок TPanel), где я реализовал свойства прозрачности и 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.

Что случилось?

4 ответа

ОК, несколько советов:

  • Рисуется только один компонент, потому что во время рисования клиентская область элемента управления снова становится недействительной, поэтому вы создаете бесконечный поток сообщений WM_PAINT, а второй компонент никогда не рисуется. Пока первый не станет невидимым, как вы описываете. Это видно по загрузке ЦП, когда один из ваших компонентов в форме использует 100% одного ядра в моей системе (Delphi 2007, компонент, созданный во время выполнения).

  • Вам следует попытаться удалить растровое изображение, в которое вы рисуете, и использовать вместо этого свойство DoubleBuffered.

  • Для чего на самом деле используется FImage?

  • Если вы изменяете параметры создания в зависимости от значения свойства Transparent, вам необходимо заново создать дескриптор окна при изменении свойства.

  • Может быть, вы можете полностью избавиться от компонента и использовать вместо него TPaintBox? Он прозрачен, если вы сами не рисуете фон. Но я не могу сказать из вашего кода, чего вы на самом деле хотите достичь, поэтому сложно сказать.

Я думаю, что вы хотите элемент управления, который может содержать другие элементы управления, такие как TPanel может сделать - и элемент управления, который может отображать содержимое окна под ним - как TImage может сделать, когда его Transparent свойство установлено. Похоже, у вас сложилось ошибочное впечатление, что если вы поместите один элемент управления поверх другого, вы получите поведение обоих вместе. Вот что не так.

Первое, что вы должны сделать, это избавиться от TImage контроль. Это только делает вещи более сложными, чем они должны быть. Когда вам нужно нарисовать рисунок кисти на панели, нарисуйте его прямо на панели.

Затем осознайте, что ws_ex_Transparent Стиль окна определяет, будут ли окрашены первые элементы окна. Это ничего не говорит о том, перерисовывается ли родитель окна. Если родитель вашей панели имеет ws_ClipChildren набор стилей, тогда он не будет краситься под тем местом, где ваша панель предположительно находится. Похоже, это поможет вам, если бы родитель вашего элемента управления панели имел ws_ex_Composited набор стилей, но как автор компонентов, вы не получаете контроль над родителями элементов управления.

TImage может выглядеть прозрачным, потому что это не оконный элемент управления. У него нет дескриптора окна, поэтому правила ОС для рисования и отсечения не применяются к нему. С точки зрения Windows, TImage не существует вообще То, что мы в мире Delphi воспринимаем как TImage На самом деле рисование - это родительское окно, которое откладывается до отдельной подпрограммы для рисования определенной области родительского окна. Из-за этого TImage код рисования может просто не закрасить часть родительской области.

Если бы я делал это, я бы спросил себя, действительно ли элемент управления с шаблоном кисти должен быть контейнерным элементом управления. Могу ли я вместо этого просто использовать обычный TImage с повторяющимся рисунком кисти? Другие элементы управления все еще могут быть поверх него, но они не будут считаться дочерними элементами элемента управления шаблоном.

Попробуйте взглянуть на библиотеку Graphics32: она очень хорошо рисует и прекрасно работает с растровыми изображениями и прозрачностью

Если вы хотите, чтобы панель была прозрачной, все, что вам нужно сделать, это переопределить Paint и ничего не делать (или нарисовать прозрачное изображение, например), а также перехватить сообщение WM_ERASEBKGND и здесь также ничего не делать. Это гарантирует, что панель не красит себя вообще.

Не забудьте также исключить флаг csOpaque из ControlStyle, чтобы родитель знал, что он должен нарисовать себя под панелью.

Кстати, то, что у вас есть в Paint, абсолютно ужасно (я имею в виду RedrawWindow). Избавиться от этого. И WS_EX_TRANSPARENT предназначен только для окон верхнего уровня, а не для элементов управления.

Другие вопросы по тегам