Vertical alignment of Grid control

「Gridコントロールの縦方向のアライメントを設定したい」

手書き答案を採点するプログラムで、「答案(の各解答欄)画像の高さ」と「得点を入力するStringGridのセルの高さ」が同じになるように設定したら、編集モード時に、データがセルの左上に表示されるのが何だか気になった(実用上は問題ない。あくまでも気分の問題)。編集してない時は、データはセルの中央に表示されてるので、編集中も垂直方向はセルの中央のまま、水平方向のみ左へ移動する形でデータを表示したい・・・と考えた。

セルの水平方向のアライメントはプロパティで設定できることは知ってたが、調べてみると、縦方向のアライメントは標準のStringGridでは設定できない(?)ようだ。なるべくなら、新しくコンポーネントをインストールしてこれを実現することは避けたい(PCを新しくした場合や、Delphi自体のバージョンアップ等の際に、再セットアップが必要なコンポーネントはなるべく少ない方がいい)ので、何とかならないかなーと思って調べてみた。そしたら、Web上に諸先輩が公開してくださっている数々のお知恵にすがりつきまくることで、案外、カンタンに、何とかなっちゃったというお話。

ここで利用させていただいた知恵のすべてを、自分で最初から作るとしたら、きっと途中で挫折するだろうし、もし、挫折しなくても、完成までには、「とほー」もない時間が必要なことだけは間違いありません。思い立ってわずか1時間で希望のプログラムができたのは、参照させていただいた資料を公開してくださっている皆様のおかげです。心から厚く御礼申し上げます。ほんとうにありがとうございました。

1.これをなんとかしたかった!
2.コンポーネントをインストールせずに使う方法
3.画像のスクロールとGridコントロールの連動
4.まとめ
5.お願いとお断り

1.これをなんとかしたかった!

作成した手書き答案採点プログラムの実行時の画像は、以下の通り。

答案画像から設問毎に解答画像をかきあつめて、受験者全員分をまとめて表示している

答案用紙画像から切り出した各解答欄の画像の高さと、StringGridのセルの高さを同じにした方が採点しやすいだろうと考えたので、次のコードでこれを設定。

  StringGrid1.RowHeights[0]:=24;
  for i := 1 to StringGrid1.RowCount-1 do
  begin
    //SrcRectは解答欄画像の矩形
    StringGrid1.RowHeights[i] := SrcRect.Height;;
  end;

で、なんとかしたい部分が、こちら。

編集中の採点欄のデータがセルの左上に表示されている。縦方向のアライメントも真ん中にしたい!

調べた限り・・・のことなので、もしかしたら間違ってるかもしれないが、標準のStringGridでは縦方向のアライメント設定はできないようだ・・・(もしかしたら、できるのかな?)。半分くらい、あきらめモードで(やっぱり、無理かなー? まぁいいかー)って思いつつもあきらめきれず、Web上の多くの資料に目を通していると、Mr.XRAYさんのWebサイトの「055_ドロップダウンリストを実装した TStringGrid コンポーネント」というページの中に、「06_インプレイスエディタの縦方向のアライメントと左右のインデント」という、まさに実現したいこと、そのものずばりの記事を発見。

055_ドロップダウンリストを実装した TStringGrid コンポーネント

http://mrxray.on.coocan.jp/Delphi/plSamples/055_TplDropStringGrid.htm

上記ページで、Mr.XRAYさんがドロップダウンリストの機能付きのTStringGrid コンポーネントとして公開してくださっているplDropStringGrid.pasには、インプレイスエディタ関係のプロパティとイベント類が追加されており、これをインストールすれば、StringGridで編集モード時に起動するインプレイスエディタの縦方向のアライメントが設定できるとのこと。

これで「夢見たことは実現可能であることがわかった」が、もし、できることなら、コンポーネントをインストールせずに使えないか? とさらに欲張りなことを考えてしまった・・・。理由はたった一つ。StringGridのセルの高さを変えるようなプログラムは、今後、たぶん書かないんじゃないかなーって、思ったから。

