ここまで、プロパティをプログラムのコードから設定する部分をメインに作成してきました。コードからはValueプロパティを使用して、数値を設定できるようになりました。
しかし、編集ボックスにユーザーが入力する部分についてはそのままで、どのような文字列でも入力できてしまいます。また、適切な数字を入力しても、Valueプロパティに反映されません。
そこで、何度かに分けて、編集ボックスへの入力部分を作っていきたいと思います。予定では、
- 数値だけを入力できるようにする
- 入力時のカーソル位置を調整する
- Valueプロパティに反映する
の順で作成します。
KeyPressメソッドのオーバーライド
ユーザーがキーボードから文字を入力したとき、コンポーネント内のどこかで入力された文字をチェックして、数値として適切なものだけを受け付けるという処理が必要です。
TWinControl
から派生したクラスでは、文字の入力があると KeyPress
メソッドが実行されます。
KeyPress
メソッドは OnKeyPress
イベントを処理するメソッドです。ですが、入力された文字が不適切な場合、別の文字へと置き換えることができます。必要であれば、その文字の入力がなかったことにもできます。
KeyPress
メソッドは TWinControl
クラスのメソッドです。このメソッドに新しい機能(数値だけを受け付ける機能)を追加するには、メソッドをオーバーライドします。
オーバーライドできるメソッドは、仮想メソッド(
virtual
)か、動的メソッド(dynamic
)、またはこれらのメソッドをオーバーライドしたメソッドに限られます。
KeyPress
メソッドのオーバーライドは、以下のように記述します(20行目)。
protected
{ Protected 宣言 }
procedure KeyPress(var Key: Char); override;
オーバーライドするときはメソッドのタイプ(手続きか関数か)や引数を、オーバーライド元のメソッドに合わせる必要があります。protected
で宣言しているのは、元のメソッドも protected
で宣言されているからです。
KeyPressメソッドの実装
Keypressメソッドの実装コードを記述します。先ほどの宣言部分(20行目)付近を、マウスで右クリックします。コンテキストメニューに「カーソル位置のクラスを補完」があるので、これを実行します(または、キーボードの [Ctrl]+[Shift]+[C] を押してもよい)。以下のコードが自動的に作成されます。
procedure TCustomNumberEdit.KeyPress(var Key: Char);
begin
inherited;
end;
自動的に追加されたコードには inherited;
があります。これは上位クラスのメソッドを呼び出すためのもので、inherited KeyPress(Key);
と同じ意味になります。inherited
については今後説明します。今は、オーバーライドしたメソッドに必要なコードと覚えておいてください。
IsValidCharメソッドの作成
ユーザーが入力した文字の検証は、別のメソッドで行います。KeyPress
メソッドを簡潔にするためと、下位クラスでオーバーライドしたときに、入力できる文字を調整できるようにするためです。
作成するメソッドは IsValidChar
とします。以下のように、protected
で仮想メソッドとして定義します(20行目)。
protected
{ Protected 宣言 }
function IsValidChar(const Key: Char): Boolean; virtual;
IsValidCharメソッドの実装では Windows の API を使用します。以下のように、interface の uses に、Winapi.Windows
を追加します(6行目)。
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.StdCtrls, Winapi.Windows;
IsValidCharメソッドの実装は下記のとおりです。
function TCustomNumberEdit.IsValidChar(const Key: Char): Boolean;
var
IsControlKey: Boolean;
Settings: TFormatSettings;
begin
Settings := TFormatSettings.Create;
IsControlKey := (GetKeyState(VK_CONTROL) and $8000 <> 0);
Result := CharInSet(Key,
[Settings.DecimalSeparator, '+', '-', '0'..'9', #$0008, #$0009, #$000D]) or
IsControlKey;
end;
IsControlKey
変数は、[Ctrl]キーが押されている場合 True
になります(50行目)。[Ctrl]キーの状態をチェックしているのは次の理由からです。
たとえば [Ctrl]+[V] キーが押された場合、[V] は数字でないのでキーが無効になります。そのため、ショートカットキーが使用できないという事態になります。これを回避するため、[Ctrl]キーが押されていればショートカットとみなし、どのような文字でも受け入れるようにしています。
[Ctrl]キーが押されていないときの有効な文字は、52行目からの CharInSet
関数で確認しています。Key
引数の文字が、[ ]
内の配列に含まれていれば、戻り値は True
になります。
入力可能な文字は、小数点(Settings.DecimalSeparator
)、プラス記号(+
)、マイナス記号(-
)、0 から 9 の数字、バックスペース(#$0008
)、タブ(#$0009
)、リターンキー(#$000D
)です。
プラス記号とマイナス記号は、数値のプラスとマイナスを入れ替えるときに使用します。バックスペースは文字を削除するため、タブはコントロールのフォーカスを移動するために使用します。また、リターンキーは入力確定や、フォーカス移動などの処理に使えるようにするため、入力できるようにしておきます。
KeyPressメソッドの実装2
KeyPressメソッドの実装は、以下のようになります。
procedure TCustomNumberEdit.KeyPress(var Key: Char);
var
S: string;
Settings: TFormatSettings;
begin
if not IsValidChar(Key) then Key := #0;
Settings := TFormatSettings.Create;
case Key of
'+', '-':
begin
// 数値の先頭にマイナスを入れる(またはマイナスを消す)
S := Text;
if S <> '' then
begin
if S[1] = '-' then
S := Copy(S, 2, Length(S) - 1)
else if (S[1] <> '-') and (Key = '-') then
S := '-' + S;
end
else if Key = '-' then
S := '-';
Text := S;
Key := #0;
end;
else
if Key = Settings.DecimalSeparator then
begin
// 小数点を入力可能か
if Pos(Settings.DecimalSeparator, Text) >= 1 then Key := #0;
end;
end;
inherited;
end;
(62行目)
ユーザーが入力した文字は、Key
引数で受け取ります。Key
引数をIsValidChar
メソッドでチェックして、有効な文字かどうかを確認します。有効ではない(戻り値がFalse
)場合、Key
引数に空文字(#0
)を設定することで、文字入力をなかったことにできます。
(66-83行目)
特に処理が必要な文字を、case文で処理します。ここではプラス記号とマイナス記号の処理を行っています。
プラス記号が入力された場合、負の数値を正の数値に変更します。実際の処理は、先頭のマイナス記号を消去するだけです。
マイナス記号が入力された場合、正負を入れ替えます。正の数だったら先頭にマイナス記号を付けて負の数にします。負の数だったら、先頭のマイナス記号を消去して、正の数にします。
(85-90行目)
小数点の処理です。すでに小数点が入力されていたら、2個目の小数点を無効にしています。
(93行目)
先ほどの inherited です。上位クラスの KeyPress メソッドを実行しています。TCustomNumberEdit
の上位クラスとは、TCustomEdit
です。
TCustomNumberEditのKeyPressメソッドは、入力された文字をチェックして、必要のない文字を取り除いているだけです。有効な文字をText
プロパティに設定して、画面の表示を更新する処理は、上位の TCustomEdit
や、さらにその上位のクラスで実装されています。そのため、上位クラスのKeyPressメソッドを実行してやらないと、何を入力しても、編集ボックスの表示が変わらないといった不具合につながります。
上位クラスで実装されている処理を行うために、オーバーライドしたメソッドでは、inherited
を実行する必要があります。