Migaro. 技術Tips

                       

ミガロ. 製品の技術情報
IBMiの活用に役立つ情報を掲載!


【Delphi】TStringGridのセル移動をカスタマイズ

TStringGridコンポーネントはTDBGridと異なり、
データ表示のロジックが必要ですが、その分自由にカスタマイズすることができます。
ただデフォルトでは、Enterキーを押しても同じセル内にとどまるため
マウスクリックや矢印キーで移動しなければならず、データを連続で入力する場合には不便です。

そこで今回は、Enterキーで右隣りのセルに移動するテクニックをご紹介します。

 
TStringGridでは、Colプロパティは現在位置づけのあるセルの列を指しますので、
右隣りであれば、Colを+1することで移動します。
(行移動の場合はRowを+1することで1つ下の行に移動します。)

注意する点として、列数(ColCountプロパティ)を超えないようにしなければなりません。
また今回は、最終列であれば次の行の先頭セルに移動するようにしてみましょう。
行を移動する場合も、行数(RowCount)を超えないようにします。

Enterキーの押下はTStringGridのOnKeyDownイベントで判断できるため、
このイベント内に次のように記述します。

procedure TForm1.StringGrid1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  // Enterキー
  if (Key = VK_RETURN) then
  begin
    if StringGrid1.Col < StringGrid1.ColCount-1 then
    begin
      // 現在列<最終列なら 次の列へ移動
      StringGrid1.Col := StringGrid1.Col + 1
    end
    else
    begin
      if StringGrid1.Row < StringGrid1.RowCount-1 then
      begin
        // 最終行でない最終列なら次の行の先頭へ
        StringGrid1.Col := 1;
        StringGrid1.Row := StringGrid1.Row + 1;
      end
      else
      begin //【※】
        // 最終行の最終列だった場合は先頭行の先頭列へ戻る
        StringGrid1.Col := 1;
        StringGrid1.Row := 1;
      end;
    end;
  end;
end;

 

上記のロジックでは、最終行の最終列のセルの場合に、
先頭行の先頭列のセルに戻るようにしていますが、新しく行を追加するということも可能です。
RowCountプロパティを増やすことで行数は増えますので、次のようになります。
(先ほどのロジックの【※】部分を変更します。元のロジックは今回コメントアウトにしています。)

      else
      begin //【※】
//        // 最終行の最終列だった場合は先頭行の先頭列へ戻る
//        StringGrid1.Col := 1;
//        StringGrid1.Row := 1;

        // 最終行なら1行増やし、その先頭列へ移動
        StringGrid1.RowCount := StringGrid1.RowCount + 1;
        StringGrid1.Row := StringGrid1.Row + 1;
        StringGrid1.Col := 1;
      end;

 

また行追加でも先頭行へ移動でもなく、次のコンポーネントに移動させたい場合は、
先ほどのロジックの【※】部分を以下のように変更します。

      else
      begin //【※】
//        // 最終行の最終列だった場合は先頭行の先頭列へ戻る
//        StringGrid1.Col := 1;
//        StringGrid1.Row := 1;

        // 最終行の最終列だった場合は次のコンポーネントへ
        SelectNext(StringGrid1, True, True);
      end;

 

ここまで実装したところで、
『こういう動きをできればいいのに』という欲が2点出てくるかと思います。

① EnterだけではなくTabキーでも同じような動きをさせたい
Shift+Enterを押したときは逆方向にフォーカス移動させたい

それぞれ解説していきます。

 

① EnterだけではなくTabキーでも同じような動きをさせたい

デフォルト状態では、
TStringGridでTabキーを押すと次のコンポーネントにフォーカス遷移します。
Shift+Tabキーを押すと前のコンポーネントにフォーカス遷移します。
またOnKeyDownやOnKeyPressイベントを拾ってくれません

この状況を改善するためには、
Optionsプロパティの「goTabs」サブプロパティをTrueに設定します。

すると、TStringGridでTabキーを押すと次のセルに移動するようになります。
Shift+Tabキーを押すと前のセルに移動します。
また最終行の最終列だった場合は先頭行の先頭列へ戻ります。
(Shift+Tabキーで先頭行の先頭列だった場合は最終行の最終列へ戻ります。)
また、OnKeyDownやOnKeyPressイベントを拾うようになります。

