VCL中网格控件原理分析lxpbuaa(桂枝香在故国晚秋)2004-9-15 去年还在成都的时候,因为同事工作需要,我研究了一下TDBGrid,最后有点收获,在TDBGrid中加入了固定列及相关一些(如固定列可得到焦点、可拖放、
数据可修改等)功能。前几天,有人在我的Blog(http://blog.csdn.net/lxpbuaa)上开骂:“TMD,我还准备来看点技术文章,
Delphi区大版主的Blog上除了几篇破译文就这些烂东西”(因为言词不雅,删除了。大意如此)。想想也是,所以今天抽空整理了一下原来的东西,发篇小文,对VCL中网格控件的实现原理作个简单介绍(但不会涉及给TDBGrid添加固定列等具体内容,那是三言两语说不清楚的事^@^)。欢迎指正、补充。
网格(Grid)控件,可直观描述二维信息。因此它具有横向和纵向二轴,就是一个二维表格。
一、类继承结构图 TCustomGrid / \TCustomDrawGrid TCustomDBGridTDrawGrid TDBGrid TStringGrid1、TCustomGrid为所有网格控件的父类,定义了网格控件的主要特征和网格控件的主要功能。在这里,我们着重要了解的是它的两个保护级(protected)方法:(1)
procedure Paint;所有TWinControl的子类都可通过Paint来绘制自身外形。在TCustomGrid.Paint中,主要实现两个功能:绘制网格线和填充网格
数据。其中,网格
数据的填充具体实现由下述的DrawCell完成。在后面的内容,我会结合源代码详细解释Paint。 (2)
procedure DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); virtual; abstract;这是一个纯虚方法,被Paint调用,用以实现网格
数据的填充。因此,所有TCustomGrid的子类都可以覆盖(override)这个方法,根据实际需要实现填充方式。2、TCustomDrawGrid并没有实际用处。它主要完成两件事情:(1)覆盖TCustomGrid的抽象方法加以实现。TCustomDrawGrid不再是一个抽象类。(2)添加了一些事件。比如它覆盖了TCustomGrid.DrawCell,并在其中触发了OnDrawCell事件。因此,我们在OnDrawCell中添加代码,就可以改变特定行列网格中的
数据及其填充方式。但要注意的是TCustomDrawGrid覆盖DrawCell后,并没有真正实现
数据填充(因为它还不知道
数据是什么)。简化后的DrawCell源代码如下:
procedure TCustomDrawGrid.DrawCell(ACol, ARow: Longint; ARect: TRect;AState: TGridDrawState);
begin if Assigned(FOnDrawCell)
then FOnDrawCell(Self, ACol, ARow, ARect, AState);
end;3、TDrawGrid、TStringGrid都是用户可以在
设计时使用的类,或者简单的说都是控件。但TDrawGrid是TCustomDrawGrid的一个简单包装,因此DrawCell仍然只简单地触发事件OnDrawCell,而没有真正实现
数据填充。也正因为如此,TDrawGrid的使用就相当灵活,我们可以利用它绘制文本、图形图像等多种信息。TStringGrid派生于TDrawGrid,专门用于描述文本信息。从以下源代码可以看到,它真正实现了
数据填充:
procedure TStringGrid.DrawCell(ACol, ARow: Longint; ARect: TRect;AState: TGridDrawState);
begin if DefaultDrawing
then Canvas.TextRect(ARect, ARect.Left+2, ARect.Top+2, Cells[ACol, ARow]);
{即这句} inherited DrawCell(ACol, ARow, ARect, AState);
end;4、TDBGrid是
数据敏感类的网格控件。它是对TCustomDBGrid的简单包装,而TCustomDBGrid的实现原理和普通网格控件是类似的,主要的区别在于
数据源不同。比如TStringGrid的
数据来自于TStringGrid.Cells,而TCustomDBGrid的
数据来自于TCustomDBGrid.DataSource.DataSet。
二、TCustomGrid的主要功能前面已经说了,TCustomGrid定义了网格控件的主要功能,具有网格控件的主要特征,因此要理解网格控件的基本原理,重点在于TCustomGrid的两个方法:Paint和DrawCell。DrawCell是一个纯虚方法,在Paint中被调用(具体过程参见下文),因此理解的重点是在两个地方:(1)Paint有什么用,Paint是如何运作的。(2)Paint中做了什么工作。1、Paint的运作机制。前面说过了,Paint用来绘制控件自身外形。Paint内部定义了具体的绘制方法,因此,只要在适当的时间和地点调用Paint,就可以改变控件外观。在VCL中,可将Paint方法简单理解为TControl对Windows标准消息WM_PAINT的反应。调用Win32 API中的UpdateWindow、RedrawWindow和InvalidateRect以及VCL中TControl的Repaint、Refresh和Update方法等都会直接或者间接引发相应的WM_PAINT消息。因此,网格控件的基本运作原理就是:
数据或者
数据源本身发生变化后,通过适当方式调用Paint方法,从而更新
数据填充。拿TStringGrid为例,其Cells的
数据改变后:
procedure TStringGrid.SetCells(ACol, ARow: Integer;
const Value:
string);
begin TStringGridStrings(EnsureDataRow(ARow))[ACol] := Value; EnsureColRow(ACol, True); EnsureColRow(ARow, False); Update(ACol, ARow);
{这句内部调用Win32 API的InvalidateRect标记[ACol, ARow]所指区域需要重画;系统接着就会发送一个WM_PAINT消息。最终引起Paint的执行。}end;2、Paint所做工作。先看看我简化后的源代码,更容易说清楚。以“★”为各功能部分划分标记:
procedure TCustomGrid.Paint;
procedure DrawLines(DoHorz, DoVert: Boolean; Col, Row: Longint;
const CellBounds:
array of Integer; OnColor, OffColor: TColor);
begin {……} end;
procedure DrawCells(ACol, ARow: Longint; StartX, StartY, StopX, StopY: Integer; Color: TColor; IncludeDrawState: TGridDrawState);
begin {……} {其中调用了TCustomGrid的纯虚方法DrawCell。 因此TCustomGrid的子类可以覆盖这个方法,自定义数据的填充方式} DrawCell(CurCol, CurRow, Where, DrawState);
{……} end;
begin {★0:计算网络绘制参数} CalcDrawInfo(DrawInfo);
with DrawInfo
do begin {★1:绘制网格线(如果线宽>0)} if (Horz.EffectiveLineWidth > 0)
or (Vert.EffectiveLineWidth > 0)
then begin {左上角固定列} DrawLines(goFixedHorzLine
in Options, goFixedVertLine
in Options, 0, 0, [0, 0, Horz.FixedBoundary, Vert.FixedBoundary], clBlack, FixedColor);
{横向固定列} DrawLines(goFixedHorzLine
in Options, goFixedVertLine
in Options, LeftCol,