【问题标题】:Stringgrid with buttons带按钮的字符串网格
【发布时间】:2012-01-28 16:32:09
【问题描述】:

第一个问题:

如何调用stringgrid中不可见的部分?您需要滚动才能看到它。
例如:
stringgrid 中有 20 行,但一次只能看到 10 行。您需要滚动才能看到其他 10 个。“隐藏”的如何称呼?

第二个问题:

我知道这可能不是正确的方法,因此我们将不胜感激。
我有一个带有 1 个固定行的字符串网格。我在运行时添加 ColorButtons。所以我用按钮填充 1 列。 我使用这个按钮来“插入/删除”行。只要所有网格都在“可见”部分中,它就可以很好地工作。 当我“插入”新行并将按钮移动到“隐藏”部分时会出现问题。然后将最后一个按钮绘制到 Cell[0,0]。 “隐藏”部分的其他按钮绘制正确。知道为什么会这样吗?我应该在 OnDraw 方法中找到一种方法来管理这个问题,还是有更好(正确)的方法来做到这一点?

代码:

procedure Tform1.addButton(Grid : TStringGrid; ACol : Integer; ARow : Integer);
var
  bt : TColorButton;
  Rect : TRect;
  index : Integer;
begin
    Rect := Grid.CellRect(ACol,ARow);
    bt := TColorButton.Create(Grid);
    bt.Parent := Grid;
    bt.BackColor := clCream;
    bt.Font.Size := 14;
    bt.Width := 50;
    bt.Top := Rect.Top;
    bt.Left := Rect.Left;
    bt.Caption := '+';
    bt.Name := 'bt'+IntToStr(ARow);
    index := Grid.ComponentCount-1;
    bt :=(Grid.Components[index] as TColorButton);
    Grid.Objects[ACol,ARow] := Grid.Components[index];
    bt.OnMouseUp := Grid.OnMouseUp;
    bt.OnMouseMove := Grid.OnMouseMove;
    bt.Visible := true;
end;

procedure MoveRowPlus(Grid : TStringGrid; Arow : Integer; stRow : Integer);
var
  r, index : Integer;
  bt : TColorButton;
  Rect : TRect;
begin
  Grid.RowCount := Grid.RowCount+stRow;

  for r := Grid.RowCount - 1 downto ARow+stRow do
    begin
      Grid.Rows[r] := Grid.Rows[r-StRow];
    end;

  index := Grid.ComponentCount-1;
  for r := Grid.RowCount - 1 downto ARow+stRow do
    begin
      bt :=(Grid.Components[index] as TColorButton);
      Rect := Grid.CellRect(10,r);
      bt.Top := Rect.Top;
      bt.Left := Rect.Left;
      Grid.Objects[10,r] := Grid.Components[index];
      dec(index);
    end;
      for r := ARow to (ARow +stRow-1) do
        begin
          Grid.Rows[r].Clear;
        end;  
end;

procedure MoveRowMinus(Grid : TStringGrid; Arow : Integer; stRow : Integer);
var
  r, index : Integer;
  bt : TColorButton;
  Rect : TRect;
begin

  for r := ARow to Grid.RowCount-stRow-1 do
    begin
      Grid.Rows[r] := Grid.Rows[r+StRow];
    end;

  index := ARow-1;
  for r := ARow to Grid.RowCount-stRow-1 do
    begin
      Rect := Grid.CellRect(10,r);
      bt :=(Grid.Components[index] as TColorButton);
      bt.Top := Rect.Top;
      bt.Left := Rect.Left;
      Grid.Objects[10,r] := Grid.Components[index];
      bt.Visible := true;
      inc(index);
    end;

  for r := Grid.RowCount-stRow to Grid.RowCount-1 do
    begin
      Grid.Rows[r].Clear;
    end;
  Grid.RowCount := Grid.RowCount-stRow;
end;

【问题讨论】:

  • 好的,我尝试访问 OnDrawCell 中的按钮,但出现“访问被拒绝”错误。我尝试将按钮停靠到网格单元格,但最后一个可见按钮的高度会降低到网格的可见部分。当然最后一个按钮被绘制到 Cell[0,0]。

