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:
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:
begin StringGrid1.MouseToCell(X, Y, col, row); // This will tell us what cell (we need row) was selected if ((row > 0) AND (row < StringGrid1.RowCount - 1)) then // 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.