I don’t want to press the enter key to confirm the input.

「入力確定のEnterキーは押したくない!」

TStringGridを使って何らかの入力作業を行う時、任意のあるキーを押したら直ちに、予め指定した内容をアクティブなセルに入力し(入力を確定)、次のセル(右 or 下)へフォーカスを移したいことがある。これは、そんな時のための備忘録。

1.Bキー押し下げでゼロを入力したい理由
2.StringGridを準備する
3.Bキー押し下げでゼロ入力を実装(その1)
4.任意の1文字+数字の入力を負の数に変換
5.Bキー押し下げでゼロ入力を実装(その2)
6.まとめ
7.お願いとお断り

1.Bキー押し下げでゼロを入力したい理由

手書き答案をスキャナーで読み込み、採点するプログラムを書いた。元の答案画像から設問ごとに解答欄をかき集めて一覧表示し、まとめて採点すれば効率よく採点できると思ったのだ(実際、試してみたら驚くほど速く採点できた!)。その手順を紹介。

ぱっと見て「よく出来てるなー」と思ったら、全員分採点記号と得点を一括入力
誤りの解答だけ採点記号を × にして、得点はゼロに変更

採点スタイルとして予定した(考えた)のは、「左手で入力作業、右手はマウス操作(解答欄のクリックと画像のスクロール)に専念する」というカタチ。

解答欄画像をクリックしたら、その座標から解答番号を計算し、採点欄のフォーカスが自動で移動するようプログラミング。(その方法は以下のリンク先を参照してください)

で、正の数値を入力したら、そのまま採点欄に、その数値が入力され、
Qとか、Sとか、何か文字を入力して確定したら、採点欄には0(ゼロ)が入り、さらに

オプション設定で「マイナス(ー)」記号に変換する文字を指定

上の設定であれば、aキーに続けて数字を入力して確定した場合は、採点欄に負の値が入力されるようにプログラミング。なぜaなのかというと、左手小指のホームポジションだからまず間違えずに(位置を確かめずに)押し下げ可能だと思ったから。

そもそも、なんで、こんな仕様(入力値が正負の数およびゼロ)にしたかというと、採点欄への数値入力と同時に、入力された数値に応じて、解答欄画像の方にも、採点記号と得点を(透過状態で)表示するプログラムにしたかったから。具体的な表示内容は次の通り。

(1)入力が正の数なら、解答欄画像の上に採点記号と得点を表示、
(2)入力が0(ゼロ)なら、解答欄画像の上に採点記号 × のみを表示
  (ゼロは〇:まるとまぎらわしいのでデフォルト設定では表示しない)、
(3)入力が負の数なら、解答欄画像の上に採点記号と部分点を表示。

当初、この採点補助プログラムでは、採点記号として〇と × しか利用できなかった(△とした場合に、それを見分ける良いフラグが用意できなかった)が、コピペしたプログラムコード中に残していた負の数は赤で表示するコードを見て、負の数を「部分点あり」のフラグとして利用できることに気づき、「部分点あり」の採点記号△も使えるように改良。

部分点を与える時は採点欄にマイナス記号に変換する文字と部分点になる数値を入力し、Enterキー押し下げ
負の数で「部分点あり」を表現(合計点は絶対値で計算すればイイ)

マウスは右手で操作する(左利きの方も?)ので、自ずと採点は左手で行うことに。

多くの場合、1問あたりの得点は5点未満だろうから、これらの数字キーはキーボードの左側にあって押しやすい。もし、数値でなく文字が入力された場合は、有無を言わさず0(ゼロ)に変換してしまえば、左手側にある1~5の数字キーの下には押しやすい文字キーがたくさんあるから、キーボード右側にあって、左手が届きにくい0(ゼロ)キーは押さなくてすむ。

あとは右手でマウスを操作し、解答欄画像を次々にクリックして、採点欄のフォーカスを切り替えて(=入力を確定して)行けば・・・

採点補助プログラムとして、十分使えるかなー?っと思ったんだけれど、

