StringGridでの行・列の挿入削除
90 StringGridRowColInsertDelete 動作確認 Delphi2007 更新日 2008/10/04(土)

StringGridで行を削除しようとすると
1行の内容をクリアしてその後に
次の行からクリアした行に文字列をコピーして
さらに次の行から前の行に文字列をコピーして
・・・・・・
という作業が必要になります。大変、面倒です。

そこで、もっと楽に行う方法がありました。


TCustomGrid.DeleteRowというメソッドが
protected 宣言で存在する事はHELPなどからわかるのですが
TStringGridでも protected 属性のままなので普通は呼び出せません。

単にこのメソッドを呼ぶために
DeleteRow を public 属性にした Component のインストールを
するのは大変です。

そこでこのようにして使います。
────────────────────
implementation

type
  TStrGridAccess = class(TStringGrid);

procedure TForm1.Button1Click(Sender: TObject);
begin
  TStrGridAccess(StringGrid1).DeleteRow(0);
  //0行目が削除される
end;
────────────────────

このキャストするやり方ならTStringGridを継承したコンポーネントを使っていても
DeleteRowを実行する事ができます。

継承クラスからはprotected属性は可視なので
このようにキャストして呼び出す事でメソッドを使う事ができます。


さて、StringGridはすごく拡張の余地が残されています。
単なる行・列の挿入、削除も用意されていないのは非常に残念です。

DeleteRowで行の削除が出来ることはわかったのですが
選択領域がある場合、綺麗に動作しないようだったのと
文字列が正しくクリアされていなかったので拡張してコードを書いてみました。

自分ですべて実装するような泥臭い方法ではなく
上記と同じように protected 属性で隠蔽されている MoveColumn を利用して
標準的なコードで実装しました。

これで、行・列の挿入と削除メソッドが出来ましたので
便利に使えるでしょう。


────────────────────
implementation

type
  TStrGridAccess = class(TStringGrid)
  public
    procedure InsertColumn(ACol: Integer);
    procedure InsertRow(ARow: Integer);
    procedure DeleteColumn(ACol: Longint); override;
    procedure DeleteRow(ARow: Longint); override;
  end;

////////////////////////////////////////////////////////////
{ TStrGridAccess }
////////////////////////////////////////////////////////////

//-------------------------------
//列を削除
procedure TStrGridAccess.DeleteColumn(ACol: Integer);
var
  GridRect1: TGridRect;
  LeftColBuff: Longint;
begin
  {↓AColが不適当な値ならはじく}
  if (ACol < 0) or (ColCount - 1 < ACol) then
  begin
    raise EAccessViolation.Create('Grid削除列指定が間違っています');
  end;

  GridRect1 := Self.Selection;
  LeftColBuff := LeftCol;

  {↓削除列が選択範囲より左にある場合}
  if ACol < GridRect1.Left then
  begin
    GridRect1.Left := GridRect1.Left -1;
    GridRect1.Right := GridRect1.Right -1;
  end else
  {↓削除列が選択範囲中にある場合}
  if (GridRect1.Left <= ACol) and (ACol <= GridRect1.Right) then
  begin
    if GridRect1.Left <> GridRect1.Right then
      GridRect1.Right := GridRect1.Right -1;
    {↑LeftとRightが異なる=選択範囲が複数行の場合のみ選択行範囲を縮める}
  end else
  {↓削除列が選択範囲より右にある場合}
  if (GridRect1.Right < ACol) then
  begin
    //何もしない
  end;

  Cols[ACol].Text := '';
  inherited DeleteColumn(ACol);

  LeftCol := LeftColBuff;
  Self.Selection := GridRect1;
end;


//-------------------------------
//行を削除
procedure TStrGridAccess.DeleteRow(ARow: Integer);
var
  GridRect1: TGridRect;
  TopRowBuff: Longint;
begin
  if (ARow < 0) or (RowCount - 1 < ARow) then
  begin
    raise EAccessViolation.Create('Grid削除行指定が間違っています');
  end;

  GridRect1 := Self.Selection;
  TopRowBuff := TopRow;

  {↓削除行が選択範囲より上にある場合}
  if ARow < GridRect1.Top then
  begin
    GridRect1.Top := GridRect1.Top -1;
    GridRect1.Bottom := GridRect1.Bottom -1;
  end else
  {↓削除行が選択範囲中にある場合}
  if (GridRect1.Top <= ARow) and (ARow <= GridRect1.Bottom) then
  begin
    if GridRect1.Top <> GridRect1.Bottom then
      GridRect1.Bottom := GridRect1.Bottom -1;
    {↑TopとBottomが異なる=選択範囲が複数行の場合のみ選択行範囲を縮める}
  end else
  {↓削除行が選択範囲より下にある場合}
  if (GridRect1.Bottom < ARow) then
  begin
    //何もしない
  end;

  Rows[ARow].Text := '';
  inherited DeleteRow(ARow);

  TopRow := TopRowBuff;
  Self.Selection := GridRect1;
end;

////////////////////////////////////////////////////////////

//-------------------------------
//列を挿入
procedure TStrGridAccess.InsertColumn(ACol: Integer);
var
  GridRect1: TGridRect;
