14、数据感知网格:TDBGrid的深入探索与应用

数据感知网格:TDBGrid的深入探索与应用

1. 数据感知网格概述

数据感知网格是一种能同时显示多条记录信息的组件,本文将着重探讨三种不同的数据感知网格:TDBGrid、TClientDataSetGrid 和 TDBCtrlGrid。其中,TDBGrid 是 Delphi 和 Kylix 都自带的标准组件,它是基于网格的数据感知组件的基础。

2. TDBGrid 基本操作

TDBGrid 发布了 DataSource 属性,间接决定了网格从中检索数据的数据集。与一次只能显示和编辑一个字段的数据感知组件不同,TDBGrid 没有 DataField 属性,而是提供了 Columns 属性,用于指定要在网格中显示的字段、字段的顺序以及其他与显示相关的设置。

使用 TDBGrid 最简单的方法是将其拖放到窗体上,连接数据源,打开数据集,然后运行应用程序。这样会看到一个使用所有默认设置的普通网格。

2.1 自定义列

通常,最基本的自定义操作是调整显示的列数或列的显示顺序。TDBGrid 的 Columns 属性类似于数据集的 Fields 属性。如果没有专门定义列,网格将按数据集中列的出现顺序显示所有列;如果为数据集定义了持久字段,网格将仅显示这些字段的列。

要为网格创建持久列对象(类似于数据集的持久字段对象),可以使用列编辑器。在设计时双击网格组件(或右键单击并从弹出菜单中选择“Columns Editor…”)即可显示列编辑器。

每个列支持多个属性,可用于自定义列的外观和感觉,如下表所示:
| 属性 | 描述 |
| ---- | ---- |
| Alignment | 设置列中显示的数据的对齐方式,可左对齐、居中对齐或右对齐。 |
| Color | 设置单个列的背景颜色。 |
| FieldName | 指定要在该列中显示的基础数据集中的字段名称。任何字段都可以显示(包括数据字段、计算字段、查找字段和聚合字段)。 |
| Font | 自定义用于显示列数据的字体。 |
| ReadOnly | 当为 True 时,即使基础字段和数据集允许编辑,列数据也不能编辑。 |
| Title | 允许自定义列的标题单元格。 |
| Visible | 当为 False 时,列不显示。 |
| Width | 设置列在屏幕像素中的宽度。 |

当设置 FieldName 属性时,Delphi 会根据字段的大小和类型自动设置 Alignment Width 属性。但对于数字字段,Delphi 不会自动将标题的对齐方式设置为右对齐,需要手动设置。

也可以添加一个没有基础数据字段的列,插入新列并将 FieldName 属性留空即可。此时需要手动设置列的 Alignment Width 属性,并使用网格的自定义绘制功能来绘制该列的单元格内容。

2.2 列类型

大多数列以简单字符串的形式显示和编辑。对于希望用户从值列表中选择,或显示对话框让用户选择单元格值的情况,TDBGrid 支持对列的活动单元格进行两种类型的修饰:
- 列可以显示查找组合框,使用户能够从预定义的值列表中选择。如果列链接到数据集中的查找字段,当用户编辑该列时,列会自动显示可接受值的组合框。
- 列可以显示省略号按钮,可对其进行编程,使其在用户单击时显示对话框或执行其他功能。

相关属性如下表所示:
| 属性 | 描述 |
| ---- | ---- |
| ButtonStyle | 设置为 bsAuto (默认值)时,列会自动为查找字段显示组合框。可以手动将此属性设置为 bsEllipsis (显示省略号按钮)或 bsNone (抑制查找字段的组合框)。 |
| DropDownRows | 指定列的组合框下拉时要显示的最大项数。 |
| PickList | 对于未连接到查找字段的列,可以在 PickList 属性中指定可接受的字段值列表。如果使用此属性,列在编辑时会自动显示组合框(除非列的 ButtonStyle 属性设置为 bsNone )。 |

对于 ButtonStyle bsEllipsis 的列,当用户单击省略号按钮时,会触发网格的 OnEditButtonClick 事件。

2.3 列标题

除了自定义列数据的外观,还可以自定义列标题的外观。要同时更改所有列标题使用的字体,可以相应地设置网格的 TitleFont 属性。

要控制每个列标题,可以使用单个列的 Title 属性,该属性可设置以下属性:
| 属性 | 描述 |
| ---- | ---- |
| Alignment | 设置列标题的对齐方式,可左对齐、居中对齐或右对齐。 |
| Caption | 指定要在列标题中显示的文本。 |
| Color | 设置列标题的背景颜色。 |
| Font | 设置列标题中显示文本的字体。 |

2.4 网格选项

