今天终于有点时间来写这个内容了,哈哈!先来浅析下StringGrid。
TStringGrid = class(TDrawGrid) = class(TCustomDrawGrid) = class(TCustomGrid) = class(TCustomControl)
可以看到这是一个自定义的控件,这也体现了MFC(或者说Windows的API)与vcl的不同,虽然最终都调用RegisterClass,但是明显DELPHI更好及容易实现。
TCustomGrid,这是一个自定义GRID,再他身上基本实现了GRID表格功能,只是把DrawCell抽象给子类了。
这是他的画图信息,非常的学习例子:
TGridAxisDrawInfo = record
EffectiveLineWidth: Integer; 线宽,默认为1,可以设置为0
FixedBoundary: Integer; 固定边界线 = (ColWidth + EffectiveLineWidth) * FixedCount
GridBoundary: Integer; Grid边界线 = FixedBoundary + (ColWidth + EffectiveLineWidth) * CellCount,如果大于GridExtent,则GridExtent
GridExtent: Integer; GRID大小,包括最后未完整显示的表格,如:ClientWidth,ClientHeight
LastFullVisibleCell: Longint; 最后完整显示的单元格编号,如有未完整的单元格,则是它的上一个
FullVisBoundary: Integer; 最后完整显示的单元格边界
FixedCellCount: Integer; 固定表格宽度FixedCount,如:FixedRow
FirstGridCell: Integer; 第一个表格(它总是完整的显示),如:TopLeft.x
GridCellCount: Integer; 表格宽度,如ColCount,RowCount
GetExtent: TGetExtentsFunc; 取得元素大小函数,比如:ColWidths[];
end;
当GridBoundary = GridExtent 时, 则FullVisBoundary < GridBoundary,LastFullVisibleCell < GridCellCount
当GridBoundary < GridExtent 时, 则FullVisBoundary = GridBoundary,LastFullVisibleCell = GridCellCount
继承TCustomControl往往的重点在Paint函数上,TCustomGrid也不例外,这个PAINT用了几个技术,非常值得学习:
第一,小内存堆栈分配变量数据。StackAlloc和StackFree
第二,多点连接使用一个polypolyline函数,只画一次
整个GRID一个最重要策略是化矩阵为向量,化整为零。
再看TCustomDrawGrid是一个真正意义可使用的类,实现了父类几个虚拟方法(RowMoved,SelectCell)和一个抽象方法(DrawCell).
由此TDrawGrid是一个提供给用户使用的GRID,并且已经注册到DELPHI的控制面板了。
可以看到,自TDrawGrid以上所有的类,一个缺点就是纯粹意义上的GRID,没有提供持久层的支持,所以DELPHI又实现了一个类,TStringGrid,他提供这样一个持久可保存的数据界面:
TStringSparseList类,本质是一个稀梳数组:TSecDir = array[0..4095] of Pointer;
他的生存意义在于如果某一行为空,则不用为他构建数据。注意:TStringGrid是以行单位为存储的。
这样,如果你的数据已经有自己界面,那么完全可以使用TDrawGrid,并且使用它比TStringGrid速度有很大的优势。
另外这个在TCustomGrid就为我们提供了一个可编辑的功能:TInplaceEdit对象,他是继承TCustomMaskEdit,也就是说相当于提供了一个编辑窗口类(edit),值得一提的是,TInplaceEdit并是唯一的TCustomGrid使用的编辑框,他也有一个继承类:TInplaceEditList(这个名字"List"似乎有点让人以为是管理者,其实他表示一个pick list,即弹出列表框),而TInplaceEditList这个重要的功能是由于有这样一个属性:
TInplaceEditList = class(TInPlaceEdit)
private
FButtonWidth: Integer;
FPickList: TCustomListbox;
FActiveList: TWinControl;
FEditStyle: TEditStyle;//这个就是本类最重要的功能,定义:TEditStyle = (esSimple, esEllipsis, esPickList);普通EDIT,带"..."按钮EDIT,带ListBox的EDIT。
我们的想象再次打开下,如果我们很多关于“EDIT”类在此基础上实现,那就容易多了,并且这样是可行。当然,我们也可直接使用此类。
再回来,今天重点是TStringSparseList类。请看:
TSparsePointerArray = class(TObject)
private
secDir: PSecDir; 数据节目录
slotsInDir: Word; 插口数
indexMask, secShift: Word; 索引掩码和节掩码,对于一个Index来说,低indexMask位是目录中索引,高secShift位是节掩码
FHighBound: Integer; 索引最大值,也是由低indexMask位和高secShift位组成
FSectionSize: Word; 节大小,和低indexMask位相关:FSectionSize = indexMask + 1
cachedIndex: Integer; 索引缓存
cachedPointer: Pointer; 数据缓存与索引缓存共同作用
{ Return item[i], nil if slot outside defined section. }
function GetAt(Index: Integer): Pointer; 取数据
{ Return address of item[i], creating slot if necessary. }
function MakeAt(Index: Integer): PPointer; 生成数据
{ Store item at item[i], creating slot if necessary. }
procedure PutAt(Index: Integer; Item: Pointer); 存数据
public
这个类还有重要的遍历方法:
function ForAll(ApplyFunction: Pointer {TSPAApply}): Integer;
ApplyFunction本身也是一个方法,但是由于这个函数要使用到类变量,因此这个函数只能是:function(TheIndex: Integer; TheItem: Pointer): Integer; far;
ForAll的功能是:从第一个插口遍历执行ApplyFunction,除非ApplyFunction返回0或插口达到最后。
另外这里还有一个TSparseList类:TSparseList = class(TObject).
它提供了一种ForAll变向继承方法:
function TSparseList.ForAll(ApplyFunction: Pointer {TSPAApply}): Integer; assembler;
asm
MOV EAX,[EAX].TSparseList.FList
JMP TSparsePointerArray.ForAll
end;
当然,其实这里直接可以使用:FList.ForAll(ApplyFunction);不知道这地方为什么没有这样写.
至此,现在我们毕竟还不是直接使用上面的稀梳矩阵,那么下类是真正可使用的类:
TStringSparseList = class(TStrings)
private
FList: TSparseList; { of StrItems }
FOnChange: TNotifyEvent;
protected
function Get(Index: Integer): String; override;
function GetCount: Integer; override;
function GetObject(Index: Integer): TObject; override;
procedure Put(Index: Integer; const S: String); override;
procedure PutObject(Index: Integer; AObject: TObject); override;
procedure Changed;
public
可以查看到这个TStrings数据存储格式,与TStringList一样:
type
PStrItem = ^TStrItem;
TStrItem = record
FObject: TObject;
FString: string;
end;
由于时间关系,不多说,现在我直接实现这两种排序方式:
procedure TStringGridHzg.SortByCol(ACol: Integer; IsDecOrder: Boolean);
begin
if ACol > ColCount - 1 then
InvalidOp('ACol is Out of Range in TStringGridHzg.QuickSort');
QuickSort(ACol, 0, RowCount - 1, CompareStr);
Invalidate;
end;
稀梳矩阵方式排序
procedure TStringGridHzg.QuickSort(ACol, L, R: Integer; SCompare: TStringsSortCompare);
var
I, J, P: Integer;
begin
repeat
I := L;
J := R;
P := (L + R) shr 1;
repeat
while SCompare(Cols[ACol][I], Cols[ACol][P]) < 0 do Inc(I);
while SCompare(Cols[ACol][J], Cols[ACol][P]) > 0 do Dec(J);
if I <= J then
begin
TSparseListHzg(FData).Exchange(I,J); //惟一区别的地方
if P = I then
P := J
else if P = J then
P := I;
Inc(I);
Dec(J);
end;
until I > J;
if L < J then QuickSort(ACol,L, J, SCompare);
L := I;
until I >= R;
end;
普通排序
procedure TStringGridHzg.QuickSort(ACol, L, R: Integer; SCompare: TStringsSortCompare);
var
I, J, P: Integer;
procedure Exchange(ASour,ADest: TStrings);
var
tmpStr: TStrings;
begin
tmpStr := TStringList.Create;
try
tmpStr.Assign(ASour);
ASour.Assign(ADest);
ADest.Assign(tmpStr);
finally
tmpStr.Free;
end;
end;
begin
repeat
I := L;
J := R;
P := (L + R) shr 1;
repeat
while SCompare(Cols[ACol][I], Cols[ACol][P]) < 0 do Inc(I);
while SCompare(Cols[ACol][J], Cols[ACol][P]) > 0 do Dec(J);
if I <= J then
begin
Exchange(Rows[I],Rows[J]);
if P = I then
P := J
else if P = J then
P := I;
Inc(I);
Dec(J);
end;
until I > J;
if L < J then QuickSort(ACol,L, J, SCompare);
L := I;
until I >= R;
end;
下面具体时间比较,大概使用稀梳矩阵方式排序比普通排序快6倍.
普通排序
---------------------------
第一次:1,000条数据,共花时间:78
第二次:1,000条数据,共花时间:62
---------------------------
---------------------------
第一次:10,000条数据,共花时间:891
第二次:10,000条数据,共花时间:906
---------------------------
---------------------------
第一次:10,0000条数据,共花时间:11360
第二次:10,0000条数据,共花时间:11422
---------------------------
---------------------------
第一次:100,0000条数据,共花时间:142672
第二次:100,0000条数据,共花时间:144219
---------------------------
稀梳矩阵排序
---------------------------
第一次:1000条数据,共花时间:15
第二次:1000条数据,共花时间:15
---------------------------
---------------------------
第一次:1,0000条数据,共花时间:109
第二次:1,0000条数据,共花时间:109
---------------------------
---------------------------
第一次:10,0000条数据,共花时间:1703
第二次:10,0000条数据,共花时间:1719
---------------------------
---------------------------
第一次:100,0000条数据,共花时间:22640
第二次:100,0000条数据,共花时间:23078
---------------------------