I am running Lazarus v0.9.30 (32 bit compiler).
I have a TForm with a standard TStringGrid on it. The grid has the following properties set. RowCount = 5, ColumnCount = 5, FixedCols = 0, FixedRows = 0.
I Googled some code that showed me how to change the cell colour and add some text to the cell when a user clicks on a TStringGrid cell. All works fine and I have extended it slightly to toggle the color/text on and off on the GridClick event.
The questions I have are more to better understand the purpose behind some of the elements of the code.
There is an array of Foregroud (FG) and Background (BG) TColor objects. Are they there to store the cell color attributes that are set on the GridClick event, so if the DrawCell event needs to get triggered again for any reason the cell can redraw itself? Can you avoid using the array of TColors and just set the colour / text in the DrawCell event as required?
If you need to use the arrays, I would assume that dimensions must match the Grid.ColCount and Grid.RowCount (ie. set via the SetLength call in Form.Create)
Is there a way to detect that you are clicking outside of the 5 x 5 cells of the stringgrid (ie in the whitespace) and thus prevent the GridClick from calling the DrawCell event. No matter where you click you always get a valid value for Row and Col.
unit testunit;
{$mode objfpc}{$H }
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
ExtCtrls, Menus, ComCtrls, Buttons, Grids, StdCtrls, Windows, Variants,
LCLType;
type
{ TForm1 }
TForm1 = class(TForm)
Grid: TStringGrid;
procedure FormCreate(Sender: TObject);
procedure GridClick(Sender: TObject);
procedure GridDrawCell(Sender: TObject; aCol, aRow: Integer;
aRect: TRect; aState: TGridDrawState);
end;
var
Form1: TForm1;
implementation
var
FG: array of array of TColor;
BG: array of array of TColor;
{$R *.lfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
var
Col, Row: integer;
begin
// Set the sizes of the arrays
SetLength(FG, 5, 5);
SetLength(BG, 5, 5);
// Initialize with default colors
for Col := 0 to Grid.ColCount - 1 do begin
for Row := 0 to Grid.RowCount - 1 do begin
FG[Col, Row] := clBlack;
BG[Col, Row] := clWhite;
end;
end;
end;
procedure TForm1.GridDrawCell(Sender: TObject; aCol, aRow: Integer;
aRect: TRect; aState: TGridDrawState);
var
S: string;
begin
S := Grid.Cells[ACol, ARow];
// Fill rectangle with colour
Grid.Canvas.Brush.Color := BG[ACol, ARow];
Grid.Canvas.FillRect(aRect);
// Next, draw the text in the rectangle
Grid.Canvas.Font.Color := FG[ACol, ARow];
Grid.Canvas.TextOut(aRect.Left 22, aRect.Top 2, S);
end;
procedure TForm1.GridClick(Sender: TObject);
var
Col, Row: integer;
begin
Col := Grid.Col;
Row := Grid.Row;
// Set the cell color and text to be displayed
if (Grid.Cells[Col,Row] <> 'Yes') then
begin
BG[Col, Row] := rgb(131, 245, 44);
FG[Col, Row] := RGB(0, 0, 0);
Grid.Cells[Col, Row] := 'Yes'
end {if}
else
begin
BG[Col, Row] := rgb(255, 255, 255);
FG[Col, Row] := RGB(255, 255, 255);
Grid.Cells[Col, Row] := '';
end; {else}
end;
end.
CodePudding user response:
If you set the AllowOutboundEvents
to False
, the OnClick
event will be fired only when you click on a certain cell, not when you click on a whitespace. So if you use this property, you will always get valid cell coordinates when you click somewhere.
procedure TForm1.FormCreate(Sender: TObject);
begin
StringGrid1.AllowOutboundEvents := False;
...
end;
Another point is that you should use the OnPrepareCanvas
event instead of OnDrawCell
, because in OnDrawCell
you would have to paint everything, including text rendering. With OnPrepareCanvas
you just set the Brush.Color
and Font.Color
for each cell which is going to be rendered.
And, you don't need to use arrays, you can use the Objects
like you did with your columns, but surely you can keep the colors in the arrays. In the following example I've used the Objects
and there's also shown the usage of the OnPrepareCanvas
event, but note that this example as well as yours from the question colorize all cells including the fixed ones:
type
TCellData = class(TObject)
private
FStateYes: Boolean;
FForeground: TColor;
FBackground: TColor;
public
property StateYes: Boolean read FStateYes write FStateYes;
property Foreground: TColor read FForeground write FForeground;
property Background: TColor read FBackground write FBackground;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Col, Row: Integer;
CellData: TCellData;
begin
for Col := 0 to StringGrid1.ColCount - 1 do
for Row := 0 to StringGrid1.RowCount - 1 do
begin
CellData := TCellData.Create;
CellData.StateYes := False;
CellData.Foreground := clBlack;
CellData.Background := clWhite;
StringGrid1.Objects[Col, Row] := CellData;
end;
StringGrid1.AllowOutboundEvents := False;
end;
procedure TForm1.FormDestroy(Sender: TObject);
var
Col, Row: Integer;
begin
for Col := 0 to StringGrid1.ColCount - 1 do
for Row := 0 to StringGrid1.RowCount - 1 do
StringGrid1.Objects[Col, Row].Free;
end;
procedure TForm1.StringGrid1Click(Sender: TObject);
var
Col, Row: Integer;
CellData: TCellData;
begin
Col := StringGrid1.Col;
Row := StringGrid1.Row;
if StringGrid1.Objects[Col, Row] is TCellData then
begin
CellData := TCellData(StringGrid1.Objects[Col, Row]);
if CellData.StateYes then
begin
StringGrid1.Cells[Col, Row] := '';
CellData.StateYes := False;
CellData.Foreground := RGB(255, 255, 255);
CellData.Background := RGB(255, 255, 255);
end
else
begin
StringGrid1.Cells[Col, Row] := 'Yes';
CellData.StateYes := True;
CellData.Foreground := RGB(0, 0, 0);
CellData.Background := RGB(131, 245, 44);
end;
end;
end;
procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
aState: TGridDrawState);
var
CellData: TCellData;
begin
if StringGrid1.Objects[ACol, ARow] is TCellData then
begin
CellData := TCellData(StringGrid1.Objects[ACol, ARow]);
StringGrid1.Canvas.Brush.Color := CellData.Background;
StringGrid1.Canvas.Font.Color := CellData.Foreground;
end;
end;