・・・ということで、今度は「コンポーネントをインストールせずに使う方法」を探してみた(探しつつ、前に見たことがあるような気がした)。

2.コンポーネントをインストールせずに使う方法

こちらも、そのものずばりの方法が次のWebサイトに公開されていました。作者の方に心から感謝申し上げます。

コンポーネントをインストールせずに使う方法

http://delfusa.main.jp/delfusafloor/technic/technic/024_ChangeComponent.html

上記Webサイトにあった情報をもとに、夢を実現。

まず、上記Mr.XRAYさんのWebサイトから「055_TplDropStringGrid.zip」をダウンロードして解凍。中に含まれている「plDropStringGrid.pas」をコピーして、Delphiのプロジェクトファイル(*.dproj)があるフォルダに貼り付け。

プログラムには、次のコードを加えた。

uses
  ・・・ 省略 ・・・
  plDropStringGrid, System.TypInfo;

  //plDropStringGrid, System.TypInfoは、実行時にコンポーネントを交換するために追加
  //-> StringGridの縦のアライメントを設定する目的

{$R *.dfm}

こちらの「コンポーネントを交換する関数」は、記事にあったものをそのまま、コピペ!

//コンポーネントを交換する関数
//usesにTypInfoの追加が必要
function ChangeComponent(Original: TComponent; NewClass: TComponentClass): TComponent;
var
  New: TComponent;
  Stream: TStream;
  Methods: array of TMethod;
  aPPropInfo: array of PPropInfo;
  MethodCount, i: Integer;
begin
  SetLength(aPPropInfo, 16379);
  MethodCount := GetPropList(Original.ClassInfo, [tkMethod], @aPPropInfo[0]);
  SetLength(Methods, MethodCount);
  for i := 0 to MethodCount - 1 do
    Methods[i] := GetMethodProp(Original, aPPropInfo[i]);

  Stream := TMemoryStream.Create;
  try
    Stream.WriteComponent(Original);
    New := NewClass.Create(Original.Owner);
    if New is TControl then
      TControl(New).Parent := TControl(Original).Parent;
    Original.Free;
    Stream.Position := 0;
    Stream.ReadComponent(New);
  finally
    Stream.free
  end;

  for i := 0 to MethodCount - 1 do
    SetMethodProp(New, aPPropInfo[i], Methods[i]);
  Result := New;
end;

この関数を、FormCreate時に呼び出して、実行。

procedure TFormCollaboration.FormCreate(Sender: TObject);
begin
  //コンポーネントを交換する関数を実行
  StringGrid1:= TStringGrid(ChangeComponent(StringGrid1, TplDropStringGrid));
end;

ここまでが準備で、縦のアライメントの設定は、次のたった1行(赤字)を追加するのみ!

procedure TFormCollaboration.StringGrid1GetEditText(Sender: TObject; ACol,
  ARow: Integer; var Value: string);
begin

  //縦のアライメントを設定
  TplDropStringGrid(StringGrid1).EditVertAlignment := vaCenter;

  //IMEの制御
  with TEdit(_TGrid(Sender).InplaceEditor) do
  begin
    //ImeMode := imClose;   //日本語入力OFF-> ×
    ImeMode := imDisable;   //日本語入力OFFは imDisable
  end;

  //現在Activeな行番号を取得
  intStringGrid1ActiveRow:=ARow;

end;

実行結果です。

インプレイスエディタ起動時、アライメント設定は「水平方向は左・垂直方向は中央」

旅行先で、ちょっと時間ができたので、前にユーザーと話しをする中で思い立った解答欄画像の高さと採点欄の高さを同じにするコードをちょこちょこっと書いて、動作を確認。そしたら今度は、編集モードでのセルの挙動が気になり、翌朝、早く目覚めたので、まさか旅先で書くとは思わなかったけど、PCは持参していたのでこれ幸いと、お日さまが昇るころまでにここまでの内容を記述(・・・というかほぼ全部コピペ)。

3.画像のスクロールとGridコントロールの連動

次に気になったのがTImageに表示した答案画像と、StringGridのスクロールの連動(同期)。

