Пользовательский контроль с не клиентской областью - сначала не рассчитывается

Я пишу пользовательский элемент управления, который является просто контейнером с не клиентской областью. В этой не клиентской области есть одна небольшая область, которая является кнопкой, а остальная часть прозрачна. Рисунок не является точным прямоугольником.

Пока у меня это наполовину работает. Проблема в том, что он не рассчитывает площадь, не относящуюся к клиенту, если я не сделаю небольшую настройку, такую ​​как изменение размера.

Я следовал за многими ресурсами, описывающими, как достигнуть этого. Моя реализация обработки WM_NCCALCSIZE более или менее идентичен "рабочим" примерам, которые я нашел. Но когда элемент управления создается впервые, он вообще не рассчитывается. Когда я помещаю точку останова в мой обработчик сообщений (WMNCCalcSize), основываясь на найденных примерах, я должен сначала проверить Msg.CalcValidRectsи только сделайте мой расчет, если это True, Но при отладке во время выполнения, это False, таким образом, расчет не сделан.

Во время разработки, если я изменяю размер элемента управления, ТОГДА он решает рассчитать правильно. Он все еще не совершенен (этот код все еще находится в разработке), но, похоже, он не устанавливает область без клиента, пока я не настрою его. Кроме того, во время выполнения, если я изменяю размер в коде, он все равно не рассчитывается.

До и после изменения размера

Изображение вверху - это когда форма изначально создается / показывается. Второй после того, как я немного изменил размер. Обратите внимание на кнопку теста, которая выровнена alLeft, Таким образом, изначально он занимает область, которая должна быть не клиентской.

Если я закомментирую чек if Msg.CalcValidRects then begin, то он рассчитывает правильно. Но я вижу, что каждый пример выполняет эту проверку, и я почти уверен, что это необходимо.

Что я делаю не так и как заставить его постоянно вычислять не клиентскую область?

unit FloatBar;

interface

uses
  System.Classes, System.SysUtils, System.Types,
  Vcl.Controls, Vcl.Graphics, Vcl.Forms,
  Winapi.Windows, Winapi.Messages;

type
  TFloatBar = class(TCustomControl)
  private
    FCollapsed: Boolean;
    FBtnHeight: Integer;
    FBtnWidth: Integer;
    procedure RepaintBorder;
    procedure PaintBorder;
    procedure SetCollapsed(const Value: Boolean);
    function BtnRect: TRect;
    procedure SetBtnHeight(const Value: Integer);
    procedure SetBtnWidth(const Value: Integer);
    function TransRect: TRect;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
    procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
    procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT;
    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
    procedure WMNCCalcSize(var Msg: TWMNCCalcSize); message WM_NCCALCSIZE;
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Repaint; override;
    procedure Invalidate; override;
  published
    property BtnWidth: Integer read FBtnWidth write SetBtnWidth;
    property BtnHeight: Integer read FBtnHeight write SetBtnHeight;
    property Collapsed: Boolean read FCollapsed write SetCollapsed;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Float Bar', [TFloatBar]);
end;

{ TFloatBar }

constructor TFloatBar.Create(AOwner: TComponent);
begin
  inherited;
  ControlStyle:= [csAcceptsControls,
    csCaptureMouse,
    csDesignInteractive,
    csClickEvents,
    csReplicatable,
    csNoStdEvents
    ];
  Width:= 400;
  Height:= 60;
  FBtnWidth:= 50;
  FBtnHeight:= 20;
  FCollapsed:= False;
end;

procedure TFloatBar.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params.WindowClass do
    Style := Style and not (CS_HREDRAW or CS_VREDRAW);
end;

destructor TFloatBar.Destroy;
begin

  inherited;
end;

procedure TFloatBar.Invalidate;
begin
  inherited;
  RepaintBorder;
end;

procedure TFloatBar.Repaint;
begin
  inherited Repaint;
  RepaintBorder;
end;

procedure TFloatBar.RepaintBorder;
begin
  if Visible and HandleAllocated then
    Perform(WM_NCPAINT, 0, 0);
end;

procedure TFloatBar.SetBtnHeight(const Value: Integer);
begin
  FBtnHeight := Value;
  Invalidate;
end;

procedure TFloatBar.SetBtnWidth(const Value: Integer);
begin
  FBtnWidth := Value;
  Invalidate;
end;

procedure TFloatBar.SetCollapsed(const Value: Boolean);
begin
  FCollapsed := Value;
  Invalidate;
end;

procedure TFloatBar.WMNCPaint(var Message: TWMNCPaint);
begin
  inherited;
  PaintBorder;
end;

procedure TFloatBar.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
  Message.Result := 1;
end;

procedure TFloatBar.WMNCCalcSize(var Msg: TWMNCCalcSize);
var
  lpncsp: PNCCalcSizeParams;
begin
  if Msg.CalcValidRects then begin            //<------ HERE --------
    lpncsp := Msg.CalcSize_Params;
    if lpncsp = nil then Exit;
    lpncsp.rgrc[0].Bottom:= lpncsp.rgrc[0].Bottom-FBtnHeight;
    Msg.Result := 0;
  end;
  inherited;
