Search code examples
delphivcl

How do i custom draw of TEdit control text?


I'd like to draw a piece of TEdit.Text using Font.Color different from the default. Are there any examples how to do that?

I'm attempting to do something like this:

NOTE: what this screenshot pictures is merely a hairy draft, but it convinces me what problem solvable.


Solution

  • Edit controls do not have owner-draw support, but you can custom-draw it by sub-classing it and handling WM_PAINT (among many other messages). It's doable, but it would be a world of pain to actually implement 100% correctly. From the docs: Developing Custom Draw Controls in Visual C++:

    Note that owner-draw will work for most controls. However, it doesn't work for edit controls; and with regards to the list control, it works only for report-view style

    I was also interested to find out how deep the rabbit hole goes, so,
    Here is a code sample using an interposer class (still needs to implement selection but the custom drawing works when the caret is in the control):

    type
      TEdit = class(StdCtrls.TEdit)
      private
        FCanvas: TCanvas;
        procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
      protected
        procedure WndProc(var Message: TMessage); override;
        procedure Paint; virtual;
        procedure PaintWindow(DC: HDC); override;
        property Canvas: TCanvas read FCanvas;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
      end;
    
    ...
    
    constructor TEdit.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      FCanvas := TControlCanvas.Create;
      TControlCanvas(FCanvas).Control := Self;
    end;
    
    destructor TEdit.Destroy;
    begin
      FCanvas.Free;
      inherited Destroy;
    end;
    
    procedure TEdit.Paint;
    var
      R: TRect;
      I: Integer;
      S: String;
    begin
      R := ClientRect;
      Inc(R.Left, 1);
      Inc(R.Top, 1);
      Canvas.Brush.Assign(Self.Brush);
      Canvas.Font.Assign(Self.Font);
      for I := 1 to Length(Text) do
      begin
        if Text[I] in ['0'..'9'] then
          Canvas.Font.Color := clRed
        else
          Canvas.Font.Color := clGreen;
        S := Text[I];
        DrawText(Canvas.Handle, PChar(S), -1, R, DT_LEFT or DT_NOPREFIX or
          DT_WORDBREAK or DrawTextBiDiModeFlagsReadingOnly);
        Inc(R.Left,Canvas.TextWidth(S));
      end;
    end;
    
    procedure TEdit.PaintWindow(DC: HDC);
    begin
      FCanvas.Lock;
      try
        FCanvas.Handle := DC;
        try
          TControlCanvas(FCanvas).UpdateTextFlags;
          Paint;
        finally
          FCanvas.Handle := 0;
        end;
      finally
        FCanvas.Unlock;
      end;
    end;
    
    procedure TEdit.WMPaint(var Message: TWMPaint);
    begin
      ControlState := ControlState+[csCustomPaint];
      inherited;
      ControlState := ControlState-[csCustomPaint];
    end;
    
    procedure TEdit.WndProc(var Message: TMessage);
    begin
      inherited WndProc(Message);
      with Message do
        case Msg of
          CM_MOUSEENTER, CM_MOUSELEAVE, WM_LBUTTONUP, WM_LBUTTONDOWN,
          WM_KEYDOWN, WM_KEYUP,
          WM_SETFOCUS, WM_KILLFOCUS,
          CM_FONTCHANGED, CM_TEXTCHANGED:
          begin
            Invalidate;
          end;
       end; 
    end;
    

    enter image description here