Title: Implementing Multi Select String Grid
I was looking for a way to allow multi select in a TStringGrid. A DBGrid allows this, but I cannot use a DBGrid because I need the information in the database to remain as unlocked as possible (so I pull the data in locally and use the String Grid). For this, multi select for the TStringGrid was required. This FAQ expands on FAQ102-2231: Coloring selected Row in a dbgrid.
First, you will need an
array of Boolean
(referred to here as
ItemsSelected
).
This will keep track of what rows are selected.
When you fill your string grid you will dynamically assign the length of the array using
SetLength
(that is assuming its dynamically sized, and not a constant size of, say for example, 5.
First, lets set up our color schemes.
For your String Grid you need to add some code to the
OnDrawCell
event.
First, a couple of notes:
1. I follow all information with a blank row, but do not want to allow them to select the blank row.
2. I have every possible set of ( ) and
begin
and
end
in here for portability across Delphi versions, but not all of them may be required for you.
There may be a slightly more efficient way, but this gets the job done nicely:
CODE
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
StringGrid1.Canvas.Font.Color := clBlack;
// Default color
StringGrid1.Canvas.Brush.Color := clWhite;
if ((ACol = 0) OR (ARow = 0)) then
// First check if Fixed row or column
begin
StringGrid1.Canvas.Brush.Color := clBtnFace;
end
else
begin
if not (ARow = (StringGrid1.RowCount - 1)) then
// Avoid empty row
begin
if (ItemsSelected[StringGrid1.Row - 1] = True) then
begin
// Will only color selected rows
StringGrid1.Canvas.Font.Color := clWhite;
StringGrid1.Canvas.Brush.Color := clBlue;
end;
end;
end;
StringGrid1.Canvas.FillRect(Rect);
StringGrid1.Canvas.TextOut(Rect.Left, Rect.Top, StringGrid1.Cells[ACol, ARow]);
end;
Okay, now our grid will draw the proper colors for any given scenario (you can of course use whatever colors you want to).
How do we change when a row is selected though?
For that I use the
OnMouseUp
event. Everything I tried with the
OnSelectCell
seemed to just fail and give me errors. That is partly why I coded everything this way. Also did not want to use the
OnMouseDown
because I wanted to make sure that the cell / row was selected as normally happens when the user presses the mouse key. Okay, the Mouse event:
CODE
procedure TForm1.StringGrid1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
col, row : Integer;
begin
StringGrid1.MouseToCell(X, Y, col, row);
// This will tell us what cell (we need row) was selected
if ((row 0) AND (row // Again don't select fixed or blank row
if (ItemsSelected[StringGrid1.Row - 1] = True) then
begin
ItemsSelected[StringGrid1.Row - 1] := False;
end
else
begin
ItemsSelected[StringGrid1.Row - 1] := True;
end;
StringGrid1.Invalidate;
end;
Again, you could probably just do ItemsSelected[StrToInt(StringGrid1.Cells[0, row]) - 1] := not ItemsSelected[StrToInt(StringGrid1.Cells[0, row]) - 1];
but that might not work in all versions of Delphi.
Adding the
Invalidate
ensures that the grid will be redrawn with the newly (un)selected row.
Thats it! Just be sure that when you create your array to monitor whether items are selected or not, that you initialize it all to
False
(or True if thats your desire).
One note that is worthy of mentioning:
For error trapping, do not have the form visible and add
ShowMessage
in there at the same time. Chances are you will enter an infinite loop with you clicking on the little pop up box and the grid trying to redraw itself since it was covered in any way.