改进 Delphi TDBGrid






4.92/5 (14投票s)
通过添加一些新功能来改进 Delphi 的 TDBGrid。
引言
TDBGrid
自发布以来已经过去很久了,直到现在,这个组件的行为也没有发生重大变化。在本文中,我们将添加一些功能,使 TDBGrid
更加用户友好,易于使用。其中一些是新的,一些可以在互联网上找到。
背景
以下是一些可以增强传统 DBGrid
并使其更用户友好的功能:
- 根据单元格类型设置渐变背景
- 为
string
字段集成搜索功能 - 列排序
- 鼠标悬停高亮
- 自动调整列宽
- 行和列大小调整
- 支持鼠标滚轮
- 单元格自动换行
- 音效
- 集成且可配置的弹出菜单,用于对单个记录应用命令
- 支持双向模式
- 加载和保存配置
用于为我们的网格提供新外观的库是 GDI+。Delphi 中有一些免费的 GDI+ 翻译,但本组件中使用的翻译可以从 www.progdigy.com 下载,不过它已经包含在项目中了。请记住,此组件是在 Delphi 7 中开发的,其排序功能仅适用于 TCustomADODataset
的后代。它生成的默认过滤表达式与 TCustomADODataset
兼容,但在应用过滤器之前有一个用于操作过滤表达式的事件。请注意,在此组件中,我们假定第一列和第一行是固定的,其他行是数据内容,因此如果您需要其他条件,请根据需要更改 DrawCell
。最终产品看起来像这样

