InputQuery Where Only Numeric Values Can Be Entered

「数値のみ入力可能なInputQuery」

ユーザーからの数値入力を受け取って動作するプログラムを作成した。このような場合には、以前からInputQueryを使用してきた(自前のDialogを作成したこともあった)が、今回、「数値だけ入力可能」なInputQueryを作成してみた。これは、その覚書。

1.入力をチェックして数値のみの入力を実現
2.MyInputQueryを作る
3.まとめ
4.お願いとお断り

1.入力をチェックして数値のみの入力を実現

Delphiでユーザーからの入力を受け取るプログラムを作る時、戻り値がString型のInputBox関数や、Boolean型のInputQuery関数を使う。僕はこれまでユーザーが「どのボタンを押したのか?」がはっきりわかるInputQuery関数を多用してきた(対し、InputBox関数ではデフォルトで設定しておいた文字列が返る)。

procedure TForm1.Button1Click(Sender: TObject);
var
  Ret:string;
begin
  if InputQuery('InputQuery', '値を入力:', Ret) then
  begin
    //OKボタンがクリックされた時

  end else begin
    //キャンセルボタンがクリックされた時(ESCキーで閉じた場合もFalseになる)

  end;
end;
InputQuery実行時の画面

今回、多くの画像の印刷を実行するプログラムの中で、何ページめの画像を印刷するのか、ユーザーに指定してもらう必要があり、そこで、やはりInputQuery関数を利用した。

以前から、このようなシーンで「数値のみ入力可能」なInputQuery関数が欲しいなー、とずっと思ってきたんだけど、とりあえず、プログラムの完成を急ぎたくて、そのたびに、この問題は先送りにされてきた。 僕の中で、もう長いこと ずっと・・・。

Excel VBAで帳票印刷を行う時は、「ちゃっちゃ」っと次のようなFormを作成して

コード書いてないのにEnterキーでFocusまで移動する・・・

「ちゃっちゃ」っと次のコードを書いて・・・

Private Sub CommandButton1_Click()

    Dim PrintNo1 As Integer
    Dim PrintNo2 As Integer
    Dim i As Integer
    
    If UserForm1.TextBox1.Text = "" Then
        MsgBox ("開始番号を半角数字で入力してください。")
        TextBox1.SetFocus
        Exit Sub
    End If
    
    If UserForm1.TextBox2.Text = "" Then
        MsgBox ("終了番号を半角数字で入力してください。")
        TextBox2.SetFocus
        Exit Sub
    End If
    
    PrintNo1 = UserForm1.TextBox1.Text
    PrintNo2 = UserForm1.TextBox2.Text
    i = PrintNo1

    For i = PrintNo1 To PrintNo2
        Range("A2").Select
        ActiveCell.FormulaR1C1 = i
        Range("B6:AB38").Select
    
        ActiveSheet.PageSetup.PrintArea = "$B$6:$AB$38"
        ActiveWindow.SelectedSheets.PrintOut Copies:=1, Collate:=True
    Next i
    
    Range("A2").Select

End Sub

Private Sub CommandButton2_Click()
    'キャンセルボタンがクリックされた場合
    Unload UserForm1
    Exit Sub
End Sub

で、FormのTextBoxをクリックして・・・

TextBox1を選択

IME ModeプロパティをDisableに設定。

IMEは絶対利用できないようにDisableを指定

あとは実行!
サク・・・っと印刷して終わり。みたいな感じで、VBAならカンタンなんだけど、これをDelphiでやるとなると、全然できるんだけど、でも、ちょっと・・・めんどくさい。

今回も、「数値のみ入力可能なInputQuery」は(現時点で)実現できないから、とりあえず、ユーザーが入力した値をチェック(数値であるか・どうか)して対応してしまった・・・。

それが次のコード。

implementation

uses
  System.UITypes;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  Ret: string;
  intNum: integer;
  //全角 -> 半角に変換
  Chr: array [0..255] of char;