设置好要在网格中显示的列后,可以设置网格范围的选项,以确定网格的整体外观和感觉。可用选项如下表所示:
| 选项 | 描述 |
| ---- | ---- |
| dgEditing | 网格可编辑。用户必须按 F2 才能开始编辑当前单元格。请注意,个别列仍可设置为只读,以防止在这些列中进行编辑。设置 dgRowSelect 选项会自动禁用 dgEditing 。 |
| dgAlwaysShowEditor | 用户一进入单元格,网格就会自动进入编辑模式。用户无需按 F2 即可开始编辑。与 dgEditing 一样,如果设置了 dgRowSelect dgAlwaysShowEditor 会被禁用。 |
| dgTitles | 设置此选项时,会显示列标题。 |
| dgIndicator | 此选项会在网格最左侧强制显示一个窄列,显示当前记录的状态(插入、编辑或浏览模式)。 |
| dgColumnResize | 设置此选项可在运行时移动或调整单个列的大小。 |
| dgColLines | 设置时,会在列之间绘制垂直线。 |
| dgRowLines | 设置时,会在行之间绘制水平线。 |
| dgTabs | 设置时,用户可以按 Tab 和 Shift + Tab 键在网格中的单元格之间移动。清除时,按 Tab 或 Shift + Tab 会分别将焦点移动到窗体上的下一个或上一个控件。 |
| dgRowSelect | 设置时,单击一行会突出显示整行,而不是选择单个单元格。也可以通过自定义绘制网格来手动实现行突出显示。 |
| dgAlwaysShowSelection | 设置此选项可在网格没有焦点时仍突出显示当前单元格。 |
| dgConfirmDelete | 如果网格的 ReadOnly 属性未设置,此选项会在用户在网格中按 Ctrl + Delete 时使 VCL 显示删除确认消息。如果未设置此选项,用户按 Ctrl + Delete 时会删除当前记录。请注意,即使未设置 dgEditing ,按 Ctrl + Delete 也会删除当前记录。 |
| dgCancelOnExit | 此选项会影响用户从网格中移出时如何处理新插入的行。设置时,未输入任何数据的新插入行将被取消。如果未设置,留空的插入行将被提交到数据集。 |
| dgMultiSelect | 设置时,用户可以通过按 Ctrl 并单击单个行来选择网格中的多行。 |

默认情况下, Options 设置为 [dgEditing, dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgConfirmDelete, dgCancelOnExit]

2.5 事件

除了前面列出的属性,TDBGrid 还提供了多个事件,可用于更精细地控制网格的显示和功能,如下表所示:
| 事件 | 描述 |
| ---- | ---- |
| OnCellClick | 用户单击单元格时触发。用户单击标题单元格、指示器或网格背景时不会触发。 |
| OnColEnter | 焦点进入当前列后立即触发。 |
| OnColExit | 焦点离开当前列之前立即触发。在此处理程序中调用 Abort 可防止网格切换到新列。 |
| OnColumnMoved | 用户在运行时移动列(但不是调整列大小时)触发。 |
| OnDrawColumnCell | 单元格即将绘制时触发。用于实现自定义绘制。 |
| OnDrawDataCell | 已过时,仅用于向后兼容。 |
| OnEditButtonClick | 用户单击单元格中的省略号按钮时触发。 |
| OnTitleClick | 用户单击标题单元格时发生(假设设置了 dgTitles 选项)。TClientDataSetGrid 内部使用此事件,当用户单击列标题时自动对基础数据集进行排序。 |

以下示例程序展示了不同网格事件的触发时机:

unit MainForm;

interface

uses
  SysUtils, Classes, QGraphics, QControls, QForms, QDialogs, DB, QGrids,
  QDBGrids, DBClient, QExtCtrls, QStdCtrls, QDBCtrls;