実際使ってみたら、入力後、次の解答欄画像をいちいちクリックして( or Enterキーを押し下げして)入力を確定 & 次の採点欄へフォーカスを移動させるのが、非常にめんどくさい。

せめて × の場合だけでも、採点欄に0(ゼロ)を入力した瞬間に、解答欄画像上に × を表示し、フォーカスが自動で次のセルへ移動するようにできないか?

そんな理由から、採点記号「 × 」は「ばってん」だから、BATTENで、Bキー押し下げ、即、0(ゼロ)を入力 & 確定、フォーカスは次のセルへ自動で移動するプログラムを書くことに決めました(Bキーも左手で押しやすい位置にあるのがうれしい!)。

2.StringGridを準備する

Bキー押し下げ、即、入力確定のプログラム自体は、前にStringGridで矢印キーの動作を制限したことがあったので、その時学んだテクニックを応用すれば、きっと書けると思ったので全然心配はなかったが、それを設定する対象のTStringGridは実に設定し甲斐のあるコントロールで、ある目的を実現(実装)しようとすると、そこに行きつくまでの工程が何段階も必要だったりする。

今回、この記事を書くのにあたり、いい機会だからStringGridの設定について(自分自身の勉強の復習の意味も込めて)まとめてみた。練習用に手間をかけずに作成したFormとコントロールは次の通り(Formに各VCLコントロールを置いただけ!)。

Form上に、StringGrid、Label、ComboBox、CheckBoxを各1個ずつ用意

で、FormCreate時の手続きは・・・

procedure TForm1.FormCreate(Sender: TObject);
begin

  //[Enter]でコントロールを移動させるために、Form上のコンポーネント
  //より先にキーボードイベントを取得する。
  KeyPreview := True;

  //描画処理は自前で行わずDelphiにおまかせ
  StringGrid1.DefaultDrawing := True;

  //Fixed(固定セル)のスタイル
  //現在のオペレーティングシステムのテーマを使用
  StringGrid1.DrawingStyle := gdsThemed;
  //標準のテーマの指定がないスタイル
  //StringGrid1.DrawingStyle := gdsClassic;
  //グラデーションのあるスタイル
  //StringGrid1.DrawingStyle := gdsGradient;

  //セルを強調表示
  StringGrid1.Options := StringGrid1.Options + [goDrawFocusSelected];

  //Clickでセル編集を可能にする-> [goEditing]をTrueに設定(方法は以下の通り)
  StringGrid1.Options := StringGrid1.Options + [goEditing];
  //常に編集可能に設定
  StringGrid1.Options := StringGrid1.Options + [goAlwaysShowEditor];

end;

KeyPreview := True の設定の他は、すべてStringGrid関係。僕がこのコントロールを使う時は、常に編集可能状態で起動するように設定することがほとんど。

続けてFormShow手続き。

procedure TForm1.FormShow(Sender: TObject);
var
  i : integer;
begin

  //行数と列数を適当に指定(Fixedセルを除いて10行10列あればテスト用途には十分)
  StringGrid1.RowCount := 11;
  StringGrid1.ColCount := 11;

  //FixedCols & FixedRows(固定列と固定行)を設定
  StringGrid1.FixedCols := 1;
  StringGrid1.FixedRows := 1;

  //フィールド名をセット(Rowに一括設定)
  //',Aの[,]に注意!-> セル[0,0]は空欄(フィールド名は入れない)
  //プログラムが長くなる時は['+]を使用してフィールド名を設定する
  StringGrid1.Rows[0].CommaText := ',A,B,C,D,E,F,'+
    'G,H,I,J';

  //FixedRows(固定行)に値をセット
  for i := 1 to 10 do
  begin
    StringGrid1.Rows[i].Append(IntToStr(i));
  end;

  //StringGrid1へフォーカスを移す。
  //下のようにまずフォーカスを移してからCol, Rowを指定。
  //でないとエラーになる。
  StringGrid1.SetFocus;
  StringGrid1.Col := 1;
  StringGrid1.Row := 1;
  StringGrid1.SetFocus;
  //カーソルが見えるようにする
  StringGrid1.EditorMode := True;

