月別アーカイブ: 2022年7月

Disable left and right arrow keys on StringGrid

「StringGrid上で左右の矢印キーを無効化」

Enterキーでのカーソル移動と違って、矢印キーの制御に大苦労(私だけカモだが)。
これが正しい方法かどうか、わからないが結果としてタイトルにある通り、
StringGrid上で左右の矢印キーの無効化に成功。これはその覚書。

0.左右の矢印キーを無効化したい理由
1.無効化する前に行った設定
2.無効化にチャレンジ
3.まとめ
4.お願いとお断り

0.左右の矢印キーを無効化したい理由

例えば、次のようなGUIで、テストの解答用紙をスキャンした画像から解答欄(1)のみをトリミングして採点したい場合、採点用のGridコントロールの列数は2列として、1列目に解答用紙の番号、2列目に採点結果を入力する仕様とすれば、最も使いやすいのではないかと考え、これを実現するために、Gridコントロールの現在必要ない列を「非表示」にする設定を行った。

画面右側に採点用のStringGridを配置している

ここまでは、全然、難しくなかった。

次に、StringGridにフォーカスがあるとき、Enterキーを押し下げるとカーソルが下のセルに移動するように設定。これも以前やったことがあったので、問題なく実現できた。

で、あとはEnterキーでコントロールを移動させるために、Form上のコンポーネントより先にFormがキーボードイベントを取得できるよう、FormCreateで以下を指定。

  KeyPreview:=True;

で、Enterキーの挙動を設定したのと同じFormKeyPressプロシージャで、

  if Ord(Key)=VK_RIGHT then
  begin
    if ActiveControl is TStringGrid then
    begin
      if TStringGrid(ActiveControl).EditorMode then
      begin
        Key:=#0;
        //Exit;
      end;
    end;
  end;

と、設定してあげれば・・・思った通りのGUIが完成・・・するはずだった。

・・・だったが、実際にコンパイルしてみると・・・

Enterキーを押し下げると、次の採点欄にフォーカスが移動(ここまでは予定通り)し、左右の矢印キーは無効化してあるので、押し下げても反応しないはず・・・が、採点欄1で右矢印キーを押し下げると、「カーソルがきえちゃった!」

で、左矢印キーを押すと、「カーソルが現れた!」

って、コトは。

非表示にしてある採点欄2へ、カーソルが移動している・・・

無効化されてない。

矢印キーは、無効化されてないよー

無駄だと思ったが、いちおう確認のため

  if Ord(Key)=VK_RIGHT then
  begin
    if ActiveControl is TStringGrid then
    begin
      if TStringGrid(ActiveControl).EditorMode then
      begin
        ShowMessage('VK_RIGHT');
        Key:=#0;
        //Exit;
      end;
    end;
  end;

右矢印キーの押し下げをフックできたらメッセージを表示するようにしてみたが、まったく反応なし。つまり、Enterキーの押し下げを検出するのと同じ方法では矢印キーの押し下げを検出できないことが判明(号泣)。なんだか、面白いことになってキタ。

1.無効化する前に行った設定

FormにStringGridを配置し、列数・行数は実行時に動的に指定する仕様でプログラミング。さらに、採点しやすいよう、Enterキーでのフォーカスの移動(下のセルへ)と、不要な列の非表示設定を次のように行った。

【StringGridの設定】

procedure TFormCollaboration.FormCreate(Sender: TObject);
var
  i:integer;
begin

  //以下StringGrid関係
  StringGrid1.DefaultDrawing := True;
  StringGrid1.DrawingStyle   := gdsThemed;
  StringGrid1.Options        := StringGrid1.Options + [goDrawFocusSelected];

  //フォーカスのあるセルを強調表示
  StringGrid1.Options:=StringGrid1.Options + [goDrawFocusSelected];
  //Clickでセル編集を可能にする-> [goEditing]をTrueに設定
  StringGrid1.Options:=StringGrid1.Options + [goEditing];
  //常に編集可能に設定
  StringGrid1.Options:=StringGrid1.Options + [goAlwaysShowEditor];
  //範囲選択を可能に設定
  StringGrid1.Options:=StringGrid1.Options + [goRangeSelect];
  //ドラッグ中グリッド内容のスクロールを「行う」に設定
  StringGrid1.Options:=StringGrid1.Options + [goThumbTracking];

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

end;

【IMEは使用不可に設定】

  type
    ・・・

  //Col毎のIMEの制御
  type
    _TGrid = class(TCustomGrid);

  private
    { Private 宣言 }
    ・・・