type
  TfrmMain = class(TForm)
    ClientDataSet1: TClientDataSet;
    DataSource1: TDataSource;
    pnlOptions: TPanel;
    pnlClient: TPanel;
    grid: TDBGrid;
    cbEditing: TCheckBox;
    cbAlwaysShowEditor: TCheckBox;
    cbTitles: TCheckBox;
    cbIndicator: TCheckBox;
    cbColumnResize: TCheckBox;
    cbColLines: TCheckBox;
    cbRowLines: TCheckBox;
    cbTabs: TCheckBox;
    cbRowSelect: TCheckBox;
    cbAlwaysShowSelection: TCheckBox;
    cbConfirmDelete: TCheckBox;
    cbCancelOnExit: TCheckBox;
    cbMultiSelect: TCheckBox;
    btnShowSelections: TButton;
    DBNavigator1: TDBNavigator;
    lbEvents: TListBox;
    Label1: TLabel;
    Label2: TLabel;
    btnClearEventLog: TButton;
    procedure FormCreate(Sender: TObject);
    procedure gridCellClick(Column: TColumn);
    procedure gridColExit(Sender: TObject);
    procedure gridColEnter(Sender: TObject);
    procedure gridColumnMoved(Sender: TObject; FromIndex,
      ToIndex: Integer);
    procedure gridEditButtonClick(Sender: TObject);
    procedure cbEditingClick(Sender: TObject);
    procedure cbAlwaysShowEditorClick(Sender: TObject);
    procedure cbTitlesClick(Sender: TObject);
    procedure cbIndicatorClick(Sender: TObject);
    procedure cbColumnResizeClick(Sender: TObject);
    procedure cbColLinesClick(Sender: TObject);
    procedure cbRowLinesClick(Sender: TObject);
    procedure cbTabsClick(Sender: TObject);
    procedure cbRowSelectClick(Sender: TObject);
    procedure cbAlwaysShowSelectionClick(Sender: TObject);
    procedure cbConfirmDeleteClick(Sender: TObject);
    procedure cbCancelOnExitClick(Sender: TObject);
    procedure cbMultiSelectClick(Sender: TObject);
    procedure btnShowSelectionsClick(Sender: TObject);
    procedure btnClearEventLogClick(Sender: TObject);
  private
    procedure RetrieveOptions;
    procedure UpdateOption(Option: TDBGridOption; Active: Boolean);
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.xfm}

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  ClientDataSet1.LoadFromFile('C:\Employee.CDS');
  RetrieveOptions;
end;

// Options set/get methods
procedure TfrmMain.RetrieveOptions;
begin
  cbEditing.Checked := (dgEditing in grid.Options);
  cbAlwaysShowEditor.Checked := (dgAlwaysShowEditor in grid.Options);
  cbTitles.Checked := (dgTitles in grid.Options);
  cbIndicator.Checked := (dgIndicator in grid.Options);
  cbColumnResize.Checked := (dgColumnResize in grid.Options);
  cbColLines.Checked := (dgColLines in grid.Options);
  cbRowLines.Checked := (dgRowLines in grid.Options);
  cbTabs.Checked := (dgTabs in grid.Options);
  cbRowSelect.Checked := (dgRowSelect in grid.Options);
  cbAlwaysShowSelection.Checked := (dgAlwaysShowSelection in grid.Options);
  cbConfirmDelete.Checked := (dgConfirmDelete in grid.Options);
  cbCancelOnExit.Checked := (dgCancelOnExit in grid.Options);
  cbMultiSelect.Checked := (dgMultiSelect in grid.Options);
end;

procedure TfrmMain.UpdateOption(Option: TDBGridOption; Active: Boolean);
begin
  if Active then
    grid.Options := grid.Options + [Option]
  else
    grid.Options := grid.Options - [Option];
  RetrieveOptions;
end;

procedure TfrmMain.cbEditingClick(Sender: TObject);
begin
  UpdateOption(dgEditing, cbEditing.Checked);
end;

procedure TfrmMain.cbAlwaysShowEditorClick(Sender: TObject);
begin
  UpdateOption(dgAlwaysShowEditor, cbAlwaysShowEditor.Checked);
end;

procedure TfrmMain.cbTitlesClick(Sender: TObject);
begin
  UpdateOption(dgTitles, cbTitles.Checked);
end;

procedure TfrmMain.cbIndicatorClick(Sender: TObject);
begin
  UpdateOption(dgIndicator, cbIndicator.Checked);
end;

procedure TfrmMain.cbColumnResizeClick(Sender: TObject);
begin
  UpdateOption(dgColumnResize, cbColumnResize.Checked);
end;

procedure TfrmMain.cbColLinesClick(Sender: TObject);
begin
  UpdateOption(dgColLines, cbColLines.Checked);
end;

procedure TfrmMain.cbRowLinesClick(Sender: TObject);
begin
  UpdateOption(dgRowLines, cbRowLines.Checked);
end;

procedure TfrmMain.cbTabsClick(Sender: TObject);
begin
  UpdateOption(dgTabs, cbTabs.Checked);
end;

procedure TfrmMain.cbRowSelectClick(Sender: TObject);
begin
  UpdateOption(dgRowSelect, cbRowSelect.Checked);
end;

procedure TfrmMain.cbAlwaysShowSelectionClick(Sender: TObject);
begin
  UpdateOption(dgAlwaysShowSelection, cbAlwaysShowSelection.Checked);
end;

procedure TfrmMain.cbConfirmDeleteClick(Sender: TObject);
begin
  UpdateOption(dgConfirmDelete, cbConfirmDelete.Checked);
end;

procedure TfrmMain.cbCancelOnExitClick(Sender: TObject);
begin
  UpdateOption(dgCancelOnExit, cbCancelOnExit.Checked);
end;

procedure TfrmMain.cbMultiSelectClick(Sender: TObject);
begin
  UpdateOption(dgMultiSelect, cbMultiSelect.Checked);
end;