begin
  {↓Indexが不適当な値ならはじく}
  if (ACol < 0) or (ColCount-1 < ACol) then
  begin
    raise EAccessViolation.Create('Grid挿入列指定が間違っています');
  end;

  {↓Gridの選択領域を確保}
  GridRect1 := Selection;

  {↓挿入列が選択範囲より左にある場合}
  if ACol <= GridRect1.Left then
  begin
    GridRect1.Left := GridRect1.Left +1;
    GridRect1.Right := GridRect1.Right +1;
  end else
  {↓挿入列が選択範囲中にある場合}
  if (GridRect1.Left < ACol) and (ACol <= GridRect1.Right) then
  begin {↑Left=Rightの場合は入らない}
    GridRect1.Right := GridRect1.Right +1;
  end else
  {↓挿入列が選択範囲より右にある場合}
  if (GridRect1.Right < ACol) then
  begin
    //何もしない
  end;

  {↓最終列を追加}
  ColCount := ColCount + 1;

  {↓挿入列に移動}
  MoveColumn(ColCount-1, ACol);

  Self.Selection := GridRect1;
end;

//-------------------------------
//行を挿入
procedure TStrGridAccess.InsertRow(ARow: Integer);
var
  GridRect1: TGridRect;
begin
  {↓Indexが不適当な値ならはじく}
  if (ARow < 0) or (ColCount-1 < ARow) then
  begin
    raise EAccessViolation.Create('Grid挿入行指定が間違っています');
  end;

  {↓Gridの選択領域を確保}
  GridRect1 := Selection;

  {↓挿入行が選択範囲より上にある場合}
  if ARow <= GridRect1.Top then
  begin
    GridRect1.Top := GridRect1.Top +1;
    GridRect1.Bottom := GridRect1.Bottom +1;
  end else
  {↓挿入行が選択範囲中にある場合}
  if (GridRect1.Top < ARow) and (ARow <= GridRect1.Bottom) then
  begin {↑Left=Rightの場合は入らない}
    GridRect1.Bottom := GridRect1.Bottom +1;
  end else
  {↓挿入行が選択範囲より下にある場合}
  if (GridRect1.Bottom < ARow) then
  begin
    //何もしない
  end;

  {↓最終行を追加}
  RowCount := RowCount + 1;

  {↓挿入行に移動}
  MoveRow(RowCount-1, ARow);

  Self.Selection := GridRect1;
end;
────────────────────

上記コードのテストコードです
何か拡張したい場合、不具合がある場合は
このテストコードが通るようにして動作確認しつつ拡張していくとよいでしょう

────────────────────
procedure CheckRect(A, B: TRect);
begin
  if not (
    (A.Top=B.Top)
    and (A.Left=B.Left)
    and (A.Bottom=B.Bottom)
    and (A.Right=B.Right) ) then raise Exception.Create('停止');
end;

//-------------------------------
//Gridに文字列をセット
procedure TForm1.SetGridString;
var
  i, j: Integer;
begin
  for i := 0 to StringGrid1.RowCount-1 do
  begin
    for j := 0 to StringGrid1.ColCount-1 do
    begin
      StringGrid1.Cells[j, i] := IntToStr(i) + ' - ' + IntToStr(j);
    end;
  end;
end;

procedure TForm1.testGridDeleteCol;
begin
  //選択範囲より削除列が左にある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).DeleteColumn(0);
  CheckRect(TRect(StringGrid1.Selection), Rect(0,1,2,3));

  //選択範囲中に削除列がある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).DeleteColumn(1);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,2,3));

  //選択範囲より削除列が下にある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).DeleteColumn(4);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,3,3));

  //Grigが1列しかない場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 1;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(0,1,0,3));

  TStrGridAccess(StringGrid1).DeleteColumn(0);
  CheckRect(TRect(StringGrid1.Selection), Rect(0,1,0,3));
  //Gridの行はなくならない
end;

procedure TForm1.testGridDeleteRow;
begin
  //選択範囲より削除行が上にある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).DeleteRow(0);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,0,3,2));

  //選択範囲中に削除行がある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).DeleteRow(1);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,3,2));

  //選択範囲より削除行が下にある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).DeleteRow(4);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,3,3));

  //Grigが1行しかない場合
  {↓初期化}
  StringGrid1.RowCount := 1;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,0,3,0));

  TStrGridAccess(StringGrid1).DeleteRow(0);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,0,3,0));
  //Gridの行はなくならない

end;
////////////////////////////////////////////////////////////
procedure TForm1.testGridInsertCol;
begin
  //選択範囲より挿入列が左にある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).InsertColumn(1);
  CheckRect(TRect(StringGrid1.Selection), Rect(2,1,4,3));

  //選択範囲中に挿入列がある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).InsertColumn(2);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,4,3));

  //選択範囲より挿入列が右にある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).InsertColumn(4);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,3,3));

  //Grigが1列しかない場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 1;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(0,1,0,3));

  TStrGridAccess(StringGrid1).InsertColumn(0);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,1,3));
  //Gridの列はなくならない
end;

procedure TForm1.testGridInsertRow;
begin
  //選択範囲より挿入行が上にある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).InsertRow(1);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,2,3,4));

  //選択範囲中に挿入行がある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).InsertRow(2);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,3,4));

  //選択範囲より挿入行が下にある場合
  {↓初期化}
  StringGrid1.RowCount := 5;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,1,3,3));

  TStrGridAccess(StringGrid1).InsertRow(4);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,3,3));

  //Grigが1行しかない場合
  {↓初期化}
  StringGrid1.RowCount := 1;
  StringGrid1.ColCount := 5;
  SetGridString;
  StringGrid1.Selection := TGridRect(Rect(1,0,3,0));

  TStrGridAccess(StringGrid1).InsertRow(0);
  CheckRect(TRect(StringGrid1.Selection), Rect(1,1,3,1));
  //Gridの行はなくならない

end;


参考────────────────────
Delphi広場<10356>