delphivcleditcontrol

TEdit and WM_PAINT message handler strange behavior


I am trying to implement my own drawing on a TEdit control when it does not have focus (show ellipsis in TEdit when the editor doesn’t fully display its text). So I starאed with this code:

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;

Notice that when FEllipsis and (not Focused) the message handler does nothing.

Now I dropped a TButton and 2 TEdit controls on the form, and added form OnCreate:

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

I expected Edit1 to draw normally, and Edit2 not to draw anything inside the edit control.

Instead the message handler was processed endlessly, Edit1 was not drawn also, and the entire application was choking (with 25% CPU usage!). I have also tried returning Message.Result := 0 - same effect.

Now, for the "strange" part: When I obtain the canvas handle with BeginPaint, everything works as expected.

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;

Notice I did not call inherited either.

How to explain this behavior? Thanks.


Solution

  • When a window is invalidated, it is asked to make itself valid at the next paint cycle. Typically that happens in the main thread message loop when GetMessage finds that the queue is empty. At that point WM_PAINT messages are synthesised and dispatched to the window.

    When the window receives these messages, its task is to paint itself. That is typically done with calls to BeginPaint and then EndPaint. The call to BeginPaint validates the window's client rect. This is the critical piece of information that you are lacking.

    Now, in your code, you did not call inherited and so did not paint anything, and did not call BeginPaint / EndPaint. Because you did not call BeginPaint, the window remains invalid. And so an endless stream of WM_PAINT messages are generated.

    The relevant documentation can be found here:

    The BeginPaint function automatically validates the entire client area.