// Grid event handlers
procedure TfrmMain.gridColExit(Sender: TObject);
begin
  lbEvents.Items.Add('OnColExit - Col ' + IntToStr(grid.SelectedIndex) +
    ', Field ' + grid.SelectedField.FieldName + ')');
  // By calling Abort here, you can prevent focus from leaving this column
  // Abort;
end;

procedure TfrmMain.gridColEnter(Sender: TObject);
begin
  lbEvents.Items.Add('OnColEnter - Col ' + IntToStr(grid.SelectedIndex) +
    ', Field ' + grid.SelectedField.FieldName + ')');
end;

procedure TfrmMain.gridEditButtonClick(Sender: TObject);
begin
  lbEvents.Items.Add('OnEditButtonClick - Col ' +
    IntToStr(grid.SelectedIndex) + ', Field ' +
    grid.SelectedField.FieldName + ')');
end;

procedure TfrmMain.gridCellClick(Column: TColumn);
begin
  lbEvents.Items.Add('OnCellClick - Col ' + IntToStr(grid.SelectedIndex) +
    ', Field ' + grid.SelectedField.FieldName + ')');
end;

procedure TfrmMain.gridColumnMoved(Sender: TObject; FromIndex,
  ToIndex: Integer);
begin
  lbEvents.Items.Add('Column moved from ' + IntToStr(FromIndex) +
    ' to ' + IntToStr(ToIndex));
end;

// Command buttons
procedure TfrmMain.btnClearEventLogClick(Sender: TObject);
begin
  lbEvents.Items.Clear;
end;

procedure TfrmMain.btnShowSelectionsClick(Sender: TObject);
var
  Index: Integer;
  s: string;
begin
  if not (dgMultiSelect in grid.Options) then
    raise Exception.Create('dgMultiSelect not set');
  if grid.SelectedRows.Count = 0 then
    raise Exception.Create('No rows selected');
  for Index := 0 to grid.SelectedRows.Count - 1 do begin
    ClientDataSet1.Bookmark := grid.SelectedRows[Index];
    if s <> '' then
      s := s + #13;
    s := s + Format('%d: %s', [ClientDataSet1.FieldByName('ID').AsInteger,
      ClientDataSet1.FieldByName('Name').AsString]);
  end;
  ShowMessage(s);
end;

end.

3. 自定义绘制

网格的默认外观虽然不错,但不够醒目。使用自定义绘制可以大大改善网格的外观。

要在网格中实现自定义绘制,需要处理网格的 OnDrawColumnCell 事件。注意,网格中还有一个类似的事件 OnDrawDataCell ,这是一个过时的事件,仅用于与早期版本的 Delphi 向后兼容,不应在新的编程工作中使用。

新创建的 OnDrawColumnCell 事件处理程序如下:

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
  DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
end;

其中, Sender 参数引用网格对象, Rect 指即将绘制的单元格的边界矩形, DataCol 是即将绘制的列的绝对位置的从零开始的索引, State 是一个集合,包含一个或多个以下值:
| 值 | 描述 |
| ---- | ---- |
| gdSelected | 单元格被选中。 |
| gdFocused | 单元格具有焦点。 |
| gdFixed | 单元格是固定的(即指示器单元格)。 |

3.1 DefaultDrawing 属性

网格的 DefaultDrawing 属性决定了网格中的绘制方式。当此属性为 True (默认值)时,VCL/CLX 会像往常一样绘制网格中的每个单元格,然后将控制权传递给设置的 OnDrawColumnCell 处理程序。 OnDrawColumnCell 会为网格中的每个单元格调用,因此要确保在该事件处理程序中编写的任何代码都能快速执行。

DefaultDrawing False 时,Delphi 会用适当的背景颜色绘制单元格,并设置网格的 Brush Font 属性,以便绘制单元格。然后调用 OnDrawColumnCell

实际上,实现自定义绘制时,Delphi 的默认绘制代码通常能完成约 90% 的工作。可以简单地更改选定单元格的颜色、在给定列中绘制图像,或者将负值绘制成红色。

解决方案是将 DefaultDrawing 设置为 False ,然后在 OnCustomDrawColumn 事件处理程序中调用网格的 DefaultDrawColumnCell ,示例代码如下:

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
  DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
  if Column.FieldName = 'Salary' then begin
    if Column.Field.AsFloat > 50000.0 then begin
      DBGrid1.Canvas.Brush.Color := clYellow;
      if gdFocused in State then
        DBGrid1.Canvas.Font.Color := clRed;
    end;
  end;
  DBGrid1.DefaultDrawColumnCell(Rect, DataCol, Column, State);
end;

此代码片段仅更改 Salary 列的绘制方式。如果工资大于 50000 美元,单元格的背景将绘制成黄色;如果单元格具有焦点,工资将绘制成红色。

3.2 示例程序

以下示例程序展示了几种自定义绘制网格单元格的方法:

unit MainForm;

interface

uses
  SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, DB,
  DBClient, QGrids, QDBGrids, QExtCtrls, DateUtils;

type
  TfrmMain = class(TForm)
    pnlClient: TPanel;
    DBGrid1: TDBGrid;
    DataSource1: TDataSource;
    ClientDataSet1: TClientDataSet;
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
    procedure DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
      DataCol: Integer; Column: TColumn; State: TGridDrawState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.xfm}

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  ClientDataSet1.LoadFromFile('C:\Employee.CDS');
end;

procedure TfrmMain.DBGrid1DrawColumnCell(Sender: TObject;
  const Rect: TRect; DataCol: Integer; Column: TColumn;
  State: TGridDrawState);
var
  RetirementBirthdate: TDateTime;
  X: Integer;
begin
  if Odd(ClientDataSet1.RecNo) then
    DBGrid1.Canvas.Brush.Color := clAqua
  else
    DBGrid1.Canvas.Brush.Color := clWhite;

  if gdSelected in State then begin
    DBGrid1.Canvas.Font.Color := clGreen;
    DBGrid1.Canvas.Font.Style := [fsBold];
  end;

  if Column.ID = 0 then begin
    DBGrid1.Canvas.FillRect(Rect);
    RetirementBirthdate := IncYear(Date, -50);
    if ClientDataSet1.FieldByName('Birthday').AsDateTime <=
      RetirementBirthdate then begin
      // Eligible for retirement
      X := (Column.Width - Image1.Picture.Width) div 2 + Rect.Left;
      DBGrid1.Canvas.Draw(X, Rect.Top, Image1.Picture.Graphic);
    end;
  end else
    DBGrid1.DefaultDrawColumnCell(Rect, DataCol, Column, State);
end;

end.

在上述代码中,首先将奇数行的背景颜色绘制成 clAqua ,偶数行的背景颜色绘制成 clWhite ,使网格具有支票簿样式的外观。其次,当前行(由 State 参数包含 gdSelected 选项确定)以粗体绿色字体绘制。最后,检查员工的生日,如果员工年满 50 岁(或更大),则在第一列中绘制一个手表图标。

3.3 常见网格问题的解决方案

3.3.1 确定当前行或列

有时需要确定当前聚焦的行、列或单元格。如果只需要知道哪一行具有焦点,最简单的方法是检查基础数据集,数据集的当前记录就是具有焦点的记录。例如,要获取当前 Salary 列的值,可以这样做:

var
  CurrentSalary: Double;
begin
  CurrentSalary := DBGrid1.DataSource.DataSet.FieldByName('Salary').AsFloat;
  ...
end;

要获取聚焦列的索引,可以访问网格的 SelectedIndex 属性,它是一个从零开始的数字,表示所选列的绝对位置。要检索当前列的字段对象,可以引用网格的 SelectedField 属性。

3.3.2 获取给定鼠标坐标处的单元格

通过一些努力,可以确定任何鼠标位置处的单元格的行和列。通常不需要这样做,因为可以使用数据集的当前记录和网格的 SelectedIndex SelectedField 属性来确定当前单元格。但可能想了解非当前单元格的信息。

可以调用网格的 MouseCoord 方法,传入相对于网格控件的鼠标的 X 和 Y 坐标。 MouseCoord 会返回一个 TGridCoord 结构,其中包含代表该鼠标位置处单元格的绝对列和行索引的 X 和 Y 字段。

以下代码片段展示了如何更新标签以显示当前鼠标位置处单元格的 X 和 Y 位置以及字段名称:

procedure TForm1.DBGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  GC: TGridCoord;
  IndicatorOffset: Integer;
  TitleOffset: Integer;
begin
  GC := DBGrid1.MouseCoord(X, Y);
  if GC.X = -1 then
    Label1.Caption := IntToStr(GC.X) + ', ' + IntToStr(GC.Y)
  else begin
    if dgIndicator in DBGrid1.Options then
      IndicatorOffset := 1
    else
      IndicatorOffset := 0;
    if dgTitles in DBGrid1.Options then
      TitleOffset := 1
    else
      TitleOffset := 0;
    if GC.X < IndicatorOffset then
      Label1.Caption := IntToStr(GC.X) + ', ' + IntToStr(GC.Y) + 
        ' - Indicator'
    else
      Label1.Caption := IntToStr(GC.X) + ', ' + IntToStr(GC.Y) + ' - ' +
        DBGrid1.Columns[GC.X - IndicatorOffset].FieldName;
    // To move the dataset to the corresponding record, clone the dataset,
    // and set the clone’s RecNo property to GC.Y - TitleOffset.
  end;
end;

要检索相应单元格的数据,可以使用以下代码:

var
  CloneDS: TClientDataSet;
  FieldValue: string;