procedure TFormCollaboration.StringGrid1GetEditText(Sender: TObject; ACol,
  ARow: Integer; var Value: string);
begin
  //IMEの制御
  with TEdit(_TGrid(Sender).InplaceEditor) do
  begin
    ImeMode := imDisable;   //日本語入力OFFは imDisable
  end;
end;

【Enterキーでカーソルは下のセルへ移動】

procedure TFormCollaboration.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にすると同じ項目の次のレコードへ移動。
        ActiveControl.Perform(WM_KEYDOWN,VK_DOWN,0);
        Key:=#0;
      end;
    end else begin
      SelectNext(ActiveControl,True,True);
      Key:=#0;
    end;
  end;

end;

【列の非表示】※StringGridの初期化時と採点欄の切り替え時の両方に設定

  //列幅の自動調整(やるなら列の非表示設定の前に実行)
  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])+10;
      if MaxColWidth < TmpColWidth then
      begin
        MaxColWidth := TmpColWidth;
      end;
    end;
    StringGrid1.ColWidths[iCOL] := MaxColWidth;
  end;

  //必要な列のみ表示
  for i := 1 to StringGrid1.ColCount-1 do
  begin
    if i<>StrToInt(ComboBox1.Text) then
    begin
      StringGrid1.ColWidths[i]:=-StringGrid1.GridLineWidth;
      //StringGrid1.ColWidths[i]:=-1;
    end else begin
      StringGrid1.ColWidths[i]:=45;
    end;
  end;

2.無効化にチャレンジ

やりたいことはわかってるけど、その方法がわからない。特に今回の場合はまったくわからない。なぜ、右矢印キーの押し下げをキャッチしてくれないのか・・・

if Ord(Key)=VK_RIGHT then

理由はわからないが、とにかくFormKeyPress手続きの中に書く上の方法ではダメらしい。VK_RIGHTはここを素通りしてしまう。

Google先生が教えてくれる様々なヒントを読み漁る(ワタシに理解できない内容大変多し・心が折れそうになる)が、「コレだ!」と叫びたくなるような瞬間は訪れず。

しかし、神は私を見捨てなかった・・・。

「Delphi 矢印キー Ord(Key)」で検索すると、検索結果のTopに表示されたのは

Googleの検索結果より抜粋

運命的な出会いを感じつつ、リンクをクリック。すると・・・リンク先(FDELPHI Delphi users’ forum)の記事には、「KeyPreviewをTrueにするだけでは不十分です」との解説が!

記事へのリンク:[Q] フォームへ仮想キーを送ることでつまずいています。

「あったー!」

どこのどなたか存じませぬが、20年以上前にこの書き込みを行ってくださった方に心より深く御礼申し上げます(回答のみで、回答者ご自身のお名前の書き込みはありませんでした)。自らの知識が後の世のどこかで役に立つ日があることを信じて、回答を寄せられたそのお気持ちに到底及ばないと知りながらも、私も同じ気持ちでこれを書いております。

なぜ、到底及ばないか?

上のサイトの回答者様=理由も、仕組みも、わかった上で回答されている。

ワタシ=理由も、仕組みも、なんにもわかってないけど、これを書いている。

共通点:困ってるひとをたすけたい、その一心。

閑話休題。

上記サイトの回答によれば、次のようなイベントハンドラが別に必要であるとのこと。

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnMessage := AppMessage;
end;

procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  if Msg.message = WM_KEYDOWN then begin

  end;
end;

ここまで用意していただけたなら、あとは「左右の矢印キーだったら、入力を無効化する処理」を書けばイイだけ! それなら、ワタシにもできます。
さっそく、記述しました!!

procedure TFormCollaboration.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin

  //StringGrid上で押された右矢印キーを無効化する
  if Msg.message = WM_KEYDOWN then
  begin
    if ActiveControl is TStringGrid then
    begin
      if TStringGrid(ActiveControl).EditorMode then
      begin
        if Msg.wParam = VK_RIGHT then
        begin
          ShowMessage('捕まえた!');
          Key:=#0;
        end;
      end;
    end;
  end;
end;

うわーん。Keyが「未定義の識別子」エラーに!!! よく見たら・・・

procedure TFormCollaboration.AppMessage(var Msg: TMsg; var Handled: Boolean);

パラメーターにあるはずの(私は「ある」と思い込んでいた)・・・、我が愛しの

var Key: Char

が・・・ない。

ない。ない。ないよー。

どこにもない!!(超号泣)

