如何在TStringGrid中切换单元格的颜色和文本的开关

3
我正在运行Lazarus v0.9.30(32位编译器)。
我有一个TForm和一个标准的TStringGrid。网格具有以下属性设置:RowCount = 5,ColumnCount = 5,FixedCols = 0,FixedRows = 0。
我谷歌了一下代码,展示了如何在用户单击TStringGrid单元格时更改单元格颜色并添加一些文本。所有工作都很好,我稍微扩展了一下,以便在GridClick事件上切换颜色/文本。
我有的问题更多是为了更好地理解代码中一些元素的目的。
有一个前景(FG)和背景(BG)TColor对象数组。它们是否用于存储在GridClick事件上设置的单元格颜色属性,以便如果需要任何原因触发DrawCell事件,则单元格可以重新绘制自己?你是否可以避免使用TColor数组,并根据需要在DrawCell事件中设置颜色/文本?
如果需要使用数组,我会假设它们的维度必须与Grid.ColCount和Grid.RowCount匹配(即通过Form.Create中的SetLength调用设置)
有办法检测您是否单击字符串网格的5 x 5个单元格之外的空白处,从而防止GridClick调用DrawCell事件。无论您单击哪里,您始终会获得行和列的有效值。
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.
1个回答

4
如果您将 AllowOutboundEvents 设置为 False,则仅当您单击某个单元格时,才会触发 OnClick 事件,而不是单击空白处。因此,如果您使用此属性,无论何时单击某处,您都将获得有效的单元格坐标。请勿删除HTML标签。
procedure TForm1.FormCreate(Sender: TObject);
begin
  StringGrid1.AllowOutboundEvents := False;
  ...
end; 

另一个要点是,你应该使用 OnPrepareCanvas 事件而不是 OnDrawCell,因为在 OnDrawCell 中,你需要绘制所有内容,包括文字渲染。使用 OnPrepareCanvas,你只需要为每个将要呈现的单元格设置 Brush.ColorFont.Color
此外,你不需要使用数组,可以像处理列一样使用 Objects,但是你当然可以将颜色保留在数组中。在以下示例中,我使用了 Objects,同时展示了如何使用 OnPrepareCanvas 事件,但请注意,这个示例和你在问题中提到的示例都会将所有单元格(包括固定单元格)着色。
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;

1
不确定您所说的使用TCustomStringGrid对象的含义?代码编写者使用多维数组来存储TColors.....那么使用TCustomStringGrid对我有什么帮助呢? - user1174918
抱歉误导了您。已修复并添加了其他提示和示例。 - TLama
好的,我明白你在做什么了。我原本计划将每个单元格都加载一个对象,现在我只需要添加TColor属性,因为我有一个类似于“StateYes”的属性。不过我有一个问题:你没有使用网格的DrawCell事件来绘制单元格,就像这个答案中所示吗?[链接](https://dev59.com/RGw15IYBdhLWcg3wT55c) - user1174918
2
因为Delphi没有OnPrepareCanvas,它们只有OnDrawCell,在那篇帖子中可以看到,你还需要渲染文本本身。如果您只想修改要呈现的单元格的画布属性,例如背景或字体颜色,则OnPrepareCanvas是正确的阶段。关于StateYes,您当然可以使用Yes字符串进行比较,但是如果您将来想将其更改为Yeah,则必须在多个位置进行更改 :-) - TLama

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接