begin
  ...
  CloneDS := TClientDataSet.Create(nil);
  try
    CloneDS.CloneCursor(DBGrid1.DataSource.DataSet);
    CloneDS.RecNo := GC.Y - TitleOffset;
    // Now access the fields of the cloned dataset
    FieldValue := CloneDS.FieldByName(DBGrid1.Columns[GC.X – 
      IndicatorOffset].FieldName).AsString;
  finally
    CloneDS.Free;
  end;
end;
3.3.3 手动设置编辑模式

如果 dgAlwaysShowEditor True ,用户进入单元格时网格会自动进入编辑模式;如果为 False ,用户按 F2 时网格进入编辑模式。但如果想在用户按不同的键(如 F9 而不是 F2)时进入编辑模式,可以在网格的 OnKeyDown 事件中控制网格何时进入编辑模式。

以下代码片段展示了如何在用户按 F9 时进入编辑模式:

procedure TForm1.DBGrid1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Shift = [] then begin
    case Key of
      VK_F2: Key := 0;
      VK_F9: DBGrid1.EditorMode := True;
    end;
  end;
end;

要使此代码正常工作,必须确保 dgEditing 选项已设置。

3.3.4 检测列何时调整大小

TDBGrid 提供了 OnColumnMoved 事件,但没有 OnColumnSized 事件。可以通过编写一个具有单个重写方法的网格派生类来实现此功能,以下是 TETHDBGrid 组件的源代码:

unit ETHDBGrid;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls, Grids, DBGrids;

type
  TETHDBGrid = class(TDBGrid)
  private
    { Private declarations }
    FOnColumnSized: TNotifyEvent;
  protected
    { Protected declarations }
    procedure ColWidthsChanged; override;
  public
    { Public declarations }
  published
    { Published declarations }
    property OnColumnSized: TNotifyEvent 
      read FOnColumnSized write FOnColumnSized;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('ETH', [TETHDBGrid]);
end;

{ TETHDBGrid }
procedure TETHDBGrid.ColWidthsChanged;
begin
  inherited ColWidthsChanged;
  if Assigned(FOnColumnSized) then
    FOnColumnSized(Self);
end;

end.

重写的 ColWidthsChanged 方法会调用继承的版本,然后触发 OnColumnSized 事件。

3.3.5 持久化网格设置

用户期望自定义设置在程序调用之间保持持久,网格设置也不例外。当用户调整或重新排序网格中的列时,通常希望下次运行应用程序时列的顺序和大小保持不变。

持久化网格列设置有两种方法:
- 将设置保存到流或单独的文件中。
- 将设置保存到 Windows 注册表或 ini 文件中。

第一种方法更简单,TDBGrid 的 Columns 属性提供了 SaveToFile SaveToStream 方法,可用于将列配置保存到文件或流中。示例代码如下:

DBGrid1.Columns.SaveToFile('GRID.CFG');

重新加载设置:

DBGrid1.Columns.LoadFromFile('GRID.CFG');

如果应用程序中有多个不同的网格,可能需要使用 SaveToStream 方法将网格的列配置保存到 ini 文件或 Windows 注册表中。以下过程将列信息保存到 ini 文件中:

procedure SaveColumnConfiguration(const FileName: string; Grid: TDBGrid;
  const SectionName: string; const Name: string);
var
  ini: TIniFile;
  MemStream: TMemoryStream;
begin
  MemStream := TMemoryStream.Create;
  try
    Grid.Columns.SaveToStream(MemStream);
    MemStream.Seek(0, soFromBeginning);
    ini := TIniFile.Create(FileName);
    try
      ini.WriteBinaryStream(SectionName, Name, MemStream);
    finally
      ini.Free;
    end;
  finally
    MemStream.Free;
  end;
end;

综上所述,TDBGrid 是一个功能强大的数据感知网格组件,通过合理使用其属性、事件和自定义绘制功能,可以创建出满足各种需求的网格界面。同时,针对常见的网格操作问题,也有相应的解决方案。

4. 各种功能的操作流程总结

4.1 自定义列操作流程

graph LR
    A[将TDBGrid拖放到窗体] --> B[连接数据源]
    B --> C[打开数据集]
    C --> D[运行应用程序查看默认网格]
    D --> E{是否需要自定义列}
    E -- 是 --> F[双击网格或右键选Columns Editor打开列编辑器]
    F --> G[创建新TColumn对象]
    G --> H[设置列属性(如FieldName、Alignment等)]
    E -- 否 --> I[结束]

4.2 自定义绘制操作流程

graph LR
    A[确定要自定义绘制的网格] --> B[处理OnDrawColumnCell事件]
    B --> C{DefaultDrawing属性设置}
    C -- True --> D[VCL/CLX绘制单元格后调用处理程序]
    C -- False --> E[Delphi设置背景和属性后调用处理程序]
    D --> F[在处理程序中编写自定义绘制代码]
    E --> F
    F --> G[调用DefaultDrawColumnCell绘制单元格]