( やっと、ここまでキタのにー )信じていたものに裏切られた哀しみが、千尋の海のような深まりをもって、胸いっぱいに広がって行くのを感じました。

(天の声:よく確認しないオマエがアホなだけだろー)

それでも(なんでかなー)と思いながら、FormCreate部分に書いた次の一文をよく確認してみると、

Application.OnMessage := AppMessage;

OnMessageをポイントして表示されるヒントを見れば、Keyパラメータが最初から入っていないことは明らかです。そうだったのか・・・

オレの人生は、これまで、いつも、いつだって、七転び八起き じゃねー

七転八倒の人生だったじゃねーか。

ここでもそれを確かめただけだぜー。どぉーってことないよ。

たったひとつだけ残された唯一の取柄、それは「あきらめない」こと☆

ここで私は気がつきます。Keyは未定義の識別子エラーになって使えないけど、

if Msg.wParam = VK_RIGHT then

もしかして、コレは動くんじゃね?

可及的速やかに「実行」

実行してみました

やったー☆ うごいたー☆☆

無効化はまだできませんが、とりあえず今までできなかった「右向きの矢印キーが押し下げられたこと」をキャッチできました。偉大なる前進です。

で、思ったことは、VK_RIGHTって確か仮想キーで、ほんとの姿はただの数字だったはずだよなってことです。試しにMsg.wParamの「wParam」をポイントしてみると表示されるヒントには、wParamは「NativeUInt型」って表示されています。

・・・ってことは、NativeUInt型がナンなのかはまったくわからない(こんな型は初めて見た)けれど、Intが付くから、おそらくInteger型の1種なのでしょう。Google先生に訊くと「プラットフォームに依存する符号なし整数型」と解説にあったので、自然数と言いながらきっと0も使えるのでしょう・・・と、自分に都合よく解釈し、Enterキーの押し下げをキャッチしたときは#0を代入していたから、同じだろうと考え wParam に 0 を投入!(正しくは代入です)

procedure TFormCollaboration.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin

  //StringGrid上で押された右矢印キーを無効化する
  if Msg.message = WM_KEYDOWN then
  begin
    if ActiveControl is TStringGrid then
    begin
      if TStringGrid(ActiveControl).EditorMode then
      begin
        if Msg.wParam=VK_RIGHT then
        begin
          //単に0を代入してwParamを書き換え
          Msg.wParam := 0;
        end;
      end;
    end;
  end;

左矢印キーの押し下げにも、忘れずに対応☆

  //StringGrid上で押された左矢印キーを無効化する
  if Msg.message = WM_KEYDOWN then
  begin
    if ActiveControl is TStringGrid then
    begin
      if TStringGrid(ActiveControl).EditorMode then
      begin
        if Msg.wParam=VK_LEFT then
        begin
          //単に0を代入してwParamを書き換え
          Msg.wParam := 0;
        end;
      end;
    end;
  end;

end;

翌日、左右の制御を合体させた方がイイと気づき、上の2つを合体させて・・・

procedure TFormCollaboration.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  //StringGrid上で押された左右の矢印キーを無効化する
  if Msg.message = WM_KEYDOWN then
  begin
    if ActiveControl is TStringGrid then
    begin
      if TStringGrid(ActiveControl).EditorMode then
      begin
        if Msg.wParam=VK_RIGHT then
        begin
          //単に0を代入してwParamを書き換え
          Msg.wParam:=0;
        end;
        if Msg.wParam=VK_LEFT then
        begin
          //単に0を代入してwParamを書き換え
          Msg.wParam:=0;
        end;
      end;
    end;
  end;
end;

で、実行!

StringGridにフォーカスして、Enterキーを押し下げ。
カーソルは期待通り、一つ下のセルへ移動。
次に祈りつつ、右矢印キーを押し下げ。
カーソルは消えません。点滅しています。
なんだか、うれしそうです(おまえだろ)。
期待が確信に変わるのを感じつつ、左矢印キーを押し下げ。
カーソルはやっぱり消えません。

ぎゃはは。為せば成る!

根本的に理解してないけど・・・ *(^_^)*♪

3.まとめ

(1)矢印キーは、Enterキーとは違う手続きで押し下げをキャッチする必要がある。
(2)FormCreate時にKeyPreview を True に設定。
(3)さらにFormCreate時に Application.OnMessage := AppMessage; を記述。
(4)Formのメンバーとして Shift+Ctrl+C でAppMessage 手続きを作成。
(5)AppMessage 手続きの中で矢印キーの押し下げをキャッチする。

4.お願いとお断り

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