end;

さらにStringGrid1DrawCell手続きで、Fixed(固定)セルの表示方法と、入力された数値の右寄せ表示を指定。

implementation

uses
  Vcl.GraphUtil;

  //GraphUtilはFixedセルのセンタリング用に追加

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
var
  i : integer;
begin
  //Fixedセルをセンタリング
  with StringGrid1 do
  begin
    if (gdFixed in State) then
    begin
      //usesにGraphUtilを追加(Vcl.GraphUtilではないことに注意!)
      //->Vcl.GraphUtilとすると「未定義の識別子エラー」になる!
      //GraphUtil.GradientFillCanvas(Canvas, GradientStartColor,
      //  GradientEndColor, Rect,gdVertical);
      //Vcl.GraphUtilとusesした場合
      //これは未定義の識別子エラーにならない
      Vcl.GraphUtil.GradientFillCanvas(Canvas, GradientStartColor,
        GradientEndColor, Rect,gdVertical);
      //センタリング
      DrawText(Canvas.Handle, PChar(Cells[ACol, ARow]),
        -1, Rect, DT_CENTER OR DT_VCENTER OR DT_SINGLELINE);
    end;
  end;

  //セルの表示を制御
  if not (gdFixed in state) then
  begin
    if StringGrid1.Cells[ACol,ARow] <> '' then
    begin
      //数値であるかどうかをCheck
      if not TryStrToInt(StringGrid1.Cells[ACol,ARow],i) then Exit;
      {数値である場合}
      //背景色を白に設定
      StringGrid1.Canvas.Brush.Color := clWhite;
      //正負をチェック
      if StrToInt(StringGrid1.Cells[ACol,ARow]) < 0 then
      begin
        StringGrid1.Canvas.Font.Color := clRed;
      end else begin
        StringGrid1.Canvas.Font.Color := clBlack;
      end;
      //セルを塗りつぶす
      StringGrid1.Canvas.FillRect(Rect);
      //数値は中央寄せで表示
      {DrawText(StringGrid1.Canvas.Handle,
              PChar(StringGrid1.Cells[ACol,ARow]),
              //[+1]は数値描画位置の調整のため
              Length(StringGrid1.Cells[ACol,ARow])+1,Rect,
              DT_CENTER or DT_VCENTER or DT_SINGLELINE);}
      //数値は右寄せで表示
      DrawText(StringGrid1.Canvas.Handle,
              PChar(StringGrid1.Cells[ACol,ARow]),
              //[+1]は数値描画位置の調整のため
              Length(StringGrid1.Cells[ACol,ARow])+1,Rect,
              DT_RIGHT or DT_VCENTER or DT_SINGLELINE);
    end;
  end;
end;

ついでにIMEも設定(IME ONの列は任意指定)。まず、次のように宣言しておいて・・・

//Col毎のIMEの制御(制御内容はStringGrid1GetEditTextを参照)
type
  _TGrid = class(TCustomGrid);

var
  Form1: TForm1;

implementation

StringGrid1GetEditText手続きで、次のように設定。

procedure TForm1.StringGrid1GetEditText(Sender: TObject; ACol, ARow: Integer;
  var Value: string);
begin
  //IMEの制御
  with TEdit(_TGrid(Sender).InplaceEditor) do
  begin
    case ACol of  //最初のAColは「 0 」
      2: ImeMode := imHira; //日本語入力ON
    else
      //ImeMode := imClose;   //日本語入力OFF-> ×
      ImeMode := imDisable;   //日本語入力OFFは imDisable
    end;
  end;
end;

ここまでの設定で、実行時の画面は、こんな感じ。

某有名表計算ソフト風の画面が出現

