4

1st Question:

How do you call the part in stringgrid that is not visible? You need to scroll to see it.
For example:
There are 20 rows in a stringgrid but you can see only 10 at a time. You need to scroll to see other 10. How are the "hidden" ones called?

2nd Question:

I know this is probably not the right way to do it so some pointers would be appreciated.
I have a string grid with 1 fixed row. I add ColorButtons at runtime. So I populate 1 column with buttons. I use this buttons to "insert/delete" rows. As long as all of the grid is in the "visible" part this works well. Problem occcurs when I "insert" new rows and move the buttons to the "hidden" part. The last button is then drawn to Cell[0,0]. Other buttons in the "hidden" part are drawn correctly. Any idea why this happens? Should I find a way to manage this problem in the OnDraw method or is there a better (correct) way to do this?

Code:

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;
NGLN
  • 43,011
  • 8
  • 105
  • 200
user805528
  • 189
  • 1
  • 5
  • 17
  • Ok, I've tryed accessing the buttons in the OnDrawCell but I get "Access denied" error. I tryed docking buttons to Grid cells, but then the last visible buttons height gets reduced to the visible part of the Grid. And of course the last button is drawn to Cell[0,0]. – user805528 Jan 28 '12 at 20:08

1 Answers1

6
  1. For the visible part there exist the VisibleRowCount and VisibleColCount properties. The TGridAxisDrawInfo record type names the visible part Boundary and all parts together Extent (or vice versa, I never remember). So there is no specific by the VCL declared name for the unvisible part of a string grid. It just is the unvisible part.

  2. I think you are making a logical error: the buttons are not moved when you scroll the grid. Though it may seem like they move, that is just the result of moving the device context contents due to an internal call to ScrollWindow. The scroll bars in the string grid component are custom added, and do not work like those of e.g. a TScrollBox.

    To always show all buttons on the locations where they really are, repaint the string grid in the OnTopLeftChanged event:

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

    When the row heights of all rows and the height of string grid never change, then it is sufficient to create all buttons only once, and let them stay where they are. This means that every button no longer is "attached" to a row, and storing them in the Objects property has no significance any more. When a button is pressed, simply calculate the intended row index from the position of the button in combination with the TopRow property of the string grid which specifies the index of the first visible scrollable row in the grid.

    If the grid can resize, e.g. by anchors, then update the button count in the parent's OnResize event. And if the row count of the string grid may become less then the maximum visible row count, then also update the (visible) button count.

    If you want more of an answer, then please update your question and explain how the MoveRowPlus and the MoveRowMinus routines are called due to interaction with the grid and or buttons, because now I do not fully understand what it is that you want.

    And about CellRect giving the wrong coordinates: that is because CellRect only works on full (or partial) visible cells. To quote the documentation:

    If the indicated cell is not visible, CellRect returns an empty rectangle.


Addition due to OP's comments

I think the following code does what you want. The original row index of every button is stored in the Tag property.

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.

But may I say that this is also possible without the use of button controls: I suggest drawing fake button controls in the string grid's OnDrawCell event.

NGLN
  • 43,011
  • 8
  • 105
  • 200
  • (Comment 1/2) Thank you for your answer. I will try it today. I see I was a bit unclear on what I want to achieve. I want to fill the grid with data and buttons. When user clicks on a button (caption := '+') MoveRowPlus is called with parameters: Grid, ARow(row where button is) and stRow(number of rows that need to be inserted under ARow). Example: I have 20 rows with 20 buttons. I click on button in row 3., Rows 4 to 20 are moved for stRow. If stRow is 2 then row 4 becomes row 6 and so on till the end of the grid. (buttons move with their assigned rows) 2 empty rows are then populated – user805528 Jan 29 '12 at 13:44
  • (Comment 2/2) ..2 empty rows are then populated with a "subquery". Number of buttons remains the same. When I click again on button (now with caption := '-') MoveRowMinus is called and Grid is returned to inital state. (subquery closes). I hope I was clear enough on what I want to acheive. Do you think this could be achieved without using Object property? (at NGLN - sorry the atName doesn't seem to work) – user805528 Jan 29 '12 at 13:54
  • (atNGLN) Thank you. This is exactly what I need. I was considering drawing fake buttons myself, but this solution seems so much better. I am intersted in "pros and cons" of using this method. Why do you recommend using onDraw? Thank you for your time and intel. I.Bagon – user805528 Jan 29 '12 at 18:10
  • @user805528 Pro's of drawing buttons yourself: no need for rearrangement of controls due to scrolling, rows can have different heights, less resources needed, less flickering of the grid, easier handling of rowcount changes, row insertion and row deletion, and easier to implement in custom derivative. – NGLN Jan 29 '12 at 21:12
  • this working good but my app crashed when StringGrid had 10000 row!! – peiman F. Jan 14 '20 at 11:24
  • @peimanF. This isn't the right solution for you. You need a virtual approach, or you need to draw the buttons in de OnDrawCell event. You may even need to re-evaluate your GUI design requirements, because such a number of controls is unrealistic for programs and users, isn't it? – NGLN Jan 14 '20 at 18:49