end;

function TFloatBar.BtnRect: TRect;
begin
  //Return a rect where the non-client collapse button is to be...
  Result:= Rect(ClientWidth-FBtnWidth, ClientHeight, ClientWidth, ClientHeight+FBtnHeight);
end;

function TFloatBar.TransRect: TRect;
begin
  //Return a rect where the non-client transparent area is to be...
  Result:= Rect(0, ClientHeight, ClientWidth, ClientHeight+FBtnHeight);
end;

procedure TFloatBar.WMNCHitTest(var Message: TWMNCHitTest);
var
  P: TPoint;
  C: TCursor;
begin
  C:= crDefault; //TODO: Find a way to change cursor elsewhere...
  P:= Point(Message.XPos, Message.YPos);
  if PtInRect(BtnRect, P) then begin
    Message.Result:= HTCLIENT;
    C:= crHandPoint;
  end else
  if PtInRect(TransRect, P) then
    Message.Result:= HTTRANSPARENT
  else
    inherited;
  Screen.Cursor:= C;
end;

procedure TFloatBar.Paint;
begin
  inherited;

  //Paint Background
  Canvas.Brush.Style:= bsSolid;
  Canvas.Pen.Style:= psClear;
  Canvas.Brush.Color:= Color;
  Canvas.FillRect(Canvas.ClipRect);

  Canvas.Pen.Style:= psSolid;
  Canvas.Pen.Width:= 3;
  Canvas.Brush.Style:= bsClear;
  Canvas.Pen.Color:= clBlue;

  Canvas.MoveTo(0, 0);
  Canvas.LineTo(ClientWidth, 0); //Top
  Canvas.LineTo(ClientWidth, ClientHeight+FBtnHeight); //Right
  Canvas.LineTo(ClientWidth-FBtnWidth, ClientHeight+FBtnHeight); //Bottom of Button
  Canvas.LineTo(ClientWidth-FBtnWidth, ClientHeight); //Left of Button
  Canvas.LineTo(0, ClientHeight); //Bottom
  Canvas.LineTo(0, 0);

end;

procedure TFloatBar.PaintBorder;
begin
  Canvas.Handle:= GetWindowDC(Handle);
  try

    //TODO: Paint "transparent" area by painting parent...


    //Paint NC button background
    Canvas.Brush.Style:= bsSolid;
    Canvas.Pen.Style:= psClear;
    Canvas.Brush.Color:= Color;
    Canvas.Rectangle(ClientWidth-FBtnWidth, ClientHeight, ClientWidth, ClientHeight+FBtnHeight);

    //Paint NC button border
    Canvas.Pen.Style:= psSolid;
    Canvas.Pen.Width:= 3;
    Canvas.Brush.Style:= bsClear;
    Canvas.Pen.Color:= clBlue;
    Canvas.MoveTo(ClientWidth, ClientHeight);
    Canvas.LineTo(ClientWidth, ClientHeight+FBtnHeight);
    Canvas.LineTo(ClientWidth-FBtnWidth, ClientHeight+FBtnHeight);
    Canvas.LineTo(ClientWidth-FBtnWidth, ClientHeight);

    //Paint NC Button Chevron      //TODO: Calculate chevron size/position
    if FCollapsed then begin
      Canvas.MoveTo(ClientWidth-30, ClientHeight+7);
      Canvas.LineTo(ClientWidth-25, ClientHeight+13);
      Canvas.LineTo(ClientWidth-20, ClientHeight+7);
    end else begin
      Canvas.MoveTo(ClientWidth-30, ClientHeight+13);
      Canvas.LineTo(ClientWidth-25, ClientHeight+7);
      Canvas.LineTo(ClientWidth-20, ClientHeight+13);
    end;
  finally
    ReleaseDC(Handle, Canvas.Handle);
  end;
end;

end.

1 ответ

Решение

... Я должен сначала проверить Msg.CalcValidRectsи только сделайте мой расчет, если это True,

Вы ошиблись. Сообщение имеет довольно сложный механизм, и документация может немного сбивать с толку, пытаясь объяснить два различных режима, в которых работает сообщение (wParam правда или ложь). Часть, которая относится к вашему делу, является вторым абзацем lParam:

Если wParam ЛОЖЬ, lParam указывает на структуру RECT. На входе структура содержит предложенный прямоугольник окна для окна. При выходе структура должна содержать экранные координаты соответствующего окна клиентской области.

Вы найдете множество примеров использования этой простой формы в VCL, где wParam вообще не проверяется, как в TToolWindow.WMNCCalcSize, TCustomCategoryPanel.WMNCCalcSize так далее..

(Обратите внимание, что NCCALCSIZE_PARAMS.rgrc даже не прямоугольный массив, когда wParam неверно, но поскольку вы работаете с предполагаемым первым прямоугольником, все в порядке.)

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