数据感知网格: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 确定当前行或列
- 若只需知道焦点行,检查基础数据集的当前记录。示例代码:
var
CurrentSalary: Double;
begin
CurrentSalary := DBGrid1.DataSource.DataSet.FieldByName('Salary').AsFloat;
...
end;
-
若要获取聚焦列索引,访问网格的
SelectedIndex属性。 -
若要检索当前列字段对象,引用网格的
SelectedField属性。
4.3.2 获取给定鼠标坐标处的单元格
-
调用网格的
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;
- 若要检索相应单元格的数据,克隆数据集并设置记录号,然后访问字段值。示例代码:
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 手动设置编辑模式
-
在网格的
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;
-
确保
dgEditing选项已设置。
4.3.4 检测列何时调整大小
-
编写具有单个重写方法的网格派生类
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 持久化网格设置
-
若保存到文件,使用
Columns.SaveToFile方法。示例代码:
DBGrid1.Columns.SaveToFile('GRID.CFG');
-
若重新加载设置,使用
Columns.LoadFromFile方法。示例代码:
DBGrid1.Columns.LoadFromFile('GRID.CFG');
-
若保存到 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 作为数据感知网格组件,具备丰富的功能和强大的扩展性。通过对其属性、事件和自定义绘制功能的灵活运用,能够创建出满足不同需求的网格界面。在实际开发中,遇到的常见网格问题也都有相应的解决方案,按照上述的操作流程和代码示例,开发者可以高效地实现各种网格功能。同时,合理利用自定义列、自定义绘制等功能,还能提升网格界面的美观度和用户体验。
超级会员免费看
10

被折叠的 条评论
为什么被折叠?