Enterキーでフォーカスを移動するために、FormKeyPress手続きで、次のように設定。

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
  //[Enter]キーでコントロールを移動
  //StringGridは編集可能にFormCreateで設定しておく
  //->忘れるとセルの移動にEnter×2回必要!
  //この方法を使う時はKeyPreview:=True;をFormCreateで指定。
  if Ord(Key) = VK_RETURN then
  begin
    if ActiveControl is TStringGrid then
    begin
      if TStringGrid(ActiveControl).EditorMode then
      begin
        //VK_TABではカーソルがレコードの項目を右へ移動。
        //ActiveControl.Perform(WM_KEYDOWN,VK_TAB,0);
        //VK_DOWNにすると同じ項目の次のレコードへ移動。
        //if intStringGrid1ActiveRow < StringGrid1.RowCount-1 then
        if TargetRow < StringGrid1.RowCount-1 then
        begin
          ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
        end else begin
          ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
        end;
        Key := #0;
      end;
    end else begin
      SelectNext(ActiveControl,True,True);
      Key := #0;
    end;
  end;
end;

さらに、列幅を自動調整したい場合は・・・

procedure TForm1.CheckBox1Click(Sender: TObject);
var
  iCOL: Integer;
  iROW: Integer;
  MaxColWidth: Integer;
  TmpColWidth: Integer;
begin
  //DefaultColWidthを設定(これでCheck OFF時に元に戻る!)
  StringGrid1.DefaultColWidth:=64;
  //AutoAllColFit(全列幅の自動調整)
  if CheckBox1.Checked then
  begin
    for iCOL := 0 to StringGrid1.ColCount-1 do begin
      MaxColWidth := 0;
      for iROW := 0 to StringGrid1.RowCount-1 do
      begin
        //数字は列幅の調整用
        TmpColWidth := Canvas.TextWidth(StringGrid1.Cells[iCOL,iROW]) + 40;
        if MaxColWidth < TmpColWidth then
          MaxColWidth := TmpColWidth;
      end;
      StringGrid1.ColWidths[iCOL] := MaxColWidth;
    end;
  end;
end;

列幅自動調整実行時の画面は・・・(チェックOFFで、列幅は元に戻る)

列幅調整用の数値を調整して好みの幅に設定(上の画像では40を使用)

これでStringGridの準備が完了!

3.Bキー押し下げでゼロ入力を実装(その1)

※ 各セルに対して10以上の値の入力がないことが前提です!

この機能の実装にあたり、次のWebサイトにあった情報を参考にさせていただきました。質問者様と解答者様のご両名に対して、心から厚く御礼申し上げます。

@NIFTY FDELPHI Delphi Users’ Forum15番会議室「FAQ編纂委員会」に寄せられた「よくある質問の答え」

http://delfusa.main.jp/delfusafloor/archive/www.nifty.ne.jp_forum_fdelphi/faq/00075.htm

StringGrid 行移動の把握

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

Private宣言に、次のローカル変数とAppMessage手続きを追加。

  private
    { Private 宣言 }
    //入力=確定&フォーカスの移動用に追加
    //行・列位置を記憶する変数
    TargetRow:integer;
    TargetCol:integer;
    //ある(矢印他)キーが押されたことを知る
    procedure AppMessage(var Msg: TMsg; var Handled: Boolean);

Shift+Ctrl+C で AppMessage手続きを作成して、次の内容を設定。

