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;
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.
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.