begin
  if InputQuery('印刷', 'ページを指定', Ret) then
  begin
    //OKボタンがクリックされた時
    //全角->半角変換
    //全角だった場合は半角数字に変換、すでに半角のものは半角のまま
    //半角にできない文字、たとえばひらがな等は変換されない
    Winapi.Windows.LCMapString(
      GetUserDefaultLCID(),
      LCMAP_HALFWIDTH,
      PChar(Ret),  //変換する文字列
      Length(Ret)+1,  //サイズ
      chr,  //変換結果
      Sizeof(chr)  //サイズ
      );
      Ret := Chr;
    //数値であるかチェック
    if TryStrToInt(Ret, intNum) then
    begin
      //数値である
      j := StrToInt(Ret);
      //本当に使える数値か、さらにチェック
      if (j = 0) or (j > 印刷ページの上限値) then
      begin
        MessageDlg('入力された値は印刷できない番号です。'+#13#10+
        '処理を中止します。', mtInformation, [mbOk] , 0);
        Exit;
      end else begin
        // j の値を使って印刷実行
        ・・・ 省略 ・・・
      end;
    end else begin
      MessageDlg('入力された値は数値ではありません!'+#13#10+
        '処理を中止します。', mtInformation, [mbOk] , 0);
      Exit;
    end;
  end;
end;

まぁ、確かにこれで目的は実現できてるから、イイっちゃイイんだけど・・・。
なんか、スマートじゃない・・・気がして。
あと出しジャンケンみたいで・・・

そこで、今回だけは逃げずに自分と戦うことにしました☆

2.MyInputQueryを作る

とりあえずの目標はVBAでやったように、InputQueryのテキストボックスのIMEモードをDisableに設定すること。

Google先生に訊いたら、次の情報を教えてくれた。

Vcl.StdCtrls.TCustomEdit.NumbersOnly

https://docwiki.embarcadero.com/Libraries/Sydney/ja/Vcl.StdCtrls.TCustomEdit.NumbersOnly

Delphi2009からNumbersOnlyプロパティがTEditに実装されたとのこと。で、さらに、そのTEditを内部に抱えているInputQueryの正体については、次のサイト他で情報をGet!

InputQueryについて

https://www.petitmonte.com/bbs/answers?question_id=4867

どうやらいちばんの問題解決方法は、InputQueryのソースコードをそのままコピペして(挙動不審にならないように)名前を変更し、それぞれの目的が実現できるようにコードを修正することのようだ。

上記Webサイト他に掲載されていたInputQueryのコードを読むと、自分でもなんとかなりそうだったので、さっそくやってみることにした。

で、書いたのが次のコード(ほとんどコピペですが・・・)。

//Formのメンバーにはしていません。
//名前は MyInputQuery に変更
function MyInputQuery(const ACaption, APrompt: string;
  var Value: string): Boolean;
var
  Form: TForm;
  Prompt: TLabel;
  Edit: TEdit;
  DialogUnits: TPoint;
  ButtonTop, ButtonWidth, ButtonHeight: Integer;

  function GetAveCharSize(Canvas: TCanvas): TPoint;
  var
    I: Integer;
    Buffer: array[0..51] of Char;
  begin
    for I := 0 to 25 do Buffer[I] := Chr(I + Ord('A'));
    for I := 0 to 25 do Buffer[I + 26] := Chr(I + Ord('a'));
    GetTextExtentPoint(Canvas.Handle, Buffer, 52, TSize(Result));
    Result.X := Result.X div 52;
  end;

