[広告] 当サイトはアフィリエイト広告を利用しています。

【コピペTips】StringGrid あるいは VirtualTree で疑似的に「列の固定」を実現する

エクセルなどの表計算ソフトで「行と列の固定」という機能があるんですが、それをStringGridやVirtualTreeで実現したいと。そう思ったことがあります。

それを実現するためにコンポーネントを複数置いて、セルの移動ごとに左上の位置を同期してみたりと今まではやってました。

できないことはないんですが、どうも野暮ったい。結構ズレる。何とかスマートな方法はないものか。

 
そう思って出した妥協案がこちら(今回は列だけの固定)。

⇒ マウスドラッグでスクロールさせちゃえばそれっぽく見えね?

 
とりあえずCol,Rowのサイズを画面に表示される部分に限定しておいて、表示部分を移動させたければ表示部分をドラッグして移動しているように見せる。

という方法です。

これは某ソフトウェアがやっていたのを パク 参考にしたものです。

サイズを偽装(人聞きが悪い。。)しているのでスクロールバーが出ませんが、まぁ仕方がない。なんならスクロールバーコントロールを別途付けてそれっぽく見せる。という方法もあるかもしれません。

試してないので責任は持てませんが。

ソース

で、ソース。ユニット名はご自由に。

VirtualTree に関しては VirtualStringTree限定(それ以外使ったことがないので)。テストは VirtualStringTree で確認(StringGridでは未確認)。ソースに解説がなくてごめんなさい。

SetControl ではもっとうまい方法がないでしょうかね。今は与えられたコンポーネントについて1つ1つコントロールの確認を行ってイベントの設定を行ってるんですけど、これを一度にやりたい。

 
クラスの形をとっているのでインスタンスを作成して SetControl でコントロールをセット。

この際にイベントが同時にセットされます。

DivSpan プロパティはどれだけ移動したらセルが1つ移動するかという数値です。初期値は30にセットしてあります。ここはご随意に。

 
で、表示部は別に用意する必要があります。それはまた後述。

 
【ソース】

unit DumUnit;

interface

 uses Vcl.Controls, System.Classes, Vcl.Grids,VirtualTrees
 ;

type

 //=====================================================================
 // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
 //=====================================================================
 TNYFixedCol=class

  private
   FDragStartPos:integer;
   FDragStartOfs:integer;
   FLeftOfs     :integer;
   FDivSpan     :integer;

   FRefresh:boolean;

   FControl:TControl;


   procedure FOnMouseDown(Sender: TObject; Button: TMouseButton
                                         ; Shift: TShiftState; X, Y: Integer);
   procedure FOnMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
   procedure FOnMouseUp(Sender: TObject; Button: TMouseButton
                                       ; Shift: TShiftState; X, Y: Integer);

  public
   constructor Create;
   destructor  Destroy; override;

   function  SetControl(Comp:TControl):boolean;


  property RefreshMode:boolean read FRefresh write FRefresh;
  property DivSpan:integer     read FDivSpan write FDivSpan;
  property LeftOfs:integer     read FLeftOfs;

 end;


implementation

//=====================================================================
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
//=====================================================================
constructor TNYFixedCol.Create;
begin
 FDragStartPos:=-1;
 FDragStartOfs:=0;
 FDivSpan     :=30;
 FRefresh     :=True;
end;


destructor TNYFixedCol.Destroy;
begin
 inherited;

end;

function TNYFixedCol.SetControl(Comp:TControl):boolean;
begin
 Result:=((Comp is TStringGrid) or (Comp is TVirtualStringTree));
 if not Result then exit;

 FControl:=Comp;
 if FControl is TStringGrid then begin
  with TStringGrid(FControl) do begin
   OnMouseDown:=FOnMouseDown;
   OnMouseMove:=FOnMouseMove;
   OnMouseUp  :=FOnMouseUp;
  end;
 end

 else if FControl is TVirtualStringTree then begin
  with TVirtualStringTree(FControl) do begin
   OnMouseDown:=FOnMouseDown;
   OnMouseMove:=FOnMouseMove;
   OnMouseUp  :=FOnMouseUp;
  end;
 end
 ;

end;

procedure TNYFixedCol.FOnMouseDown(Sender: TObject; Button: TMouseButton
                                         ; Shift: TShiftState; X, Y: Integer);
begin
 if ssLeft in Shift then begin
  FDragStartPos:=x;
  FDragStartOfs:=FLeftOfs;
 end;
end;

procedure TNYFixedCol.FOnMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
 if FDragStartPos=-1 then exit;

 FLeftOfs:=FDragStartOfs-((x-FDragStartPos)div FDivSpan);
 if FRefresh then FControl.Refresh;
end;

procedure TNYFixedCol.FOnMouseUp(Sender: TObject; Button: TMouseButton
                                       ; Shift: TShiftState; X, Y: Integer);
begin
 FDragStartPos:=-1;
end;

end.

表示部ほか

とりあえず設定部分から。

■ 宣言部の Private あたりでインスタンス用の変数を設定

FFixedCol:TNYFixedCol;

 
■ フォームの OnCreate と OnDestroy あたりで以下のコードを実行。

procedure TForm1.FormCreate(Sender: TObject);
begin
 FFixedCol:=TNYFixedCol.Create;
 FFixedCol.SetControl(VirtualStringTree1);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
 FreeAndNil(FFixedCol);
end;

 
で、表示部。VirtualStringTreeで。VirtualStringTreeなら OnGetTextイベントにて。参考まで。

procedure TForm1.VirtualStringTree1GetText(
  Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
  TextType: TVSTTextType; var CellText: string);
begin
 if not Assigned(node) then exit;

 case Column of
  0..2:CellText:=(Column+1).ToString;
  else CellText:=(FFixedCol.LeftOfs+Column).Tostring;
 end;
end;

多分これでイケるはず。

固定部分は左側3列(Caseでいうところの 0..2)が1,2,3で固定されていて、マウスドラッグをするとそれ以降の数字がドラッグに応じて変わるという形になっています。

固定列を可変にしたいならcase文をやめて Column を if 文で判断するとよいでしょう。

これが実際の横位置

FFixedCol.LeftOfs+Column 

 
表示領域のデータを位置で保存しているなら、上記式が Col の値。僕は

type
TNYPos=Record
 Col:integer;
 Row:integer;
end;

といったレコードを用意しておいて、

FGridVal:TDictionary<TNYPos,String>;

といった変数を用意してデータを保存します。

 
で、これを実行するにあたり、VirtualStringTree のカラムを設定しておく必要があります。

Header.Columns でカラムを追加しておいて下さい。さらに表示すべき行がないと表示できないので行も追加で。

ソースでやるならフォームの OnCreate 部に以下のコードを追加。

 with VirtualStringTree1 do begin
  with Header.Columns do begin
   add;
   add;
   add;
   add;
   add;
   add;
   add;
  end;

  AddChild(nil);
 end;

まぁ超適当。

 
これでとりあえずは動くと思います。

※スクロールバーコンポーネントを別途追加してそれっぽく見せるなら、VirtualStringTree の OnGetText イベントのあたりをゴニョニョとすればできるかもしれませんね。今、記事を書いていてそう思いました。

タイトルとURLをコピーしました