How to expand a TMemo while keying in #116

Question
I need a memo component or text component (that can handle multiple lines), that should stay at its design time height and get enlarged when it receives focus. In other words, it should automatically grow or shrink while the user types in words.

The following control will do that. You can set a maximum height for the control, if the text needs more space the control will sprout a scrollbar. WordWrap should be true: the control as is does not deal well with a horizontal scrollbar, if present.

unit PopupMemo;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TPopupMemo = class(TMemo)
  private
    FDesigntimeHeight: Integer;
    FFocusedHeight: Integer;
    FMaximumHeight: Integer;
    FCanvas: TControlCanvas;
    procedure CMTextChanged(var msg: TMessage); message CM_TEXTCHANGED;
    procedure SetFocusedHeight(const Value: Integer);
    procedure SetMaximumHeight(const Value: Integer);
    procedure UpdateHeight;
    procedure ChangeScrollbar( value: TScrollStyle );
  protected
    procedure DoEnter; override;
    procedure DoExit; override;
    procedure Change; override;
    procedure AdjustHeight;
    property Canvas: TControlCanvas read FCanvas;
  public
    Constructor Create( aOwner: TComponent ); override;
    Destructor Destroy; override;
    property FocusedHeight: Integer
      read FFocusedHeight write SetFocusedHeight;
  published
    property MaximumHeight: Integer
      read FMaximumHeight write SetMaximumHeight;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('PBGoodies', [TPopupMemo]);
end;

{ TPopupMemo }

procedure TPopupMemo.AdjustHeight;
const
  alignflags: Array [TAlignment] of DWORD =
    (DT_LEFT, DT_CENTER, DT_RIGHT);
var
  oldrect, newrect: TRect;
  newheight: Integer;
  S: String;
begin
  if not HandleAllocated then
    Exit;
  Perform( EM_GETRECT, 0, lparam(@oldrect));
  S:= Text;
  {DrawText discards a trailing linebreak for measurement, so if the
  user hits return in the control and the new line would require a
  larger memo we do not get the correct value back. To fix that we add
  a blank just for the measurement if the last character is a linefeed.}
  if (Length(S) > 0) and (S[Length(S)] = #10) then
    S := S + ' ';
  Canvas.Font := Font;
  newrect := oldrect;
  DrawText(
    Canvas.Handle,
    PChar(S),
    Length(S),
    newrect,
    DT_CALCRECT or DT_EDITCONTROL or DT_WORDBREAK or DT_NOPREFIX
      or DT_EXPANDTABS or alignflags[ Alignment ]
  );
  if oldrect.bottom <> newrect.bottom then
  begin
    newHeight := Height - (oldrect.bottom-oldrect.top)
      + (newrect.bottom - newrect.top );
    if newHeight > MaximumHeight then
      ChangeScrollbar( ssVertical )
    else
      ChangeScrollbar( ssNone );
    FocusedHeight := newHeight;
  end;
end;

procedure TPopupMemo.Change;
begin
  AdjustHeight;
  inherited;
end;

procedure TPopupMemo.ChangeScrollbar(value: TScrollStyle);
var
  oldpos: Integer;
begin
  if Scrollbars <> value then
  begin
    {Changing the scrollbar recreates the window and looses the
    caret position!}
    oldpos := SelStart;
    Scrollbars := value;
    SelStart := oldpos;
    Perform( EM_SCROLLCARET, 0, 0 );
  end;
end;

procedure TPopupMemo.CMTextChanged(var msg: TMessage);
begin
  AdjustHeight;
  inherited;
end;

constructor TPopupMemo.Create(aOwner: TComponent);
begin
  inherited;
  FFocusedHeight := Height;
  FMaximumHeight := 5 * Height;
  FCanvas:= TControlCanvas.Create;
  FCanvas.Control := Self;
end;

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

procedure TPopupMemo.DoEnter;
begin
  inherited;
  FDesigntimeHeight := Height;
  UpdateHeight;
  {Height := FFocusedHeight;}
end;

procedure TPopupMemo.DoExit;
begin
  inherited;
  Height := FDesigntimeHeight;
end;

procedure TPopupMemo.SetFocusedHeight(const Value: Integer);
begin
  if FFocusedHeight <> Value then
  begin
    if Value > MaximumHeight then
      FFocusedHeight := MaximumHeight
    else
      FFocusedHeight := value;
    if Focused then
      UpdateHeight;
  end;
end;

procedure TPopupMemo.SetMaximumHeight(const Value: Integer);
begin
  if FMaximumHeight <> Value then
  begin
    FMaximumHeight := Value;
    if Value < FocusedHeight then
      FocusedHeight := Value;
  end;
end;

procedure TPopupMemo.UpdateHeight;
var
  line: Integer;
begin
  if HandleAllocated and Focused then
  begin
    Height := FocusedHeight;
    if Scrollbars = ssNone then
    begin
      line := Perform( EM_GETFIRSTVISIBLELINE, 0, 0 );
      if line > 0 then
        Perform( EM_LINESCROLL, 0, - line );
    end;
  end;
end;

end.
Original resource: The Delphi Pool
Author: Peter Below
Added: 2009/10/26
Last updated: 2009/10/26