begin
  Result := False;
  Form := TForm.Create(Application);
  with Form do begin
    try
      Canvas.Font := Font;
      DialogUnits := GetAveCharSize(Canvas);
      BorderStyle := bsDialog;
      Caption := ACaption;
      ClientWidth := MulDiv(180, DialogUnits.X, 4);
      Position := poScreenCenter;

      Prompt := TLabel.Create(Form);
      with Prompt do
      begin
        Parent := Form;
        Caption := APrompt;
        Left := MulDiv(8, DialogUnits.X, 4);
        Top := MulDiv(8, DialogUnits.Y, 8);
        Constraints.MaxWidth := MulDiv(164, DialogUnits.X, 4);
        WordWrap := True;
      end;
      Edit := TEdit.Create(Form);
      with Edit do
      begin
        Parent := Form;
        Left := Prompt.Left;
        Top := Prompt.Top + Prompt.Height + 5;
        Width := MulDiv(164, DialogUnits.X, 4);
        MaxLength := 255;
        Text := Value;
        SelectAll;

        //Password入力用にInputQueryを使用するための設定(Password Mask)
        //EditコントロールではPasswordCharに設定した文字が
        //入力した文字の代わりに表示される(デフォルトは'#0')
        //パスワードマスクするなら
        //PasswordChar:= '*';
        //これでマスクしなくなる('#0'として文字列化しないこと)
        PasswordChar := #0;

        //Delphi2009からTEditにNumbersOnlyプロパティ(数字だけを入力可能にする)が
        //実装されているそうなので、せっかくだからTrueにしてみた!
        //全角文字の「123」も「数値である」と判断してくれます・・・
        NumbersOnly := True;

        //IMEは使用不可(この1行がどうしても書きたかった!)
        ImeMode := imDisable;

        //文字位置
        //Alignment := taCenter;
        Alignment := taLeftJustify;
        //Alignment := taRightJustify;

      end;
      ButtonTop := Edit.Top + Edit.Height + 15;
      ButtonWidth := MulDiv(50, DialogUnits.X, 4);
      ButtonHeight := MulDiv(14, DialogUnits.Y, 8);
      with TButton.Create(Form) do
      begin
        Parent := Form;
        Caption := 'OK';
        ModalResult := mrOk;
        Default := True;
        SetBounds(MulDiv(38, DialogUnits.X, 4), ButtonTop, ButtonWidth,
          ButtonHeight);
      end;
      with TButton.Create(Form) do
      begin
        Parent := Form;
        Caption := 'キャンセル';
        ModalResult := mrCancel;
        Cancel := True;
        SetBounds(MulDiv(92, DialogUnits.X, 4), Edit.Top + Edit.Height + 15,
          ButtonWidth, ButtonHeight);
        Form.ClientHeight := Top + Height + 13;
      end;
      if ShowModal = mrOk then
      begin
        Value := Edit.Text;
        Result := True;
      end;
    finally
      Form.Free;
    end;
  end;
end;

このInputQueryをボタンクリックで呼び出します。

procedure TForm1.Button1Click(Sender: TObject);
var
  Ret:string;
begin
  if MyInputQuery('Dialog Caption', 'Please Enter the number:', Ret) then
  begin
    ShowMessage('Entered: '+ Ret);
  end else begin
    ShowMessage('False!');
  end;
end;

上のコードを実行すると・・・

InputQueryのTextBoxではIMEモードは変更できない
Form上のEditコントロールではIMEモードの切り替えが可能

これで、ずっと夢だった「数値のみ入力可能な」InputQueryが完成しました☆
さらに工夫すれば、入力できる数値の範囲を制限したりすることもできると思います。

また、次のWebサイトではマウスポインタの近くにInputQueryを表示させるという技も紹介されていました。

.InputQueryのポップアップ位置

https://www.petitmonte.com/bbs/answers?question_id=6520

3.まとめ

数値のみ入力可能なInputQueryを実現するには、InputQueryのソースコードをコピペして、名前を変更したInputQuery関数(例:MyInputQuery)を作成し、その中でやりたいことを書いていけばイイということ。

ここでは、「IMEModeの設定」を主に、それに加えて「パスワードマスク、NumbersOnlyプロパティ、文字位置(Alignment)」等の設定を行ってみた。

4.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。