※ usesに System.UITypes を追加するのを忘れないこと!(忘れるとBキーを意味するVKBが「未定義の識別子エラー」になる。

重要 次のコードでは、各セルに対して10以上の数値の入力は「ない」ものとしている。

implementation

uses
  Vcl.GraphUtil,
  System.UITypes;

  //GraphUtilはFixedセルのセンタリング用に追加
  //System.UITypesはキーコードでBキー(=VKB)を指定するために追加

{$R *.dfm}

procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  //任意のキーの押し下げをキャッチ
  if Msg.message = WM_KEYDOWN then
  begin
    //StringGridがアクティブだったら
    if ActiveControl is TStringGrid then
    begin
      //StringGridが編集可能だったら
      if TStringGrid(ActiveControl).EditorMode then
      begin
        //Bキー or 0キー押し下げでゼロを入力(入力値は10未満であることが前提)
        if (Msg.wParam=VKB) or (Msg.wParam=VK0) then
        begin
          //keybd_event(VK_TAB,0,0,0);
          //VK_TABではカーソルがレコードの項目を右へ移動。
          //ActiveControl.Perform(WM_KEYDOWN,VK_TAB,0);
          //VK_DOWNにすると同じ項目の次のレコードへ移動。
          if TargetRow < StringGrid1.RowCount-1 then
          begin
            //アクティブなセルが最終行でない場合はフォーカスは下へ移動
            StringGrid1.Cells[TargetCol, TargetRow]:='0';
            ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
          end else begin
            //最終行ならフォーカスは上へ移動
            StringGrid1.Cells[TargetCol, TargetRow]:='0';
            ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
          end;
          //Msg.wParam:=#0; //エラーになる
          Msg.wParam:=0;
        end;
      end;
    end;
  end;
end;

FormCreate時に、AppMessageを有効にする。これを忘れると動かない!

procedure TForm1.FormCreate(Sender: TObject);
begin

  ・・・ 省略(StringGridその他の初期設定) ・・・

  //入力=確定&フォーカスの移動用に追加
  //StringGridの初期位置の設定
  TargetRow := 1;
  TargetCol := 1;
  //AppMessageを有効にする
  Application.OnMessage := AppMessage;

end;

AppMessage手続きの引数にはACol, ARowがないから、その代わりにStringGrid1SelectCell手続きの最後で、行列位置を変数に取得。

procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
  var CanSelect: Boolean);
begin
  //入力=確定&フォーカスの移動用に追加
  //セルを選んだときに行位置を記憶
  TargetRow := ARow;
  //セルを選んだときに列位置を記憶
  TargetCol := ACol;
end;

実行時の様子は・・・

Bキーもしくは0キーの入力と同時にフォーカスは下のセルに移動する

4.任意の1文字+数字の入力を負の数に変換

Formに用意したLabel1のCaptionプロパティには「マイナス記号に置換する文字:」を設定し、ComboBox1のTextプロパティに「a」を設定。

FormCreate手続きの最後で、マイナス記号に置換する文字の選択肢を準備。

procedure TForm1.FormCreate(Sender: TObject);
begin

  ・・・ 省略 ・・・

  //入力=確定&フォーカスの移動用に追加
  //StringGridの初期位置の設定
  TargetRow := 1;
  TargetCol := 1;
  //AppMessageを有効にする
  Application.OnMessage := AppMessage;

  //マイナス記号に変換する文字の選択肢
  ComboBox1.Items.Add('q');
  ComboBox1.Items.Add('a');
  ComboBox1.Items.Add('z');

end;

StringGrid1DrawCell手続きに、次の赤字の部分を追加。
(1列目であったら、文字の入力はすべてゼロに変換する処理も追加している)

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
var
  i: integer;
  str1, str2: string;
begin

  ・・・ 省略 ・・・

  //セルの表示を制御(中央寄せ・負の数は赤で表示)
  if not (gdFixed in state) then
  begin
    if StringGrid1.Cells[ACol,ARow] <> '' then
    begin

      //文字数が2文字なら実行
      if Length(WideString(StringGrid1.Cells[ACol,ARow])) = 2 then
      begin
        //指定文字が入力されたら'-'に変換
        str1 := LowerCase(Copy(StringGrid1.Cells[ACol,ARow],1,1));
        str2 := Copy(StringGrid1.Cells[ACol,ARow],2,1);
        if str1 = LowerCase(ComboBox1.Text) then
        begin
          StringGrid1.Cells[ACol,ARow] := '-'+str2;
        end;
      end;

      if ACol = 1 then
      begin
        //「文字」はすべて'0'に変換
        if not TryStrToInt(StringGrid1.Cells[ACol,ARow], i) then
        begin
          StringGrid1.Cells[ACol,ARow] := '0';
        end;
      end;

  ・・・ 省略 ・・・

実行時の様子は・・・

a2と入力してEnterキー押し下げで確定
無事、目的を達成

左手だけで、視線をキーボードに落とすことなく、負の数も簡単に入力できるようになった☆

