It is hard to do this well. The most ambitious approach would be to do a lot of logic on key down, paste, etc., like in this answer. You'd be surprised how many cases there are to handle!
This works pretty well, I think:
type
TEdit = class(StdCtrls.TEdit)
...
procedure TEdit.KeyPress(var Key: Char);
function InvalidKey: boolean;
begin
InvalidKey :=
(
(Key = '0') and
(
((SelStart = 0) and (Length(Text) - SelLength <> 0))
or
((SelStart = 1) and (Text[1] = '0'))
)
)
or
(
not (Key in ['0'..'9', #8, ^Z, ^X, ^C, ^V])
)
end;
begin
inherited;
if InvalidKey then
begin
beep;
Key := #0;
end else if (SelStart = 1) and (Text[1] = '0') then
begin
Text := Copy(Text, 2);
end;
end;
procedure TEdit.WMPaste(var Message: TWMPaste);
var
ClipbrdText: string;
function ValidText: boolean;
var
i: Integer;
begin
result := true;
for i := 1 to Length(ClipbrdText) do
if not (ClipbrdText[i] in ['0'..'9']) then
begin
result := false;
break;
end;
end;
function RemoveLeadingZeros(const S: string): string;
var
NumLeadingZeros: integer;
i: Integer;
begin
NumLeadingZeros := 0;
for i := 1 to Length(S) do
if S[i] = '0' then
inc(NumLeadingZeros)
else
break;
result := Copy(S, NumLeadingZeros + 1);
end;
begin
if Clipboard.HasFormat(CF_TEXT) then
begin
ClipbrdText := Clipboard.AsText;
if not ValidText then
begin
beep;
Exit;
end;
if SelStart = 0 then
ClipbrdText := RemoveLeadingZeros(ClipbrdText);
SelText := ClipbrdText;
end
else
inherited;
end;
This looks complicated, but all this logic is necessary. For instance, the code handles all these cases well:
You can only enter digits.
If SelStart = 0
and not every character is selected, you cannot enter 0
, because then you would get a leading zero.
If SelStart = 0
and every character is selected, you can enter 0
. Because you should be able to enter this valid number.
- A special case: If there are zero characters, then of course zero characters are selected, so by simply logic you will be able to enter
0
from the state of the empty string!
If SelStart = 1
and the Text[1] = '0'
[if SelStart = 1
, then Text[1]
exists, so we rely on boolean short-circuit evaluation here], then you cannot enter 0
.
If the contents of the edit field is 0
and you add a non-zero character after the zero, then the zero is removed (and there is no interference with the caret position).
You can only paste digits. If SelStart = 0
, leading zeros (in the clipboard data) are discarded.
In fact, to make the behaviour perfect, you need even more code. For instance, suppose that the contents of the edit field is 123000456
. As it is now, you can select the first three characters (123
) and press Backspace or Delete to obtain the invalid text 000456
. The solution is to add extra code that removes leading zeroes resulting from either of these two operations. Update: I have been doing some thinking, and have come to realise that it is probably best not to implement this (why?).
One shortcut, however, would be to 'prettify' the contents of the edit when it isn't focused, because when it isn't, you are much less likely to shoot the user in the foot by doing something with the contents of the edit.
For instance, you can do something like
function PrettifyNumber(const S: string): string;
var
NumLeadingZeros: integer;
i: Integer;
begin
NumLeadingZeros := 0;
for i := 1 to Length(S) do
if S[i] = '0' then
inc(NumLeadingZeros)
else
break;
result := Copy(S, NumLeadingZeros + 1);
if length(result) = 0 then
result := S;
end;
and then replace the contents of the edit field by its prettified version when it loses keyboard focus. Of course, you should consider adding this functionality to a derived edit class, but for testing you can simply use the OnExit
event:
procedure TForm1.Edit1Exit(Sender: TObject);
begin
Edit1.Text := PrettifyNumber(Edit1.Text);
end;
I suppose that you want this functionality in a lot of edits in a major application (or not so major). So you will in fact write your own subclassed control. Then I suppose you will also need other types of verification than simply removal of leading zeros? You thus have to integrate this code nicely with the rest of the code. If in fact you will use this edit 'subclass' everywhere in a big application, you could also consider adding a shortcut, to the edit control class, so that you can prettify/verify the text on demand, and not only on exit (which is particularly difficult if the edit control is alone on its form!)
Update
On request, this is a simple solution that works badly:
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if (Key = '0') and (Edit1.SelStart = 0) then Key := #0;
end;
I leave it as an exercise to figure out the issues with this approach. If you fix the issues, one by one, as you discover them, you will end up with my long code above!