実はこれも前から気になっていたコトだったんだけれど、いろんな事情から、とりあえずプログラムを使える状態にすることが最優先だったので、ずっと後回しにしてきた課題。

今回、解答欄画像の高さと、採点欄の高さを揃えたら、以前にも増して同期の必要性を痛感。まだ、期待通りの動きになった・・・とは言い難い状態なんだけど、現在のコードは次の通り(こちらもずっと以前にMr.XRAYさんのWebサイトにあった記事を参考にさせていただいて書いたプログラムからコピペしたコードだったような記憶が・・・)。参考にさせていただいたのは、おそらく次のページ。

078_コントロールのマウスホイール操作によるスクロール

http://mrxray.on.coocan.jp/Delphi/plSamples/078_Control_MouseWheel.htm
調整値1-10を設定するComboBox

マウスに関する諸設定は、環境により異なるので、調整値は固定値にしないで、ユーザーが自由に設定できるようにした(つもり)。My PC環境で試したところ、次のコードでは、調整値「7~8」くらいが期待に近い動きをするようだ。

procedure TFormCollaboration.FormMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
var
  LDelta:Integer;
  LWinCtrl:TWinControl;
  LCurPos:TPoint;
  //スクロール量の調整(SA:Scroll Amount)
  intSA:integer;
begin

  //マウスカーソルが TScrollBox の領域内にある時だけスクロールを可能にする
  //(解答欄画像を表示しているTImageはTScrollBoxの上に配置)
  LCurPos := ScrollBox1.Parent.ScreenToClient(MousePos);
  if PtInRect(ScrollBox1.BoundsRect, LCurPos) then
  begin
    //スクロール量の調整
    if not TryStrToInt(調整値1-10を設定するComboBoxの値, intSA) then
    begin
      intSA:=1;
    end;
    //心配なので、念のために設定その1
    if 調整値1-10を設定するComboBoxの値 ='0' then
    begin
      intSA:=1;
    end;
    //心配なので、念のために設定その2
    if StrToInt(調整値1-10を設定するComboBoxの値) < 0 then
    begin
      intSA:=1;
    end;
    //大きい数値を選ぶとスクロール量も大きくなるように設定
    intSA:=11-intSA;
    LDelta := WheelDelta div intSA;
    if ssCtrl in Shift then
    begin
      ScrollBox1.HorzScrollBar.Position := 
        ScrollBox1.HorzScrollBar.Position - LDelta;
    end else begin
      ScrollBox1.VertScrollBar.Position := 
        ScrollBox1.VertScrollBar.Position - LDelta;
      //StringGridも連動してスクロールさせる
      if LDelta > 0 then
      begin
        StringGrid1.Perform(WM_VSCROLL, SB_LINEUP, 0);
      end else begin
        StringGrid1.Perform(WM_VSCROLL, SB_LINEDOWN, 0);
      end;
    end;
  end else begin
    //マウス直下のコントロールを取得
    LWinCtrl:=FindVCLWindow(MousePos);
    //TStringGridの場合
    if LWinCtrl is TStringGrid then
    begin
      if WheelDelta > 0 then
      begin
        LWinCtrl.Perform(WM_VSCROLL, SB_LINEUP, 0);
      end else begin
        LWinCtrl.Perform(WM_VSCROLL, SB_LINEDOWN, 0);
      end;
    end;
  end;
  //この1行を忘れないこと!
  Handled:=True;

end;

テストしたPCのマウス関連の諸設定は、以下の通り。

テストしたPCのマウス関連の諸設定①
テストしたPCのマウス関連の諸設定②
テストしたPCのマウス関連の諸設定③

4.まとめ

StringGridで編集モード時に、縦のアライメントを設定するには、標準のStringGridでは機能的に難しいので、それが可能な標準のStringGridを継承したコンポーネントを利用する。コンポーネントのインストールが難しい場合は、実行時に標準のStringGridと入れ替える形で、そのコンポーネントを動的に生成することで、目的を実現できる可能性がある(実行時の動的な生成で、目的を実現できるか・どうかは、十分なテストを行って確認する)。

5.お願いとお断り

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