标签: delphi tstringgrid


【解决方案1】:
  1. 对于可见部分,存在VisibleRowCountVisibleColCount 属性。 TGridAxisDrawInfo 记录类型将可见部分命名为 Boundary 和所有部分一起命名为 Extent(反之亦然,我不记得了)。因此,VCL 没有为字符串网格的不可见部分声明特定的名称。它只是不可见的部分

  2. 我认为您犯了一个逻辑错误:滚动网格时按钮移动。尽管它们看起来像是在移动,但这只是由于对ScrollWindow 的内部调用而移动设备上下文内容的结果。字符串网格组件中的滚动条是自定义添加的,不像 e.g. TScrollBox

    要始终在它们真正所在的位置显示所有按钮,请在 OnTopLeftChanged 事件中重新绘制字符串网格:

    procedure TForm1.StringGrid1TopLeftChanged(Sender: TObject);
    begin
      StringGrid1.Repaint;
    end;
    

    当所有行的行高和字符串网格的高度都不会改变时,那么只创建一次所有按钮就足够了,让它们保持在原来的位置。这意味着每个按钮不再“附加”到一行,将它们存储在Objects 属性中不再有意义。当按下按钮时,只需从按钮的位置结合字符串网格的TopRow 属性计算预期的行索引,该属性指定网格中第一个可见的可滚动行的索引。

    如果网格可以调整大小,例如通过锚点,然后更新父级的 OnResize 事件中的按钮计数。如果字符串网格的行数可能小于最大可见行数,则还要更新(可见)按钮数。

    如果您想要更多答案,请更新您的问题并解释如何调用 MoveRowPlusMoveRowMinus 例程由于与网格和或按钮的交互,因为现在我不完全理解它是什么是你想要的。

    关于CellRect 给出错误的坐标:那是因为CellRect 仅适用于完整(或部分)可见单元格。引用documentation

    如果指定的单元格不可见,CellRect 返回一个空矩形。


由于 OP 的 cmets 而添加

我认为以下代码可以满足您的需求。每个按钮的原始行索引存储在Tag 属性中。

unit Unit1;

interface

uses
  Windows, Classes, Controls, Forms, StdCtrls, Grids;