ただし、上の手続きでは、StringGridのセルへの入力が3桁であった場合に対応できない。こと採点に関しては、部分点が2桁の数値になることは、多分アリエナイから、採点補助プログラム用のアルゴリズムとしての利用に限れば、上の手続きでも、まず問題は起きないと思うが・・・もし、どうしても3桁以上の入力値に対応させたいなら、コードを次のように変更すればOK!

//StringReplaceuses関数を使用するので uses節に System.SysUtils を追加
uses
  System.SysUtils
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
var
  i: integer;
  str1, str2: string;
begin

  ・・・ 省略 ・・・

  //セルの表示を制御(中央寄せ・負の数は赤で表示)
  if not (gdFixed in state) then
  begin
    if StringGrid1.Cells[ACol,ARow] <> '' then
    begin

      //文字数が2文字なら実行 -> コメント化
      {if Length(WideString(StringGrid1.Cells[ACol,ARow])) = 2 then
      begin
        //指定文字が入力されたら'-'に変換
        str1 := LowerCase(Copy(StringGrid1.Cells[ACol,ARow],1,1));
        str2 := Copy(StringGrid1.Cells[ACol,ARow],2,1);
        if str1 = LowerCase(ComboBox1.Text) then
        begin
          StringGrid1.Cells[ACol,ARow] := '-' + str2;
        end;
      end;}

      //文字数が2文字以上なら実行
      if Length(WideString(StringGrid1.Cells[ACol,ARow])) >= 2 then
      begin
        //指定文字が入力されたら'-'に変換
        str1 := LowerCase(Copy(StringGrid1.Cells[ACol,ARow],1,1));
        //2桁以上の入力値に対応
        str2 := StringReplace(
          LowerCase(StringGrid1.Cells[ACol,ARow]), 
          str1, '', [rfReplaceAll, rfIgnoreCase]);
        if str1=LowerCase(ComboBox1.Text) then
        begin
          StringGrid1.Cells[ACol,ARow] := '-'+str2;
        end;
      end;

      if ACol = 1 then
      begin
        //「文字」はすべて'0'に変換
        if not TryStrToInt(StringGrid1.Cells[ACol,ARow], i) then
        begin
          StringGrid1.Cells[ACol,ARow] := '0';
        end;
      end;

  ・・・ 省略 ・・・
適当な値を入力してEnterキーを押し下げて確定
採点プログラムとしての実用性は感じられないが・・・プログラム的には目的を達成

5.Bキー押し下げでゼロ入力を実装(その2)

※ 各セルに対して10以上の値の入力がある場合

各セルに対して10以上の値の入力がある場合は、入力された0(ゼロ)が不正解の0(ゼロ)なのか、10の2桁目の0(ゼロ)なのか、判定する工夫が必要になるが、良い判定方法が思いつかなかった。

そこで思い切って問題を単純化し、「高速入力モード」を作成して、それが ON の場合は入力値を0-9に限定し、ユーザーがそのことを理解した上で操作できるように工夫してみた。もし、各セルに対して10以上の値の入力がある場合は、「高速入力モード」は OFF で使用して貰い、Bキーが押された場合のみ、0(ゼロ)に変換して入力確定 ⇨ フォーカスを移動することにして、数字キーの0(ゼロ)の入力に対しては、直ちに入力の確定としないことにした。

あと、ついでだから、「高速入力モード」の名に恥じないよう、それが ON の場合は、0-9の数字キー押し下げで、直ちに入力確定、次のセルへフォーカスが移動する処理も追加してみた。以下、その実装。

CheckBox2を追加し、Captionを設定
procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
var
  str1:string;