4.3 解决常见网格问题操作流程

4.3.1 确定当前行或列
  1. 若只需知道焦点行,检查基础数据集的当前记录。示例代码:
var
  CurrentSalary: Double;
begin
  CurrentSalary := DBGrid1.DataSource.DataSet.FieldByName('Salary').AsFloat;
  ...
end;
  1. 若要获取聚焦列索引,访问网格的 SelectedIndex 属性。
  2. 若要检索当前列字段对象,引用网格的 SelectedField 属性。
4.3.2 获取给定鼠标坐标处的单元格
  1. 调用网格的 MouseCoord 方法,传入鼠标的 X 和 Y 坐标,获取 TGridCoord 结构。示例代码:
procedure TForm1.DBGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  GC: TGridCoord;
  IndicatorOffset: Integer;
  TitleOffset: Integer;
begin
  GC := DBGrid1.MouseCoord(X, Y);
  if GC.X = -1 then
    Label1.Caption := IntToStr(GC.X) + ', ' + IntToStr(GC.Y)
  else begin
    if dgIndicator in DBGrid1.Options then
      IndicatorOffset := 1
    else
      IndicatorOffset := 0;
    if dgTitles in DBGrid1.Options then
      TitleOffset := 1
    else
      TitleOffset := 0;
    if GC.X < IndicatorOffset then
      Label1.Caption := IntToStr(GC.X) + ', ' + IntToStr(GC.Y) + 
        ' - Indicator'
    else
      Label1.Caption := IntToStr(GC.X) + ', ' + IntToStr(GC.Y) + ' - ' +
        DBGrid1.Columns[GC.X - IndicatorOffset].FieldName;
    // To move the dataset to the corresponding record, clone the dataset,
    // and set the clone’s RecNo property to GC.Y - TitleOffset.
  end;
end;
  1. 若要检索相应单元格的数据,克隆数据集并设置记录号,然后访问字段值。示例代码:
var
  CloneDS: TClientDataSet;
  FieldValue: string;
begin
  ...
  CloneDS := TClientDataSet.Create(nil);
  try
    CloneDS.CloneCursor(DBGrid1.DataSource.DataSet);
    CloneDS.RecNo := GC.Y - TitleOffset;
    // Now access the fields of the cloned dataset
    FieldValue := CloneDS.FieldByName(DBGrid1.Columns[GC.X – 
      IndicatorOffset].FieldName).AsString;
  finally
    CloneDS.Free;
  end;
end;
4.3.3 手动设置编辑模式
  1. 在网格的 OnKeyDown 事件中控制编辑模式。示例代码:
procedure TForm1.DBGrid1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Shift = [] then begin
    case Key of
      VK_F2: Key := 0;
      VK_F9: DBGrid1.EditorMode := True;
    end;
  end;
end;
  1. 确保 dgEditing 选项已设置。
4.3.4 检测列何时调整大小
  1. 编写具有单个重写方法的网格派生类 TETHDBGrid 。示例代码:
unit ETHDBGrid;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls, Grids, DBGrids;

type
  TETHDBGrid = class(TDBGrid)
  private
    { Private declarations }
    FOnColumnSized: TNotifyEvent;
  protected
    { Protected declarations }
    procedure ColWidthsChanged; override;
  public
    { Public declarations }
  published
    { Published declarations }
    property OnColumnSized: TNotifyEvent 
      read FOnColumnSized write FOnColumnSized;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('ETH', [TETHDBGrid]);
end;

{ TETHDBGrid }
procedure TETHDBGrid.ColWidthsChanged;
begin
  inherited ColWidthsChanged;
  if Assigned(FOnColumnSized) then
    FOnColumnSized(Self);
end;

end.
4.3.5 持久化网格设置
  1. 若保存到文件,使用 Columns.SaveToFile 方法。示例代码:
DBGrid1.Columns.SaveToFile('GRID.CFG');
  1. 若重新加载设置,使用 Columns.LoadFromFile 方法。示例代码:
DBGrid1.Columns.LoadFromFile('GRID.CFG');
  1. 若保存到 ini 文件,使用 SaveToStream 方法结合 TIniFile 。示例代码:
procedure SaveColumnConfiguration(const FileName: string; Grid: TDBGrid;
  const SectionName: string; const Name: string);
var
  ini: TIniFile;
  MemStream: TMemoryStream;
begin
  MemStream := TMemoryStream.Create;
  try
    Grid.Columns.SaveToStream(MemStream);
    MemStream.Seek(0, soFromBeginning);
    ini := TIniFile.Create(FileName);
    try
      ini.WriteBinaryStream(SectionName, Name, MemStream);
    finally
      ini.Free;
    end;
  finally
    MemStream.Free;
  end;