前項のロジックの【※】部分で最終行の最終列だった場合の処理を
「先頭行の先頭列へ戻る」以外にしている場合は、
OnKeyDownイベントの最初に以下のロジックを追加することで、
Tabキーが押されたらEnterとして扱わせることができます。

procedure TForm1.StringGrid1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  // Tabキーの場合はEnterキーに置換する
  if (Key = VK_TAB) then
  begin
    Key := VK_RETURN;
  end;

  // Enterキー
  if (Key = VK_RETURN) then
  begin
    // ~以下略~

 

② Shift+Enterを押したときは逆方向にフォーカス移動させたい

ここまでご紹介してきたロジックではShiftキーを考慮していないため、
Shiftを押しながらEnter(Tab)を押しても通常のEnterと同じ挙動をします。
(上記①の「VK_TABをVK_RETURNに置換」していない場合のTabキーを除く)

前項のロジックでは遷移先のセル番地を直接指定しているため、
Shiftで戻したい場合は地道にこれと逆の処理でセル番地を指定する必要があります。

これを実装していくと、最終的に以下のようなロジックとなります。
(コメントにしている箇所は、実際の挙動をどうするかによって切り替えてください。)

procedure TForm1.StringGrid1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  // Tabキーの場合はEnterキーに置換する
  if (Key = VK_TAB) then
  begin
    Key := VK_RETURN;
  end;

  // Enterキー
  if (Key = VK_RETURN) then
  begin
    // Shiftが押されていない場合(EnterまたはTabのみ)
    if not(ssShift in Shift) then
    begin
      if StringGrid1.Col < StringGrid1.ColCount-1 then
      begin
        // 現在列<最終列なら 次の列へ移動
        StringGrid1.Col := StringGrid1.Col + 1
      end
      else
      begin
        if StringGrid1.Row < StringGrid1.RowCount-1 then
        begin
          // 最終行でない最終列なら次の行の先頭へ
          StringGrid1.Col := 1;
          StringGrid1.Row := StringGrid1.Row + 1;
        end
        else
        begin
          // 最終行の最終列だった場合は先頭行の先頭列へ戻る
          StringGrid1.Col := 1;
          StringGrid1.Row := 1;

//          // 最終行なら1行増やし、その先頭列へ移動
//          StringGrid1.RowCount := StringGrid1.RowCount + 1;
//          StringGrid1.Row := StringGrid1.Row + 1;
//          StringGrid1.Col := 1;

//          // 最終行の最終列だった場合は次のコンポーネントへ
//          SelectNext(StringGrid1, True, True);
        end;
      end;
    end

    // Shiftが押されている場合
    else
    begin
      if StringGrid1.Col > 1 then
      begin
        // 現在列>先頭列なら 前の列へ移動
        StringGrid1.Col := StringGrid1.Col - 1
      end
      else
      begin
        if StringGrid1.Row > 1 then
        begin
          // 先頭行でない先頭列なら前の行の最終列へ
          StringGrid1.Col := StringGrid1.ColCount-1;
          StringGrid1.Row := StringGrid1.Row - 1;
        end
        else
        begin
          // 先頭行の先頭列だった場合は最終行の最終列へ戻る
          StringGrid1.Col := StringGrid1.ColCount-1;
          StringGrid1.Row := StringGrid1.RowCount-1;

//          // 先頭行の先頭列だった場合は前のコンポーネントへ
//          SelectNext(StringGrid1, False, True);
        end;
      end;
    end;
  end;
end;

 

TStringGridはプログラミング次第で柔軟にカスタマイズすることができるコンポーネントです。
工夫の一例として、是非ご活用ください。

※今回のロジックは、対象TStringGridにおける
 固定列・固定行(FixedCols・FixedRows)がいずれもの前提で作成しています。
 またOptionsの行全体選択(goRowSelect)や複数セル選択(goRangeSelect)はFalseの前提です。

 

 

(ミガロ.情報マガジン「MIGARO News!!」Vol.211 2018年6月号より加筆修正)