begin
  //任意のキーの押し下げをキャッチ
  if Msg.message = WM_KEYDOWN then
  begin
    //StringGridがアクティブだったら
    if ActiveControl is TStringGrid then
    begin
      //StringGridが編集可能だったら
      if TStringGrid(ActiveControl).EditorMode then
      begin

        //高速入力使用の有無で処理を切り替え
        if not CheckBox2.Checked then
        begin

          //高速入力を使用しない場合の処理
          //Bキー押し下げでゼロを入力
          //0キー押し下げは無視
          if (Msg.wParam=VKB) then
          begin
            //keybd_event(VK_TAB,0,0,0);
            //VK_TABではカーソルがレコードの項目を右へ移動。
            //ActiveControl.Perform(WM_KEYDOWN,VK_TAB,0);
            //VK_DOWNにすると同じ項目の次のレコードへ移動。
            if TargetRow < StringGrid1.RowCount-1 then
            begin
              //下のセルへ移動
              StringGrid1.Cells[TargetCol, TargetRow]:='0';
              ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
            end else begin
              //上のセルへ移動
              StringGrid1.Cells[TargetCol, TargetRow]:='0';
              ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
            end;
            //Msg.wParam:=#0; //エラーになる
            Msg.wParam:=0;
          end;

        end else begin

          //高速入力を使用する場合の処理
          //Bキー押し下げでゼロを入力
          //0キー押し下げにも対応
          if (Msg.wParam=VKB) or (Msg.wParam=VK0) then
          begin
            //keybd_event(VK_TAB,0,0,0);
            //VK_TABではカーソルがレコードの項目を右へ移動。
            //ActiveControl.Perform(WM_KEYDOWN,VK_TAB,0);
            //VK_DOWNにすると同じ項目の次のレコードへ移動。
            if TargetRow < StringGrid1.RowCount-1 then
            begin
              //下のセルへ移動
              StringGrid1.Cells[TargetCol, TargetRow]:='0';
              ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
            end else begin
              //上のセルへ移動
              StringGrid1.Cells[TargetCol, TargetRow]:='0';
              ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
            end;
            //Msg.wParam := #0; //エラーになる
            Msg.wParam := 0;
          end;

          //1-9の入力があった場合
          if StringGrid1.Cells[TargetCol, TargetRow] <> '' then
          begin
            str1:=Copy(StringGrid1.Cells[TargetCol, TargetRow],1,1);
          end else begin
            str1 := '';
          end;

          //任意の1文字+数字の入力を負の数に変換する処理用に追加
          if (str1 <> '-') and (str1 <> ComboBox1.Text) then
          begin
            if (Msg.wParam = VK1) then
            begin
              //keybd_event(VK_TAB,0,0,0);
              //VK_TABではカーソルがレコードの項目を右へ移動。
              //ActiveControl.Perform(WM_KEYDOWN,VK_TAB,0);
              //VK_DOWNにすると同じ項目の次のレコードへ移動。
              //if intStringGrid1ActiveRow < StringGrid1.RowCount-1 then
              if TargetRow < StringGrid1.RowCount-1 then
              begin
                //下のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '1';
                ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
              end else begin
                //上のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '1';
                ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
              end;
              //Msg.wParam := #0; //エラーになる
              Msg.wParam := 0;
            end;

            if (Msg.wParam = VK2) then
            begin
              if TargetRow < StringGrid1.RowCount-1 then
              begin
                //下のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '2';
                ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
              end else begin
                //上のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '2';
                ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
              end;
              Msg.wParam := 0;
            end;

            if (Msg.wParam = VK3) then
            begin
              if TargetRow < StringGrid1.RowCount-1 then
              begin
                //下のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '3';
                ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
              end else begin
                //上のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '3';
                ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
              end;
              Msg.wParam := 0;
            end;

            if (Msg.wParam = VK4) then
            begin
              if TargetRow < StringGrid1.RowCount-1 then
              begin
                //下のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '4';
                ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
              end else begin
                //上のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '4';
                ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
              end;
              Msg.wParam := 0;
            end;

            if (Msg.wParam = VK5) then
            begin
              if TargetRow < StringGrid1.RowCount-1 then
              begin
                //下のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '5';
                ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
              end else begin
                //上のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '5';
                ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
              end;
              Msg.wParam := 0;
            end;

            if (Msg.wParam = VK6) then
            begin
              if TargetRow < StringGrid1.RowCount-1 then
              begin
                //下のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '6';
                ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
              end else begin
                //上のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '6';
                ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
              end;
              Msg.wParam := 0;
            end;

            if (Msg.wParam = VK7) then
            begin
              if TargetRow < StringGrid1.RowCount-1 then
              begin
                //下のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '7';
                ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
              end else begin
                //上のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '7';
                ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
              end;
              Msg.wParam := 0;
            end;

            if (Msg.wParam = VK8) then
            begin
              if TargetRow < StringGrid1.RowCount-1 then
              begin
                //下のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '8';
                ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
              end else begin
                //上のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '8';
                ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
              end;
              Msg.wParam := 0;
            end;

            if (Msg.wParam = VK9) then
            begin
              if TargetRow < StringGrid1.RowCount-1 then
              begin
                //下のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '9';
                ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
              end else begin
                //上のセルに移動
                StringGrid1.Cells[TargetCol, TargetRow] := '9';
                ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
              end;
              Msg.wParam := 0;
            end;

          end;
        end;
      end;
    end;
  end;
