TEdit 和 WM_PAINT 消息处理程序的奇怪行为

TEdit and WM_PAINT message handler strange behavior

我正在尝试在没有焦点的 TEdit 控件上实现我自己的绘图(当编辑器未完全显示其文本时,在 TEdit 中显示省略号)。所以我用这段代码加注星标:

type
  TEdit = class(StdCtrls.TEdit)
  private
    FEllipsis: Boolean;
    FCanvas: TCanvas;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

constructor TEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FEllipsis := False;
  FCanvas := TControlCanvas.Create;
  TControlCanvas(FCanvas).Control := Self;
end;

destructor TEdit.Destroy;
begin
  FCanvas.Free;
  inherited;
end;

procedure TEdit.WMPaint(var Message: TWMPaint);
begin
  if FEllipsis and (not Focused) then
  begin
    // Message.Result := 0;
    // TODO...
  end
  else
    inherited;
end;

请注意,当 FEllipsis and (not Focused) 消息处理程序不执行任何操作。

现在我在表单上删除了一个 TButton 和 2 个 TEdit 控件,并添加了表单 OnCreate:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit2.FEllipsis := True;
end;

我希望 Edit1 可以正常绘制,并且 Edit2 不会在编辑控件内绘制任何东西。

取而代之的是,消息处理程序被无休止地处理,Edit1 也没有被绘制,整个应用程序令人窒息(25% CPU 使用率!)。我也试过返回 Message.Result := 0 - 同样的效果。

现在,对于 "strange" 部分:当我使用 BeginPaint 获得 canvas 句柄时,一切都按预期工作。

procedure TEdit.WMPaint(var Message: TWMPaint);
var
  PS: TPaintStruct;
begin
  if FEllipsis and (not Focused) then
  begin    
    if Message.DC = 0 then
      FCanvas.Handle := BeginPaint(Handle, PS)
    else
      FCanvas.Handle := Message.DC;
    try
      // paint on FCanvas...
    finally
      FCanvas.Handle := 0;
      if Message.DC = 0 then EndPaint(Handle, PS);
    end;
  end
  else
    inherited;
end;

注意我也没有打电话给 inherited

如何解释这种行为?谢谢。

当一个window无效时,它会被要求在下一个绘制周期使自己有效。当 GetMessage 发现队列为空时,通常会在主线程消息循环中发生这种情况。那时 WM_PAINT 消息被合成并发送到 window.

当window收到这些消息后,它的任务就是画自己。这通常通过调用 BeginPaint 然后调用 EndPaint 来完成。对 BeginPaint 的调用验证了 window 的客户端 rect。这是您缺少的关键信息。

现在,在您的代码中,您没有调用 inherited,因此没有绘制任何东西,也没有调用 BeginPaint / EndPaint。因为您没有调用 BeginPaint,所以 window 仍然无效。就这样,源源不断的WM_PAINT消息产生了。

可以找到相关文档here:

The BeginPaint function automatically validates the entire client area.