外观
为了创建渐变位图背景,我们首先应该在内存中创建它。然后,我们将 StrechDraw
它到每个相关的单元格。要绘制垂直渐变,只需生成一个宽度为 1 像素、高度任意的位图,高度可以从 1 像素到例如 50 像素不等。此高度将被称为 **Step**,它与渐变的分辨率具有相同的含义。函数 drawVerticalGradient
接收一个位图变量,为其分配内存,然后根据“开始颜色”、“中心颜色”和“结束颜色”绘制一个渐变条,其中心位置是可调的。
procedure TEnhDBGrid.drawVerticalGradient(var grBmp: TBitmap; gHeight: integer;
color1, color2, color3: TColor; centerPosition: integer);
var
graphics: TGPGraphics;
linGrBrush: TGPLinearGradientBrush;
r1, g1, b1, r2, g2, b2, r3, g3, b3: byte;
colors: array [0 .. 2] of TGPColor;
blendPoints: array [0 .. 2] of single;
begin
try
if Assigned(grBmp) then
grBmp.Free;
grBmp := TBitmap.create;
grBmp.Width := 1;
grBmp.Height := gHeight;
extractRGB(color1, r1, g1, b1);
extractRGB(color2, r2, g2, b2);
extractRGB(color3, r3, g3, b3);
graphics := TGPGraphics.create(grBmp.Canvas.Handle);
linGrBrush := TGPLinearGradientBrush.create(MakePoint(0, 0),
MakePoint(0, gHeight), MakeColor(255, 255, 255, 255),
MakeColor(255, 255, 255, 255));
colors[0] := MakeColor(r1, g1, b1);
blendPoints[0] := 0;
colors[1] := MakeColor(r2, g2, b2);
blendPoints[1] := centerPosition / 100;
colors[2] := MakeColor(r3, g3, b3);
blendPoints[2] := 1;
linGrBrush.SetInterpolationColors(@colors, @blendPoints, 3);
graphics.FillRectangle(linGrBrush, 0, 0, 1, gHeight);
linGrBrush.Free;
graphics.Free;
except
OutputDebugString('Error in creating gradient.');
end;
end;
我们有这 5 种渐变,在必要时使用 StrechDraw
绘制它们。
grBmpTitle: TBitmap;
grBmpSelected: TBitmap;
grBmpActive: TBitmap;
grBmpAlt1: TBitmap;
grBmpAlt2: TBitmap;
grBmpTitle
用于固定单元格背景。grBmpSelected
用于绘制选定项背景,grBmpActive
用于绘制活动单元格背景,grBmpAlt1
和 grBmpAlt1
用于交替绘制普通行背景。
使用这些位图之一的过程是重写的 DrawColumnCell
过程。
row := DataSource.DataSet.recNo;
if (gdSelected in State) then
begin
Canvas.StretchDraw(Rect, grBmpActive);
tempFont.Color:=FActiveCellFontColor;
end
else if isMultiSelectedRow then
begin
Canvas.StretchDraw(Rect, grBmpSelected);
tempFont.Color:=FSelectedCellFontColor;
end
else if Odd(row) then
Canvas.StretchDraw(Rect, grBmpAlt1);
else
Canvas.StretchDraw(Rect, grBmpAlt2);
if Column.Field<>nil then
myDrawText(Column.Field.DisplayText, Canvas, Rect, Column.alignment, tempFont);
myDrawText
透明地绘制一个 string
,如果其宽度大于绘图矩形宽度,它将换行,并在后续行中尽可能多地写入单词。
要绘制标题栏、固定单元格和指示器,我们应该重写 DrawCell
。指示器形状在 Data.res 中,它是项目的一部分。
if ARow > 0 then //draw contents
begin
if ACol = 0 then // draw indicators
begin
dec(ARow);
Canvas.StretchDraw(ARect, grBmpTitle);
// shape borders like a button
DrawEdge(Canvas.Handle, ARect, BDR_RAISEDOUTER, BF_RECT);
if (gdFixed in AState) then
begin
if Assigned(DataLink) and DataLink.Active then
begin
MultiSelected := False;
if ARow >= 0 then
begin
prevousActive := DataLink.ActiveRecord;
try
Datalink.ActiveRecord := ARow;
MultiSelected := isMultiSelectedRow;
finally
Datalink.ActiveRecord := prevousActive;
end;
end;
if (ARow = DataLink.ActiveRecord) or MultiSelected then
begin
indicIndex := 0;
if DataLink.DataSet <> nil then
case DataLink.DataSet.State of
dsEdit: indicIndex := 1;
dsInsert: indicIndex := 2;
dsBrowse:
if MultiSelected then
if (ARow <> Datalink.ActiveRecord) then
indicIndex := 3
else
indicIndex := 4; // multiselected and current row
end;
myIndicators.BkColor := FixedColor;
myLeft := ARect.Right - myIndicators.Width - 1;
if Canvas.CanvasOrientation = coRightToLeft then Inc(myLeft);
myIndicators.Draw(Canvas, myLeft,
(ARect.Top + ARect.Bottom - myIndicators.Height) shr 1,
indicIndex, dsTransparent, itImage,True);
end;
end;
end;
inc(ARow);
end
else // draw grid content
inherited;
end
else // draw titles
begin
// draw title gradient bitmap
Canvas.StretchDraw(ARect, grBmpTitle);
ar:=ARect;
// shape borders like a button
DrawEdge(Canvas.Handle, AR, BDR_RAISEDOUTER, BF_RECT);
// write title
if ACol > 0 then
myDrawText(Columns[ACol - 1].Title.Caption,
Canvas, AR, Columns[ACol - 1].Title.Alignment , Columns[ACol - 1].Title.Font)
end;
myDrawText
使用 DrawText
API,因为它具有对齐和自动换行功能。
procedure TEnhDBGrid.myDrawText(s:string; outputCanvas: Tcanvas; drawRect: TRect;
drawAlignment:TAlignment ; drawFont:TFont);
const
drawFlags : array [TAlignment] of Integer =
(DT_WORDBREAK or DT_LEFT or DT_NOPREFIX,
DT_WORDBREAK or DT_RIGHT or DT_NOPREFIX,
DT_WORDBREAK or DT_CENTER or DT_NOPREFIX );
var
r:trect;
bw, bh, cw, ch, difX:integer;
begin
if s='' then
exit;
if UseRightToLeftAlignment then
case drawAlignment of
taLeftJustify: drawAlignment := taRightJustify;
taRightJustify: drawAlignment := taLeftJustify;
end;
r:= drawRect;
cw:=ClientWidth;
ch:=ClientHeight;
//set dimensions for output
bmpDrawText.Width:=( r.Right - r.Left);
bmpDrawText.Height:=r.Bottom- r.Top;
bw:=bmpDrawText.Width;
bh:=bmpDrawText.Height;
//set drawing area in output bmp
drawRect.Left:=0;
drawRect.Top:=0;
drawRect.Right:=bw;
drawRect.Bottom:=bh;
// if the drawing font color is same as transparent color
//change transparent color
if ColorToRGB( drawFont.Color )=(ColorToRGB
( bmpDrawText.TransparentColor) and $ffffff) then
toggleTransparentColor;
//to make entire surface of canvas transparent
bmpDrawText.Canvas.FillRect(drawRect);
//shrink the rectangle
InflateRect(drawRect, -2,-2);
bmpDrawText.Canvas.Font:= drawFont;
DrawText(bmpDrawText.Canvas.Handle,
pchar(s), length(s), drawRect,
drawFlags[drawAlignment]
);
if UseRightToLeftAlignment then
begin
if r.Right > ClientWidth then
begin
bmpClipped.Width:=cw-r.Left;
bmpClipped.Height:=bh;
bmpClipped.Canvas.CopyRect(bmpClipped.Canvas.ClipRect,
bmpDrawText.Canvas, Rect(bw, 0, bw-( cw - r.Left ), bh) );
outputCanvas.StretchDraw(rect(r.Left , r.Top, cw, r.Bottom), bmpClipped);
end
else
outputCanvas.StretchDraw(Rect(r.Right, r.Top, r.Left, r.Bottom), bmpDrawText);
end
else
outputCanvas.Draw(r.Left, r.top, bmpDrawText);
end;
当 BiDiMode
为 RightToLeft
时,Canvas.Draw
会反转绘制我们的 bmpDrawText
。要解决此问题,应使用一个矩形调用 StretchDraw
,该矩形的左右边界坐标已互换。
搜索
如果用户想搜索 string
字段,就像右键单击 string
字段标题栏并键入所需语句的一部分一样简单。它将更新 dataset
的 Sort
属性,以仅显示所需的结果。用户可以通过按 Escape 键取消过滤。
因此,我们在 Create
过程中创建一个 TEditBox
和一些控制变量开始。
edtSearchCriteria := TEdit.create(Self);
edtSearchCriteria.Width := 0;
edtSearchCriteria.Height := 0;
edtSearchCriteria.Parent := Self;
edtSearchCriteria.Visible := false;
searchVisible := false;
lastEditboxWndProc := edtSearchCriteria.WindowProc;
edtSearchCriteria.WindowProc := edtSearchCriteriaWindowProc;
filtering := false;
下一步是检测标题栏上的鼠标右键单击并显示搜索编辑框。因此,我们重写 MouseDown
过程。
// detect right clicking on a column title
if (Button = mbRight) and FAllowFilter then
begin
for i := 0 to Columns.Count - 1 do
begin
r := CellRect(i + 1, 0);
mp := CalcCursorPos;
// if mouse in column title
if pointInRect(mp, r) then
begin
if (Columns[i].Field.DataType = ftString) or
(Columns[i].Field.DataType = ftWideString) then
begin
if not(filtering and (lastSearchColumn = Columns[i])) then
ClearFilter;
lastSearchColumn := Columns[i];
edtSearchCriteria.Visible := true;
searchVisible := true;
if searchFieldName <> Columns[i].FieldName then
searchFieldName := Columns[i].FieldName
else
edtSearchCriteria.Text := lastSearchStr;
edtSearchCriteria.Font := Columns[i].Title.Font;
edtSearchCriteria.Left := r.Left;
edtSearchCriteria.top := r.top;
edtSearchCriteria.Width := r.Right - r.Left;
edtSearchCriteria.Height := r.bottom - r.top;
filtering := true;
LeftCol:=myLeftCol;
windows.SetFocus(edtSearchCriteria.Handle);
break;
end;
end;
end;
end;
如果您想将非 string
字段添加到允许的过滤字段中,您应该修改上述过程以处理这些字段。
为了在网格滚动时移动编辑框,我们只需在 DrawCell
过程中设置 edtSearchCriteria
坐标。
// make search editbox visible if it is necessary
if lastSearchColumn <> nil then
if (ACol > 0) and (ARow = 0) then
begin
if searchVisible then
begin
edtSearchCriteria.Visible :=isVisibleColumn(lastSearchColumn);
// reposition edit box
if (Columns[ACol - 1].FieldName = searchFieldName) then
begin
// adjust search edit box position
ar := CellRect(ACol, 0);
if edtSearchCriteria.Visible then
begin
if UseRightToLeftAlignment then
edtSearchCriteria.Left := ClientWidth - ARect.Right
else
edtSearchCriteria.Left := ARect.Left;
edtSearchCriteria.Width := ARect.Right - ARect.Left;
end;
end;
end
end;
输入到编辑框中的 string
应该作为 Filter
应用到 Dataset
。执行此操作的地方是处理传递给 edtSearchCriteria
的消息的 edtSearchCriteriaWindowProc
。
// there was a change in search criteria
if lastSearchStr<>edtSearchCriteria.Text then
begin
if filtering then
begin
plc := leftCol;
lastSearchStr := edtSearchCriteria.Text;
psp:=edtSearchCriteria.SelStart;
if lastSearchStr <> '' then
begin
DataSource.DataSet.Filtered := false;
critStr := '[' + searchFieldName + '] LIKE ''%' + lastSearchStr + '%''';
//critStr := '[' + searchFieldName + '] = ''' + lastSearchStr + '*''';
if Assigned(FOnBeforeFilter) then
FOnBeforeFilter(Self, lastSearchColumn, lastSearchStr, critStr);
DataSource.DataSet.Filter := critStr;
try
DataSource.DataSet.Filtered := true;
except
ShowMessage('Couldn''t filter data.');
end;
end
else
begin
DataSource.DataSet.Filtered := false;
end;
leftCol := plc;
if not edtSearchCriteria.Focused then
begin
windows.SetFocus(edtSearchCriteria.Handle);
edtSearchCriteria.SelStart:=psp;
end;
end;
end;
它在应用过滤器之前调用 OnBeforeFilter
,以防用户想要操作 Filter string
。此外,它还处理特殊字符,如 Escape 键和上下箭头键,以将焦点切换到网格。
if Message.Msg = WM_KEYDOWN then
begin
if Message.WParam = VK_ESCAPE then
begin
playSoundInMemory(FEscSoundEnabled, sndEsc, 'Escape');
// first escape disappears the search box
// second escape disables searches and sorting
if searchVisible then
begin
// there are some remaining messages that cause windows to play an
// exclamation sound because editbox is not visible after this.
// by removing remaining messages we prevent that unwanted sounds
while (GetQueueStatus(QS_ALLINPUT)) > 0 do
PeekMessage(Msg, 0, 0, 0, PM_REMOVE);
edtSearchCriteria.Visible := false;
searchVisible := false;
edtSearchCriteria.invalidate;
end
else
ClearFilter;
end
else if (Message.WParam = VK_DOWN) then
begin
// if user presses down arrow it means that he/she needs to go forward
// in records
DataSource.DataSet.Next;
windows.SetFocus(Handle);
end
else if (Message.WParam = VK_UP) then
begin
DataSource.DataSet.Prior;
windows.SetFocus(Handle);
end;
end;
排序
虽然许多网站都提到了如何在 DBGrid
中排序数据,但我们在这里仍然会讨论它,因为 TEnhDBGrid
具有此功能,并且应该描述其功能。
排序仅适用于 TCustomADOGrid
的后代 DataSets
。此网格对单击标题的每一列进行排序,并在此之后跟踪升序或降序模式。此外,它会显示一个箭头来指示排序的列和排序的类型。为此重写的过程是 TitleClick
。
Type
TSortType = (stNone, stAsc, stDesc);
procedure TEnhDBGrid.TitleClick(Column: TColumn);
var
p: pointer;
plc: integer; // previous left column
begin
inherited;
if not(DataSource.DataSet is TCustomADODataSet) then
Exit;
plc := leftCol;
p := DataSource.DataSet.GetBookmark;
if lastSortColumn <> Column then
begin
// new column to sort
lastSortColumn := Column;
lastSortType := stAsc;
try
TCustomADODataset(DataSource.DataSet).Sort := '[' + Column.FieldName + '] ASC';
except
ShowMessage('Didn''t sorted !');
lastSortColumn := nil;
lastSortType := stNone;
end;
end
else
begin
// reverse sort order
if lastSortType = stAsc then
begin
lastSortType := stDesc;
TCustomADODataset(DataSource.DataSet).Sort := '[' + Column.FieldName + '] DESC';
end
else
begin
lastSortType := stAsc;
TCustomADODataset(DataSource.DataSet).Sort := '[' + Column.FieldName + '] ASC';
end;
end;
if DataSource.DataSet.BookmarkValid(p) then
begin
DataSource.DataSet.GotoBookmark(p);
DataSource.DataSet.FreeBookmark(p);
end;
leftCol := plc;
end;
为了显示指示排序列和排序类型的箭头,DrawCell
是执行此操作的合适位置。
// draw an arrow in sorted columns
if (lastSortColumn <> nil) then
if (lastSortColumn.Index + 1 = ACol) and (ARow = 0) then
drawTriangleInRect(ARect, lastSortType, Columns[ACol - 1].Title.Alignment);
drawTriangleInRect
顾名思义,根据排序类型在已排序列的标题中绘制一个三角形。如果您对如何显示标题栏中的排序类型有更艺术的想法,请根据您的意愿修改 drawTriangleInRect
过程。
鼠标悬停高亮
为了实现鼠标悬停高亮行为,我们应该确定鼠标下方的行号,并将数据库的 RecNo
移动到该位置。在原始的 DBgrid
中,记录编号和边界行矩形之间没有关系,因此我们不得不制作一个已绘制记录及其位置的列表,并在可见记录更改时更新它们。所以我们有一个行信息数组。
type
TRowInfo = record
recNo,
top,
bottom: integer;
end;
{**************************}
{ class members }
{**************************}
ri: array of TRowInfo;
lastRowCount: integer;
每当 lastRowCount
与 RowCount
不同时,我们都会重新分配数组并开始更新其内容。更新发生在 DrawColumnCell
。
if RowCount <> lastRowCount then
begin
SetLength(ri, RowCount);
lastRowCount := RowCount;
// reset all records
for i := 0 to RowCount - 1 do
begin
ri[i].recNo := -1;
ri[i].top := 0;
ri[i].bottom := 0;
end;
end;
// find first empty rowInfo element or same row position
// and store this row info
for i := 0 to RowCount - 1 do
if (ri[i].recNo = -1) OR
((ri[i].top = Rect.top) AND (ri[i].bottom = Rect.bottom)) then
begin
ri[i].recNo := row;
ri[i].top := Rect.top;
ri[i].bottom := Rect.bottom;
break;
end;
现在我们有了记录编号和行的可见位置之间的关系,所以我们可以通过重写 MouseMove
来在鼠标移动时进行鼠标悬停高亮。
if FHotTrack then
if DataSource.DataSet.State = dsBrowse then //do not bother user edit
//or insert operations
begin
// prevent repetitive mouse move events
if (lastMouseX = X) and (lastMouseY = Y) then
Exit
else
begin
lastMouseX := X;
lastMouseY := Y;
end;
// move to the suitable row
// ri was filled in CellDraw
for i := 0 to high(ri) do
if (Y >= ri[i].top) and (Y <= ri[i].bottom) then
begin
if ri[i].recNo < 1 then
continue;
// movebackward or forward to reach to the pointer
// you could set RecNo exactly to the desired no to
// see the disastrous results
if ri[i].recNo > DataSource.DataSet.recNo then
begin
while ri[i].recNo > DataSource.DataSet.recNo do
DataSource.DataSet.Next;
break;
end
else if ri[i].recNo < DataSource.DataSet.recNo then
begin
while ri[i].recNo < DataSource.DataSet.recNo do
DataSource.DataSet.Prior;
break;
end
end;
// if row select is not enabled
if not(dgRowSelect in Options) then
begin
// move to cell under mouse pointer
gc := MouseCoord(X, Y);
if (gc.X > 0) and (gc.Y > 0) then
begin
gr.Left := gc.X;
gr.Right := gc.X;
gr.top := gc.Y;
gr.bottom := gc.Y;
Selection := gr;
end;
end;
// update indicator column
InvalidateCol(0);
end;
自动调整列宽
与排序一样,许多网站都提到了这一点,如果您不感兴趣,可以跳过此部分。
我们的自动宽度调整发生在用户双击列的右侧边界时,不仅仅是在标题栏上,当然也可以是整个列的右边框。
为了实现这一点,我们必须重写 DblClick
方法。
plc := leftCol;
p := CalcCursorPos;
// find the column that should be auto widthed
for i := 0 to Columns.Count - 1 do
begin
r := CellRect(i + 1, 0);
// if you want just title DblClicks uncomment this line
// if (p.Y>=r.Top) and (p.Y<=r.Bottom) then
begin
if (UseRightToLeftAlignment and (abs(p.X - r.Left) < 5)) or
((not UseRightToLeftAlignment) and (abs(p.X - r.Right) < 5)) then
begin
autoFitColumn(i, true);
leftCol := plc;
// don't allow an extra click event
dblClicked := true;
break;
end
end;
end;
此外,用户可以通过双击零行零列的第一个单元格来自动调整所有列的宽度。
// if cell is the corner one then autofit all columns
if pointInRect(p, CellRect(0, 0)) then
begin
autoFitAll;
Exit;
end;
如上代码所示,左侧列索引被保留,并且在自动调整列宽度后不会改变。
行和列大小调整
为此,我们应该重写 CalcSizingState
过程,并允许父 Grid
对象调整行和列的大小。
决定是否允许调整列大小
for i := myLeftCol - 1 to Columns.Count - 1 do
if abs(getColumnRightEdgePos(Columns[i]) - X) < 5 then
begin
State := gsColSizing;
Index := i + 1;
if IsRightToLeft then
SizingPos := ClientWidth - X
else
SizingPos := X;
SizingOfs := 0;
end;
决定是否允许调整行大小
if FAllowRowResize then
if State <> gsColSizing then
for i := 0 to high(ri) do
begin //search rows bottom line positions
if (abs(ri[i].bottom - Y) < 3) and (ri[i].bottom>0) then
begin
State := gsRowSizing;
Index := i + 1;
SizingPos := Y;
SizingOfs := 0;
lastResizedRow := Index;
Break;
end;
end;
防止在单元格区域外进行大小调整
if MouseCoord(x,y).X=-1 then
exit;
支持鼠标滚轮
当鼠标滚轮滚动时,Windows 会向控件发送 WM_MOUSEWHEEL
,我们应该将 dataset
当前记录移动到下一个或上一个位置,为了用户更舒适,如果用户在转动鼠标滚轮时按住 Ctrl 键,则水平滚动。我们将要重写的过程是 WndProc(var Message: TMessage)
。
// the control should have focus to receive this message
if Message.Msg = WM_MOUSEWHEEL then
begin
ctrlPressed := ((Message.WParam and $FFFF) and (MK_CONTROL)) > 0;
if Message.WParam < 0 then
begin
if not checkDBPrerequisites then
Exit;
if ctrlPressed then
begin
// horizontal scroll
incLeftCol;
end
else
begin
// vertical scroll
if not DataSource.DataSet.Eof then
begin
DataSource.DataSet.Next;
InvalidateCol(0);
end;
end;
end
else
begin
if not checkDBPrerequisites then
Exit;
if ctrlPressed then
// horizontal scroll
decLeftCol
else
begin
// vertical scroll
if not DataSource.DataSet.Bof then
begin
DataSource.DataSet.Prior;
InvalidateCol(0);
end;
end;
end;
end;
控件必须获得焦点才能接收 WM_MOUSEWHEEL
,因此我们必须提供一些自动获取焦点的方法,如果用户将指针移到此网格上。实现此功能的地点是 MouseMove
。
// if need auto focus then take focus to this control
if (not searchVisible) and FAutoFocus and (not Focused) then
windows.SetFocus(Handle);
单元格自动换行
在 myDrawText
中,通过在调用 DrawText
时使用 DT_WORDBREAK
来实现自动换行。事实上,在 RightToLeft
模式下使用 DrawText
函数将文本直接绘制到控件画布上时,存在一些关于反转坐标的问题。为了解决这个问题,我们应该在 TBitmaps
画布上绘制文本,然后将其绘制到控件画布上。这样,我们也将获得双缓冲输出。
音效
用户将在“鼠标悬停”、“双击”、“排序”和“按 Escape 键”时听到声音。显然,我们应该在 KeyDown
、DblClick
、TitleClick
和 Scroll
过程中异步播放声音。我们将要调用的过程是 playSoundInMemory
。
procedure TEnhDBGrid.playSoundInMemory(cnd: boolean; m: TResourceStream;
name: string);
begin
try
if cnd then
sndPlaySound(m.Memory, SND_MEMORY or SND_ASYNC);
except
OutputDebugString(PChar('Error in playing ' + name + ' sound !'));
end;
end;
声音嵌入在 Data.res 中,并在 Create
过程中加载。
try
sndHover := TResourceStream.create(HInstance, 'hover', RT_RCDATA);
sndDblClick := TResourceStream.create(HInstance, 'dblclick', RT_RCDATA);
sndSort := TResourceStream.create(HInstance, 'click', RT_RCDATA);
sndEsc := TResourceStream.create(HInstance, 'esc', RT_RCDATA);
except
OutputDebugString('Error in loading sounds from resources');
end;
集成且可配置的弹出菜单,用于对单个记录应用命令
程序员在放置 TDabaseGrid
后通常要做的常见工作是提供一些方法来对 dataset
的单个记录执行操作。为了简化这项工作,有一个可自定义的弹出菜单和一个回调机制,可以加速实现记录上的常见操作。
存储命令标题及其值的成员变量是 FPopupMenuCommands
,它是一个 TStrings
对象,并保存 CommandTitle, CommandID
对的列表。

这将生成一个如下所示的弹出菜单:

当用户单击弹出菜单项时,控件会触发此事件。
TOnPopupCommandEvent = procedure(Sender: TObject; commandID, rowNo: integer ) of object;
虽然 TEnhDBGrid.DataSource.Dataset
显示当前活动行,但 dataset
的当前行号会传递给事件处理程序。
加载和保存配置
在 saveConfig(fn: String)
和 loadConfig(fn: String)
这两个过程中,我们保存了此组件的一些视觉属性。可以修改它们以保存和加载您认为遗漏的其他属性。
结语
我希望这个组件能为您的数据库应用程序带来新的、有吸引力的外观。任何错误报告或建议都将受到欢迎和赞赏。已包含一个示例项目来测试此组件。
历史
- 2011 年 5 月 20 日:初始发布