end;

5. 总结

TDBGrid 作为数据感知网格组件,具备丰富的功能和强大的扩展性。通过对其属性、事件和自定义绘制功能的灵活运用,能够创建出满足不同需求的网格界面。在实际开发中,遇到的常见网格问题也都有相应的解决方案,按照上述的操作流程和代码示例,开发者可以高效地实现各种网格功能。同时,合理利用自定义列、自定义绘制等功能,还能提升网格界面的美观度和用户体验。

内容概要:本文系统阐述了企业新闻发稿在生成式引擎优化(GEO)时代下的全渠道策略效果评估体系,涵盖当前企业传播面临的预算、资源、内容效果评估四大挑战,并深入分析2025年新闻发稿行业五大趋势,包括AI驱动的智能化转型、精准化传播、首发内容价值提升、内容资产化及数据可视化。文章重点解析央媒、地方官媒、综合门户和自媒体四类媒体资源的特性、传播优势发稿策略,提出基于内容适配性、时间节奏、话题设计的策略制定方法,并构建涵盖品牌价值、销售转化GEO优化的多维评估框架。此外,结合“传声港”工具实操指南,提供AI智能投放、效果监测、自媒体管理舆情应对的全流程解决方案,并针对科技、消费、B2B、区域品牌四大行业推出定制化发稿方案。; 适合人群:企业市场/公关负责人、品牌传播管理者、数字营销从业者及中小企业决策者,具备一定媒体传播经验并希望提升发稿效率ROI的专业人士。; 使用场景及目标:①制定科学的新闻发稿策略,实现从“流量思维”向“价值思维”转型;②构建央媒定调、门户扩散、自媒体互动的立体化传播矩阵;③利用AI工具实现精准投放GEO优化,提升品牌在AI搜索中的权威性可见性;④通过数据驱动评估体系量化品牌影响力销售转化效果。; 阅读建议:建议结合文中提供的实操清单、案例分析工具指南进行系统学习,重点关注媒体适配性策略GEO评估指标,在实际发稿中分阶段试点“AI+全渠道”组合策略,并定期复盘优化,以实现品牌传播的长期复利效应。
【EI复现】基于主从博弈的新型城镇配电系统产消者竞价策略【IEEE33节点】(Matlab代码实现)内容概要:本文介绍了基于主从博弈理论的新型城镇配电系统中产消者竞价策略的研究,结合IEEE33节点系统进行建模仿真分析,采用Matlab代码实现。研究聚焦于产消者(兼具发电用电能力的主体)在配电系统中的竞价行为,运用主从博弈模型刻画配电公司产消者之间的交互关系,通过优化算法求解均衡策略,实现利益最大化系统运行效率提升。文中详细阐述了模型构建、博弈机制设计、求解算法实现及仿真结果分析,复现了EI期刊级别的研究成果,适用于电力市场机制设计智能配电网优化领域。; 适合人群:具备电力系统基础知识和Matlab编程能力,从事电力市场、智能电网、能源优化等相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①学习主从博弈在电力系统中的建模方法;②掌握产消者参电力竞价的策略优化技术;③复现EI级别论文的仿真流程结果分析;④开展配电网经济调度市场机制设计的相关课题研究。; 阅读建议:建议读者结合提供的Matlab代码,深入理解博弈模型的数学表达程序实现细节,重点关注目标函数构建、约束条件处理及算法收敛性分析,可进一步拓展至多主体博弈或多时间尺度优化场景。
【BFO-BP】基于鳑鲏鱼优化算法优化BP神经网络的风电功率预测研究(Matlab代码实现)内容概要:本文研究了基于鳑鲏鱼优化算法(BFO)优化BP神经网络的风电功率预测方法,并提供了相应的Matlab代码实现。通过将生物启发式优化算法传统BP神经网络相结合,利用鳑鲏鱼算法优化BP网络的初始权重和阈值,有效提升了模型的收敛速度预测精度,解决了传统BP神经网络易陷入局部最优、训练效率低等问题。该方法在风电功率预测这一典型非线性时序预测任务中展现出良好的适用性和优越性,有助于提升风电并网的稳定性调度效率。; 适合人群:具备一定机器学习优化算法基础,从事新能源预测、电力系统调度或智能算法研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于风电场短期或超短期功率预测,提高电网调度的准确性;②作为智能优化算法神经网络结合的典型案例,用于学习BFO等群智能算法在实际工程问题中的优化机制实现方式;③为类似非线性系统建模预测问题提供可复现的技术路线参考。; 阅读建议:建议读者结合Matlab代码深入理解算法实现细节,重点关注BFO算法的种群初始化、适应度函数设计、参数更新机制及其BP网络的耦合方式,同时可通过更换数据集或对比其他优化算法(如PSO、GA)进一步验证模型性能。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值