type
  TForm1 = class(TForm)
    Grid: TStringGrid;
    procedure GridTopLeftChanged(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FPrevTopRow: Integer;
    procedure CreateGridButtons(ACol: Integer);
    procedure GridButtonClick(Sender: TObject);
    procedure RearrangeGridButtons;
    function GetInsertRowCount(ARow: Integer): Integer;
    function GridButtonToRow(AButton: TButton): Integer;
    procedure MoveGridButtons(ButtonIndex, ARowCount: Integer);
  end;

implementation

{$R *.dfm}

type
  TStringGridAccess = class(TStringGrid);

procedure TForm1.FormCreate(Sender: TObject);
begin
  FPrevTopRow := Grid.TopRow;
  CreateGridButtons(2);
end;

procedure TForm1.CreateGridButtons(ACol: Integer);
var
  R: TRect;
  I: Integer;
  Button: TButton;
begin
  R := Grid.CellRect(ACol, Grid.FixedRows);
  Inc(R.Right, Grid.GridLineWidth);
  Inc(R.Bottom, Grid.GridLineWidth);
  for I := Grid.FixedRows to Grid.RowCount - 1 do
  begin
    Button := TButton.Create(Grid);
    Button.BoundsRect := R;
    Button.Caption := '+';
    Button.Tag := I;
    Button.ControlStyle := [csClickEvents];
    Button.OnClick := GridButtonClick;
    Button.Parent := Grid;
    Grid.Objects[0, I] := Button;
    OffsetRect(R, 0, Grid.DefaultRowHeight + Grid.GridLineWidth);
  end;
end;

procedure TForm1.GridButtonClick(Sender: TObject);
var
  Button: TButton absolute Sender;
  N: Integer;
  I: Integer;
begin
  N := GetInsertRowCount(Button.Tag);
  if Button.Caption = '+' then
  begin
    Button.Caption := '-';
    Grid.RowCount := Grid.RowCount + N;
    for I := 1 to N do
      TStringGridAccess(Grid).MoveRow(Grid.RowCount - 1,
        GridButtonToRow(Button) + 1);
    MoveGridButtons(Button.Tag, N);
  end
  else
  begin
    Button.Caption := '+';
    for I := 1 to N do
      TStringGridAccess(Grid).MoveRow(GridButtonToRow(Button) + 1,
        Grid.RowCount - 1);
    Grid.RowCount := Grid.RowCount - N;
    MoveGridButtons(Button.Tag, -N);
  end;
end;

procedure TForm1.GridTopLeftChanged(Sender: TObject);
begin
  RearrangeGridButtons;
  FPrevTopRow := Grid.TopRow;
end;

procedure TForm1.RearrangeGridButtons;
var
  I: Integer;
  Shift: Integer;
begin
  Shift := (Grid.TopRow - FPrevTopRow) *
    (Grid.DefaultRowHeight + Grid.GridLineWidth);
  for I := 0 to Grid.ControlCount - 1 do
  begin
    Grid.Controls[I].Top := Grid.Controls[I].Top - Shift;
    Grid.Controls[I].Visible := Grid.Controls[I].Top > 0;
  end;
end;

function TForm1.GetInsertRowCount(ARow: Integer): Integer;
begin
  //This function should return the number of rows which is to be inserted
  //below ARow. Note that ARow refers to the original row index, that is:
  //without account for already inserted rows. For now, assume three rows:
  Result := 3;
end;

function TForm1.GridButtonToRow(AButton: TButton): Integer;
begin
  for Result := 0 to Grid.RowCount - 1 do
    if Grid.Objects[0, Result] = AButton then
      Exit;
  Result := -1;
end;

procedure TForm1.MoveGridButtons(ButtonIndex, ARowCount: Integer);
var
  I: Integer;
begin
  for I := 0 to Grid.ControlCount - 1 do
    if Grid.Controls[I].Tag > ButtonIndex then
      Grid.Controls[I].Top := Grid.Controls[I].Top +
        ARowCount * (Grid.DefaultRowHeight + Grid.GridLineWidth);
end;

end.

但我可以说不使用按钮控件也可以:我建议在字符串网格的 OnDrawCell 事件中绘制假按钮控件。

【讨论】:

  • (评论 1/2) 谢谢你的回答。我今天会试试。我看到我对我想要实现的目标有点不清楚。我想用数据和按钮填充网格。当用户点击一个按钮(标题:='+')时,MoveRowPlus 会使用参数调用:Grid、ARow(按钮所在的行)和 stRow(需要在 ARow 下插入的行数)。示例:我有 20 行,有 20 个按钮。我单击第 3 行中的按钮。为 stRow 移动第 4 到 20 行。如果 stRow 为 2,则第 4 行变为第 6 行,依此类推,直到网格结束。 (按钮随其分配的行移动)然后填充 2 个空行
  • (评论 2/2)..2 空行然后填充“子查询”。按钮数量保持不变。当我再次单击按钮(现在带有标题:='-')时,将调用 MoveRowMinus 并将 Grid 返回到初始状态。 (子查询关闭)。我希望我对我想要实现的目标足够清楚。你认为这可以在不使用 Object 属性的情况下实现吗? (在 NGLN - 抱歉 atName 似乎不起作用)
  • (atNGLN) 谢谢。这正是我需要的。我正在考虑自己画假按钮,但这个解决方案似乎好多了。我对使用这种方法的“利弊”很感兴趣。为什么推荐使用 onDraw?感谢您的时间和情报。 I.Bagon
  • @user805528 自己绘制按钮的专业人士:无需因滚动而重新排列控件,行可以有不同的高度,需要的资源更少,网格的闪烁更少,更容易处理行数更改,行插入和行删除,更容易在自定义衍生中实现。
  • 这很好,但是当 StringGrid 有 10000 行时我的应用程序崩溃了!!
猜你喜欢
  • 2019-02-23
  • 1970-01-01
  • 2012-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-15
  • 1970-01-01
  • 2016-05-11
相关资源
最近更新 更多