【问题标题】:Autoresizing TCheckBox (like TLabel)自动调整 TCheckBox(如 TLabel)
【发布时间】:2019-11-29 15:11:20
【问题描述】:

我想创建一个可以自动调整其宽度的复选框,就像 TLabel 一样。

UNIT cvCheckBox;
{  It incercepts CMTextChanged where it recomputes the new Width}
INTERFACE
USES
  Winapi.Windows, Winapi.Messages, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.StdCtrls;

TYPE
 TcCheckBox = class(TCheckBox)
 private
   FAutoSize: Boolean;
   procedure AdjustBounds;
   procedure setAutoSize(b: Boolean);  reintroduce;
   procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
   procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;
 protected
    procedure Loaded; override;
 public
    constructor Create(AOwner: TComponent); override;
 published
    //property Caption read GetText write SetText;
    property AutoSize: Boolean read FAutoSize write setAutoSize stored TRUE;
 end;

IMPLEMENTATION

CONST
  SysCheckWidth: Integer = 21;  // In theory this can be obtained from the "system"

constructor TcCheckBox.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  FAutoSize:= TRUE;
end;


procedure TcCheckBox.AdjustBounds;
VAR
   DC: HDC;
   Canvas: TCanvas;
begin
  if not (csReading in ComponentState) and FAutoSize then
  begin
    // this caused the problem [solution provided by Dima] 
    if HandleAllocated then   // Deals with the missing parent during Creation
    begin
     // We need a canvas but this control has none. So we need to "produce" one.
     Canvas := TCanvas.Create;
     DC     := GetDC(Handle);
     TRY
       Canvas.Handle := DC;
       Canvas.Font   := Font;
       Width := Canvas.TextWidth(Caption) + SysCheckWidth + 4;
       Canvas.Handle := 0;
     FINALLY
       ReleaseDC(Handle, DC);
       Canvas.Free;
     END;
    end;
  end;
end;


procedure TcCheckBox.setAutoSize(b: Boolean);
begin
  if FAutoSize <> b then
  begin
    FAutoSize := b;
    if b then AdjustBounds;
  end;
end;

procedure TcCheckBox.CMTextChanged(var Message:TMessage);
begin
  Invalidate;
  AdjustBounds;
end;


procedure TcCheckBox.CMFontChanged(var Message:TMessage);
begin
  inherited;
  if AutoSize
  then AdjustBounds;
end;

procedure TcCheckBox.Loaded;
begin
  inherited Loaded;
  AdjustBounds;
end;
end.

但是我有一个问题。放置在 PageControl 的非活动选项卡中的复选框不会自动重新计算其大小。换句话说,如果我有两个包含复选框的选项卡,则在应用程序启动时,只有当前打开的选项卡中的复选框会正确调整大小。当我单击另一个选项卡时,复选框将具有原始大小(在设计时设置的那个)。

我确实在程序启动时设置了整个表单的字体大小(在 Form Create 之后,使用 PostMessage(Self.Handle, MSG_LateInitialize) )。

procedure TForm5.FormCreate(Sender: TObject);
begin
 PostMessage(Self.Handle, MSG_LateInitialize, 0, 0);  
end;

procedure TForm5.LateInitialize(var message: TMessage);
begin
 Font:= 22;
end;

为什么非活动标签中的复选框没有宣布字体已更改?

【问题讨论】:

  • 显然,问题出在AjustBounds方法的if HandleAllocated then。因为自然原因TPageControl(节省资源)不分配非活动页面,直到您选择适当的选项卡。您可以通过编译您的应用程序并调用放置在非活动页面上的MyCheckBox.HandleAllocated 轻松检查它。这就是AdjustBounds 方法无效的原因。
  • @Dima - 它奏效了。如果您发表评论作为答案,我会接受。

标签: delphi checkbox delphi-xe7 autoresize


【解决方案1】:

正如我在对该问题的评论中所说,问题在于TPageControl 仅初始化当前选择的页面。这意味着另一个页面将没有有效的句柄。因此,放置在其上的所有组件也没有手柄。这是AdjustBounds 方法根本不起作用的原因。

但是这种糟糕的情况可以通过使用常量HWND_DESKTOP 以其他方式获取DeviceContext 来解决(详见更新部分)。
请看下面的代码:

procedure TcCheckBox.AdjustBounds;
var
  DC: HDC;
  Canvas: TCanvas;
begin
  if not (csReading in ComponentState) and FAutoSize then
  begin
    // Retrieve DC for the entire screen
    DC := GetDC(HWND_DESKTOP);
    try
      // We need a canvas but this control has none. So we need to "produce" one.
      Canvas := TCanvas.Create;
      try
        Canvas.Handle := DC;
        Canvas.Font := Font;
        Width := Canvas.TextWidth(Caption) + SysCheckWidth + 4;
        Canvas.Handle := 0;
      finally
        Canvas.Free;
      end;
    finally
      ReleaseDC(HWND_DESKTOP, DC);
    end;
  end;
end;

更新
由于已经发布了一些有用的 cmets,我更改了代码以摆脱对 GetDesktopWindow 函数的调用。相反,代码使用HWND_DESKTOP 常量,传递给GetDC 函数允许获取整个屏幕的DeviceContext

【讨论】:

  • @SertacAkyuz,我在哪里可以了解这个常数?我积极使用docs(以前称为msdn),但从未看到对此的详细描述。你能分享一些链接或其他东西吗?另外,在VCL中GetDesktopWindow经常使用,而HWND_DESKTOP只在Grids.pas单元中使用。
  • 在 TCustomLabel.AdjustBounds 中,Embarcadero 使用 GetDC(0)。
  • 我会再次尝试说服您...此代码存在 2 个小问题。真的很小。首先是,即使 GetDesktopWindow 返回 0(我不知道它是否返回 0),您也不必退出。因为如果您想要整个屏幕的 DC,0 可以很好地传递给 GetDC。您可以参考 GetDC 的文档来验证这一点。
  • 第二个是,因为 0 可以很好地传递给 GetDC,所以您甚至根本不需要调用 GetDesktopWindow。如果 VCL 有这样的代码,这是 VCL 代码中的一个小问题。
  • HWND_DESKTOP 是一个常量,可以用作桌面窗口的伪句柄,它的值为 0。您可以决定使用它,这使得代码更具可读性,当您需要伪桌面窗口的句柄。或者,如果您想坚持使用文档,则可以在调用 GetDC[Ex] 时使用 0。
【解决方案2】:

这是一个修复(嗯,它更多的是一个功能,而不是一个修复),如果 Alignment 是 taLeftJustify,它将移动标题而不是复选框

procedure TcCheckBox.AdjustBounds;
var
  DC: HDC;
  Canvas: TCanvas;
  PrevWidth : integer;
begin
  if not (csReading in ComponentState) and FAutoSize then
  begin
    // Retrieve DC for the entire screen
    DC := GetDC(HWND_DESKTOP);
    try
      // We need a canvas but this control has none. So we need to "produce" one.
      Canvas := TCanvas.Create;
      try
        Canvas.Handle := DC;
        Canvas.Font := Font;
        PrevWidth := Width;
        Width := Canvas.TextWidth(Caption) + SysCheckWidth + 4;
        Canvas.Handle := 0;
        if (Alignment = taLeftJustify) then
          begin
            Left := Left - (Width - PrevWidth);
          end;
      finally
        Canvas.Free;
      end;
    finally
      ReleaseDC(HWND_DESKTOP, DC);
    end;
  end;
end;

【讨论】:

  • 如果桌面的canvas和app的canvas不一样怎么办?如果您的应用由于 DPI 不兼容而运行虚拟化,则可能会发生这种情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-15
  • 1970-01-01
  • 2016-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多