1

Short Version

How can i select all text in a TEdit when the control gains input focus from the user clicking the edit box?

Motiviation

Following the Windows User Design Guidelines for a "search" box:

Interaction

On input focus, automatically select any previously entered text. Doing so allows users to enter a new search by typing, or to modify the previous search by positioning the caret using the arrow keys.

That is my hypothetical reason; but i don't need a reason. I just need the behaviour i'm asking for, which is the behaviour of Windows (see Windows Explorer).

Research Effort

We all know that SelectAll property:

Determines whether all the text in the edit control is automatically selected when the control gets focus.

Set AutoSelect to select all the text when the edit control gets focus. AutoSelect only applies to single-line edit controls.

Use AutoSelect when the user is more likely to replace the text in the edit control than to append to it.

And that does work; but only when:

  • the edit control gains input focus through the Tab key
  • or through a call to SetFocus

It doesn't work if the user's clicks the control to give it input focus (meaning that this portion of the documentation above isn't, strictly speaking, correct).

What have you tried?

  1. Edit1.AutoSelect := True;

    Simply does not work.

  2. OnEnter call SelectAll

    procedure TForm1.Edit1Enter(Sender: TObject);
    begin
     Edit1.SelectAll;
    end;
    

    it simply does not work; presumably for the same reason AutoSelect doesn't. Without debugging the VCL, i assume that it is selecting all, but then a click is registered and that tries to set the caret; clearing the selection.

  3. OnClick call SelectAll

    procedure TForm1.Edit1Click(Sender: TObject);
    begin
     Edit1.SelectAll;
    end;
    

    That doesn't work because it calls SelectAll every time the user clicks, rather than when the edit control gains input focus (i.e. clicking to set the caret position re-selects all—not what we want.

  4. OnClick call SelectAll with focus check

    procedure TForm1.Edit1Click(Sender: TObject);
    begin
     if not Edit1.Focused then
         Edit1.SelectAll;
    end;
    

    By the time OnClick is called, Focused is already true.

  5. OnMouseDown with Focus check

    procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    begin
     if not Edit1.Focused then
         Edit1.SelectAll;
    end;
    

    Doesn't work because by the time MouseDown is called, the control already has focus.

The Question

Which brings us back to the question:

  • How to SelectAll text when a TEdit control gains input focus by the user clicking?

And for reference: the Windows control selects all text before the mouse is released. I feel like it needs an ugly hack of:

  • catch WM_SETFOCUS
  • somehow know if focus came from mouse (rather than keyboard navigation, or programmatic) and set a flag
  • if the flag was set: eat the next MouseDown/MouseClick

But i feel like that is fraught with eating user mouse clicks if it is not done correctly; which is even worse.

Bonus Reading

  • TEdit onclick select all? (has a different ask; wants to always select all text every click. Definitly not what we want, or what Windows does)

CRME

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Edit1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
//  Edit1.SetFocus;
end;

procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
//  if not Edit1.Focused then
//      Edit1.SelectAll;
end;

end.
8
  • "which is the behaviour of Windows (see Windows Explorer)". I don't really get this. The standard Windows EDIT control (which TEdit is a thin interface to) doesn't behave like that. Modern Windows apps, on the other hand, don't use the classic desktop controls at all, so they are different. Still, if I enter a search text in the Windows Explorer search field (top-right corner), move focus away from the search box, and then click the search box again, the text isn't selected. The behaviour is like that of a standard TEdit. Commented Oct 25 at 15:06
  • Anyhow, personally, I tend always to activate search features (and indeed almost any text boxes) using Alt+X accelerator keys (like "Frog &colour:"), keyboard shortcuts (like Ctrl+F for find), or Tab-based navigation, and then you always get the auto-select-all behaviour. Commented Oct 25 at 15:08
  • What you're asking for is the default behavior of an EDIT control in a dialog. If your EDIT control lives in a dialog, then you already get that behavior (unless you somehow managed to break it). If the EDIT control is the child of a non-dialog window, you'll have to add that functionality. I don't know whether calling IsDialogMessage is sufficient or if you have to send WM_GETDLGCODE messages and respond accordingly. Commented Oct 25 at 16:11
  • 1
    A quick experiment seems to indicate that adding a procedure TEdit.WMSetFocus(var Message: TWMSetFocus); begin inherited; PostMessage(Self.Handle, EM_SETSEL, 0, -1); end; does the trick. Commented Oct 25 at 16:27
  • If this approach is good enough for you, you can either use an interposer class in every file you want this new behaviour, or you could create your own TEditEx control (maybe with a published boolean property AlwaysAutoSelect (or ClickAutoSelect)) and register it in the IDE. Commented Oct 25 at 16:34

1 Answer 1

2

A simple approach is to post a "select all" message when you handle the edit box's WM_SETFOCUS message:

procedure TEdit.WMSetFocus(var Message: TWMSetFocus);
begin
  inherited;
  PostMessage(Self.Handle, EM_SETSEL, 0, -1);
end;

You can either use an interposer class in every file in which you want this new behaviour, or you could create your own TEditEx control (maybe with a published AlwaysAutoSelect (or ClickAutoSelect) boolean property) and register it in the IDE.

Here's how you'd declare an interposer in a unit:

interface

uses ...

type
  TEdit = class(Vcl.StdCtrls.TEdit)
  public
    procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
  end;

  TForm1 = ...

Screen recording: Clicking an edit box makes it select all.

Sign up to request clarification or add additional context in comments.

3 Comments

You don't really need to handle WM_SETFOCUS directly, since the VCL uses that notification to fire the OnEnter event, so just handle that event instead. And you can replace PostMessage() with TThread.ForceQueue(nil, SelectAll) so you are not tied to the current Handle.
@RemyLebeau: Too many times I've noticed that the VCL is out of sync with reality when it comes to focus, so I feel safer just telling the OS what I want. (But you are right in this case, OnEnter seems to be good enough, but it doesn't really offer any advantage. Likely you want the same behaviour for all of your edit controls, and then subclassing is to be preferred anyway.)
@RemyLebeau: Regarding TThread.ForceQueue(nil, SelectAll): That will effectively do the same thing, but using significantly heavier machinery. I guess the advantage is mainly that you hide the "scary" Win32 parts (PostMessage(Self.Handle, EM_SETSEL, 0, -1);) behind some more modern and readable code. But whenever I can choose between a plain PostMessage and a ForceQueue, I tend to choose the former. (Having said that, I often do use TThread.ForceQueue(nil, ...) when I don't have a single message available with the precise semantics I need.)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.