end;

6.まとめ

重要 各セルへの入力値が10未満であることが前提のコードです!

Bキーを押すだけでStringGridのアクティブなセルにゼロを入力し、フォーカスを次のセルへ移動するプログラムで、必要な変数と手続きは次の通り。

各セルへの入力値が10以上の場合、「まとめ」のコードは期待通りに動作しません。
10以上の入力値にも対応させたい場合は、「5.各セルに対して10以上の値の入力がある場合」が参考になるかもしれません。

  private
    { Private 宣言 }
    //行・列位置を記憶する変数
    TargetRow:integer;
    TargetCol:integer;

    //ある(矢印他)キーが押されたことを知る
    procedure AppMessage(var Msg: TMsg; var Handled: Boolean);

//Col毎のIMEの制御(制御内容はStringGrid1GetEditTextを参照)
type
  _TGrid = class(TCustomGrid);

var
  Form1: TForm1;

implementation

uses
  Vcl.GraphUtil,
  System.UITypes;

  //GraphUtilはFixedセルのセンタリング用に追加
  //System.UITypesはキーコードでBキー(=VKB)を指定するために追加

{$R *.dfm}

procedure TFormCollaboration.FormCreate(Sender: TObject);
begin
  //StringGridの初期位置の設定
  TargetRow := 1;
  TargetCol := 1;
    //AppMessageを有効にする <- 忘れないこと!
  Application.OnMessage := AppMessage;
  //[Enter]でコントロールを移動させるために、Form上のコンポーネント
  //より先にFormがキーボードイベントを取得する。
  KeyPreview := True;
end;

procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
  var CanSelect: Boolean);
begin
  //入力=確定&フォーカスの移動用に追加
  //セルを選んだときに行位置を記憶
  TargetRow := ARow;
  //セルを選んだときに列位置を記憶
  TargetCol := ACol;
end;

procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  //任意のキーの押し下げをキャッチ
  if Msg.message = WM_KEYDOWN then
  begin
    //StringGridがアクティブだったら
    if ActiveControl is TStringGrid then
    begin
      //StringGridが編集可能だったら
      if TStringGrid(ActiveControl).EditorMode then
      begin
        //Bキー or 0キー押し下げでゼロを入力(入力値は10未満であることが前提)
        if (Msg.wParam=VKB) or (Msg.wParam=VK0) then
        begin
          //keybd_event(VK_TAB,0,0,0);
          //VK_TABではカーソルがレコードの項目を右へ移動。
          //ActiveControl.Perform(WM_KEYDOWN,VK_TAB,0);
          //VK_DOWNにすると同じ項目の次のレコードへ移動。
          if TargetRow < StringGrid1.RowCount-1 then
          begin
            //アクティブなセルが最終行でない場合はフォーカスは下へ移動
            StringGrid1.Cells[TargetCol, TargetRow] := '0';
            ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
          end else begin
            //最終行ならフォーカスは上へ移動
            StringGrid1.Cells[TargetCol, TargetRow] := '0';
            ActiveControl.Perform(WM_KEYDOWN,VK_UP,0);
          end;
          //Msg.wParam := #0; //エラーになる
          Msg.wParam := 0;
        end;
      end;
    end;
  end;
end;

7.お願いとお断り

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