unit CbStrGrid;
{************************扩展的TStringGrid控件TcbStrGrid********************
[功能简介] 增强的字符串表格控件,主要功能有
1.在strGrid上显示带CheckBox的列;
2.设置列标题及列数据对齐方式,列数据的显示方式,如按货币的方式,数字的方式;
若是按货币/数字方式显示的话,能进行输入控制,即只能输入数字。
3.自动生成行号,设置要显示合计的行,自动求合计;
4.加入清除表格clear方法等
[实现思想]
1.重载DrawCell方法。按照属性的设置情况,自定义画出显示的内容。
而实际的值保持不变。
2.重载SelectCell方法实现设置只读列等。
3.重载SizeChanged方法实现自动添加行号
4.根据上面的方法其实你可以做得更多,包括
在表格中画图片,进度条等
绑定数据集,相信会对做三层很有帮助。
[关键属性/方法]
集合字符串,特指以数字和,构成的字符串,如 '1,2,3'
1.procedure clear; //清空表格中的数据
2.procedure DoSumAll; //对所有的数字列/货币求和
property OnSumValueChanged: TSumValueChanged
合计值发生变化时触发
property DisplaySumRow: Boolean
是否要显示合计,要显示合计,则用户在strGrid上编辑时,自动更新合计值,若要手动更新合计,
请调用doSumAll方法
3.property CheckColumnIndex:integer //设置带checkBox的列
property OnCheckChanged: TCheckChanged
当鼠标/空格键操作导致checkBox列的值发生变化时触发该事件
注意: 只是响应了鼠标/键盘在strGrid上操作,当在程序中赋值而导致的checkbox变化时,该事件并不触发
function NonChecked: boolean; //若没有check选择任何行返回True;
4.property TitleAlign: TTitleAlign //标题对齐方式
5.property ColsCurrency: String //以货币方式显示的列的集合字符串
property ColsNumber: String //以数字方式显示的列的集合字符串
property ColsAlignLeft: String //向左靠齐显示的列的集合字符串
property ColsAlignCenter: String //居中显示的列的集合字符串
property ColsAlignRight: String //向右靠齐显示的列的集合字符串
注意:设置时请不要重复设置列,包括checkColumnIndex,为什么呢? 请看源代码
6.property ColsReadOnly: string //设置只读的列的集合字符串,其他的列可以直接编辑
[注意事项]
按方向键有点画FocusRect时有点小问题。
[修改日志]
作者: majorsoft(杨美忠) 创建日期: 2004-6-6 修改日期 2004-6-8 Ver0.92
Email: majorcompu@163.com QQ:122646527 (dfw) 欢迎指教!
[版权声明] Ver0.92
该程序版权为majorsoft(杨美忠)所有,你可以免费地使用、修改、转载,不过请附带上本段注释,
请尊重别人的劳动成果,谢谢。
****************************************************************************}
interface
uses
Windows, SysUtils, Classes, Controls, Grids, Graphics;
const
STRSUM='合计';
type
TTitleAlign=(taLeft, taCenter, taRight); //标题对齐方式
TInteger=set of 0..254;
TCheckChanged = procedure (Sender: TObject; ARow: Longint) of object;
TSumValueChanged = procedure (Sender: TObject) of object;
TCbStrGrid = class(TStringGrid)
private
fCheckColumnIndex: integer;
FDownColor: TColor;
fIsDown: Boolean; //鼠标(或键盘)是否按下 用来显示动画效果
fTitleAlign: TTitleAlign; //标题对齐方式
FAlignLeftCols: String;
FAlignLeftSet: TInteger;
FAlignRightCols: String;
FAlignRightSet: TInteger;
FAlignCenterCols: String;
FAlignCenterSet: TInteger;
fCurrCols: string; //需要以货币方式显示的列的字符串,以','分隔
fCurrColsSet: TInteger; //需要以货币方式显示的列的序号的集合
fNumCols: string; //需要以数字方式显示的列的字符串,以','分隔
fNumColsSet: TInteger; //需要以数字方式显示的列的序号的集合
FColsReadOnly: string; //只读列的列序号字符串
FReadOnlySet: TInteger; //只读列的序号的集合
FCheckChanged: TCheckChanged; //最近check变化事件
FDisplaySumRow: Boolean;
FOnSumValueChanged: TSumValueChanged;
procedure AlterCheckColValue; //交替更换带checkbox的列的值
procedure SetAlignLeftCols(const Value: String);
procedure SetAlignCenterCols(const Value: String);
procedure SetAlignRightCols(const Value: String);
procedure setCheckColumnIndex(const value:integer);
procedure SetColorDown(const value: TColor);
procedure setTitleAlign(const value: TTitleAlign);
procedure setCurrCols(const value: string);
procedure setNumCols(const value: string);
procedure SetColsReadOnly(const Value: string);
procedure SetDisplaySumRow(const Value: Boolean);
procedure SetOnSumValueChanged(const Value: TSumValueChanged);
protected
procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState); override; //画
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyPress(var Key: Char); override;
procedure KeyUp(var Key: Word; Shift: TShiftState); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
function SelectCell(ACol, ARow: Longint): Boolean; override;
procedure SizeChanged(OldColCount, OldRowCount: Longint); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure clear; //清空表格中的数据
procedure DoSumAll; //对所有的数字列/货币求和
function NonChecked: boolean; //若没有check选择任何行返回True;
published
property CheckColumnIndex:integer read FCheckColumnIndex write SetCheckColumnIndex default 1; //设置带checkBox的列
property ColorDown: TColor read FDownColor write SetColorDown default $00C5D6D9;
property TitleAlign: TTitleAlign read fTitleAlign write setTitleAlign default taLeft; //标题对齐方式
property ColsCurrency: String read fCurrCols write setCurrCols; //以货币方式显示的列的集合字符串
property ColsNumber: String read fNumCols write SetNumCols; //以数字方式显示的列的集合字符串
property ColsAlignLeft: String read FAlignLeftCols write SetAlignLeftCols; //向左靠齐显示的列的集合字符串
property ColsAlignCenter: String read FAlignCenterCols write SetAlignCenterCols; //居中显示的列的集合字符串
property ColsAlignRight: String read FAlignRightCols write SetAlignRightCols; //向右靠齐显示的列的集合字符串
property ColsReadOnly: string read FColsReadOnly write SetColsReadOnly; //设置只读的列的集合字符串,其他的列可以直接编辑
{property DisplaySumRow:
是否要显示合计,要显示合计,则用户在strGrid上编辑时,自动更新合计值,若要手动更新合计,
请调用doSumAll方法}
property DisplaySumRow: Boolean read FDisplaySumRow write SetDisplaySumRow;
{property OnCheckChanged:
当鼠标/空格键操作导致checkBox列的值发生变化时触发该事件
注意: 只是响应了鼠标/键盘在strGrid上操作,当在程序中赋值而导致的checkbox变化时,该事件并不触发}
property OnCheckChanged: TCheckChanged read FCheckChanged write FCheckChanged;
property OnSumValueChanged: TSumValueChanged read FOnSumValueChanged write SetOnSumValueChanged;
end;
procedure Register;
function MyStrToint(Value:string):integer;
function MyStrToFloat(str:string):extended;
function PointInRect(const pt:Tpoint; const Rect: TRect):boolean;
function ExtractNumToSet(const str: string; var aSet: TInteger):Boolean; //从 str中提取数字放到aSet集合中,若成功则返回true
implementation
function MyStrToint(value:string):integer;
begin
tryStrToInt(trim(value),result);
end;
function MyStrToFloat(str:string):extended;
begin
if trim(str)='' then
result:=0.0
else TryStrTofloat(trim(str),result);
end;
function PointInRect(const pt:Tpoint; const Rect: TRect):boolean;
begin
if (Pt.X>=Rect.Left) and (Pt.X<=Rect.Right) and
(Pt.Y>= Rect.Top) and (Pt.Y<=Rect.Bottom) then
result:=True
else result:=false;
end;
function ExtractNumToSet(const str: string; var aSet: TInteger):Boolean;
var
tmpStr:string;
iComma, i:Integer; //逗号位置
begin
aSet:=[]; //初始化集合
if Length(str)=0 then
begin
result:=true;
exit;
end;
if not (str[1] in ['0'..'9']) then //检查合法性1
begin
result:=false;
exit;
end;
for i:=1 to Length(str) do //检查合法性2
if not (str[i] in ['0'..'9', ',']) then
begin
result:=false;
exit;
end;
tmpStr:=Trim(Str);
while length(tmpStr)>0 do
begin
iComma:=pos(',', tmpStr);
if (tmpstr[1] in ['0'..'9']) then
if (iComma>0) then
begin
include(aSet, StrToInt(Copy(tmpStr, 1, iComma-1)));
tmpStr:=copy(tmpStr, iComma+1, length(tmpStr)-iComma);
end
else begin
include(aSet, StrToInt(tmpStr));
tmpStr:='';
end
else tmpStr:=copy(tmpStr, iComma+1, length(tmpStr)-iComma);
end;
result:=true;
end;
procedure Register;
begin
RegisterComponents('MA', [TCbStrGrid]);
end;
{ TCbStrGrid }
procedure TCbStrGrid.AlterCheckColValue;
begin
if (Row>0) and (col=fCheckColumnIndex) then
begin
if MyStrToint(Cells[col,Row])=0 then
Cells[col, Row]:='1'
else Cells[col, Row]:='0';
end;
end;
constructor TCbStrGrid.Create(AOwner: TComponent);
begin
inherited;
Options:=Options + [goColSizing];
fCheckColumnIndex:=1;
FDownColor:=$00C5D6D9;
Height:=150;
Width:=350;
col:=ColCount-1;
end;
destructor TCbStrGrid.Destroy;
begin
inherited;
end;
procedure TCbStrGrid.DrawCell(ACol, ARow: Integer; ARect: TRect;
AState: TGridDrawState);
var
area, CheckboxRect: TRect;
CurPt: TPoint;
value, OffSetX, OffSetY:integer;
strCell: String;
begin
Area:= ARect;
InflateRect(Area, -2, -2); //缩小区域 主要作为text out区域
if (ARow>0) then
begin
if aCol in fNumColsSet then //数字方式
begin
strCell:=FormatFloat('#,##0.##', MyStrToFloat(Cells[ACol, ARow]));
DrawText(canvas.Handle, PChar(strCell), Length(strCell), Area, DT_RIGHT) //设为靠右
end
else if aCol in fCurrColsSet then //货币方式
begin
strCell:='¥'+FormatFloat('#,###.00', MyStrToFloat(Cells[ACol, ARow]));
DrawText(canvas.Handle, PChar(strCell), Length(strCell), Area, DT_RIGHT) //设为靠右
end
else if aCol in FAlignLeftSet then
DrawText(Canvas.Handle, PChar(Cells[ACol, ARow]),Length(Cells[ACol, ARow]), Area, DT_Left)
else if aCol in FAlignCenterSet then
DrawText(Canvas.Handle, PChar(Cells[ACol, ARow]),Length(Cells[ACol, ARow]), Area, DT_Center)
else if aCol in FAlignRightSet then
DrawText(Canvas.Handle, PChar(Cells[ACol, ARow]),Length(Cells[ACol, ARow]), Area, DT_Right)
else if (aCol=fCheckColumnIndex) then //checkBox方式
begin
if (Cells[0, ARow]=STRSUM) then exit; //合计行的checkBox不画
value:=MyStrToint(Cells[fCheckColumnIndex,aRow]);
Canvas.FillRect(ARect);
with ARect do
begin
OffSetX:=(Right- Left- 10) div 2;
OffSetY:=(Bottom- Top- 10) div 2;
end;
CheckboxRect:=Rect(ARect.Left+OffSetX, ARect.Top + OffSetY, //取得checkBox要画的区域
ARect.Left+OffSetX+11, ARect.Top + OffSetY +11);
canvas.pen.style := psSolid;
canvas.pen.width := 1;
getCursorPos(CurPt);
CurPt:=self.ScreenToClient(CurPt);
{画背景}
if (fisDown) and PointInRect(CurPt, ARect) then
begin
canvas.brush.color := fDownColor;
canvas.pen.color := clBlack;
end
else begin
canvas.brush.color := color;
canvas.pen.color := clBlack;
end;
canvas.FillRect(CheckboxRect);
{ 画勾}
if (value<>0) then //不为0表示checked=true;
begin
canvas.penpos := Point(CheckboxRect.left+2, CheckboxRect.top+4);//设置起点
canvas.lineto(CheckboxRect.left+6, CheckboxRect.top+8); //画到...
canvas.penpos := Point(CheckboxRect.left+2, CheckboxRect.top+5);
canvas.lineto(CheckboxRect.left+5, CheckboxRect.top+8);
canvas.penpos := Point(CheckboxRect.left+2, CheckboxRect.top+6);
canvas.lineto(CheckboxRect.left+5, CheckboxRect.top+9);
canvas.penpos := Point(CheckboxRect.left+8, CheckboxRect.top+2);
canvas.lineto(CheckboxRect.left+4, CheckboxRect.top+6);
canvas.penpos := Point(CheckboxRect.left+8, CheckboxRect.top+3);
canvas.lineto(CheckboxRect.left+4, CheckboxRect.top+7);
canvas.penpos := Point(CheckboxRect.left+8, CheckboxRect.top+4);
canvas.lineto(CheckboxRect.left+5, CheckboxRect.top+7);
end;
{画边界}
Area:=CellRect(Col, Row);
DrawFocusRect(canvas.Handle, Area); //
canvas.brush.color :=clBlack;
canvas.FrameRect(CheckboxRect);
end
else inherited DrawCell(ACol, ARow, ARect, AState);
end
else if (ARow=0) then
begin
Canvas.FillRect(ARect);
case fTitleAlign of
taLeft: DrawText(Canvas.Handle, PChar(Cells[ACol, ARow]),Length(Cells[ACol, ARow]), Area, DT_Left);
taCenter: DrawText(Canvas.Handle, PChar(Cells[ACol, ARow]),Length(Cells[ACol, ARow]), Area, DT_Center);
taRight: DrawText(Canvas.Handle, PChar(Cells[ACol, ARow]),Length(Cells[ACol, ARow]), Area, DT_Right);
end;
end
else inherited DrawCell(ACol, ARow, ARect, AState);
end;
procedure TCbStrGrid.KeyDown(var Key: Word; Shift: TShiftState);
begin
if (key=vk_space) and (Row>0) and (col=fCheckColumnIndex)then
fIsDown:=True;
inherited;
end;
procedure TCbStrGrid.KeyUp(var Key: Word; Shift: TShiftState);
var
Area:TRect;
begin
if (key=vk_space) and (Row>0) and (col=fCheckColumnIndex)then
begin
AlterCheckColValue;
fIsDown:=false;
if Assigned(FCheckChanged) then FCheckChanged(self, Row);
end;
inherited;
if key=vk_Up then //vk_up TMD变态
begin
Area:=self.CellRect(Col, Row);
DrawFocusRect(canvas.Handle, Area);
end;
if FDisplaySumRow then DoSumAll;
end;
procedure TCbStrGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
begin
if (Row>0) and (col=fCheckColumnIndex)then
fIsDown:=True;
inherited;
end;
procedure TCbStrGrid.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
var
curPt: TPoint;
Area:TRect;
begin
getCursorPos(CurPt);
CurPt:=self.ScreenToClient(CurPt);
Area:=self.CellRect(Col, Row);
if (Row>0) and (col=fCheckColumnIndex) and PointInRect(CurPt, Area) then
begin
AlterCheckColValue;
fIsDown:=false;
if Assigned(FCheckChanged) then FCheckChanged(self, Row);
end;
inherited;
if FDisplaySumRow then DoSumAll;
end;
procedure TCbStrGrid.SetAlignLeftCols(const Value: String);
begin
if ExtractNumToSet(Value, fAlignLeftSet) then
FAlignLeftCols := Value
else Raise Exception.Create('属性值设置错误, 请用数字和,分隔的方式设置属性');
InvalidateGrid;
end;
procedure TCbStrGrid.setCheckColumnIndex(const value: integer);
begin
if (value>colCount) then raise exception.Create('CheckColumnIndex越界');
fCheckColumnIndex:=Value;
repaint;
end;
procedure TCbStrGrid.SetColorDown(const value: TColor);
begin
fDownColor:=value;
InvalidateCell(fCheckColumnIndex, row);
end;
procedure TCbStrGrid.SetAlignCenterCols(const Value: String);
begin
if ExtractNumToSet(Value, FAlignCenterSet) then
FAlignCenterCols := Value
else Raise Exception.Create('属性值设置错误, 请用数字和,分隔的方式设置属性');
InvalidateGrid;
end;
procedure TCbStrGrid.SetAlignRightCols(const Value: String);
begin
if ExtractNumToSet(Value, FAlignRightSet) then
FAlignRightCols := Value
else Raise Exception.Create('属性值设置错误, 请用数字和,分隔的方式设置属性');
InvalidateGrid;
end;
procedure TCbStrGrid.setCurrCols(const value: string);
begin
if ExtractNumToSet(Value, fCurrColsSet) then
fCurrCols:=value
else Raise Exception.Create('属性值设置错误, 请用数字和,分隔的方式设置属性');
InvalidateGrid;
end;
procedure TCbStrGrid.setNumCols(const value: string);
begin
if ExtractNumToSet(Value, fNumColsSet) then
fNumCols:=value
else Raise Exception.Create('属性值设置错误, 请用数字和,分隔的方式设置属性');
InvalidateGrid;
end;
procedure TCbStrGrid.setTitleAlign(const value: TTitleAlign);
begin
if not(value in [taLeft, taCenter, taRight]) then Raise Exception.Create('属性值设置错误,请在[taLeft, taCenter, taRight]选择');
fTitleAlign:=value;
InvalidateGrid;
end;
function TCbStrGrid.SelectCell(ACol, ARow: Integer): Boolean;
begin
if (ACol=fCheckColumnIndex) or (ACol in FReadOnlySet) then
Options:=Options - [goEditing]
else Options:=Options + [goEditing];
Inherited SelectCell(ACol, ARow);
end;
procedure TCbStrGrid.SetColsReadOnly(const Value: string);
begin
if ExtractNumToSet(Value,FReadOnlySet) then
FColsReadOnly := Value
else Raise Exception.Create('属性值设置错误, 请用数字和,分隔的方式设置属性');
InvalidateGrid;
end;
procedure TCbStrGrid.clear;
var
i,j:integer;
begin
for i:=1 to RowCount-1 do
for j:=1 to ColCount-1 do
Cells[j,i]:=''; //注意j,i的顺序
InvalidateGrid;
end;
procedure TCbStrGrid.SizeChanged(OldColCount, OldRowCount: Integer);
var
i:integer;
begin
inherited;
for i:=1 to RowCount-1 do
Cells[0,i]:=inttostr(i);
if FDisplaySumRow then cells[0, RowCount-1]:=STRSUM;
InvalidateGrid;
end;
procedure TCbStrGrid.SetDisplaySumRow(const Value: Boolean);
begin
FDisplaySumRow := Value;
RowCount:=RowCount+1; //仅做刷新用 会调用SizeChanged
RowCount:=RowCount-1; //非常规做法。没想到好办法。
if FDisplaySumRow then DoSumAll;
InvalidateGrid;
end;
procedure TCbStrGrid.DoSumAll;
var
i, j:integer;
begin
if not fDisplaySumRow then exit;
for j:=1 to ColCount-1 do //先初始化
if (j in fCurrColsSet) or (j in fNumColsSet) then
Cells[j, RowCount-1]:='0';
for i:=1 to RowCount-2 do
for j:=1 to ColCount-1 do
if (j in fCurrColsSet) or (j in fNumColsSet) then
Cells[j, RowCount-1]:=FloatToStr((MyStrToFloat(Cells[j, RowCount-1]) + MyStrToFloat(Cells[j, i])));
if Assigned(FOnSumValueChanged) then FOnSumValueChanged(self);
end;
procedure TCbStrGrid.KeyPress(var Key: Char);
begin
if (Col in fCurrColsSet+ fNumColsSet) then
if not(key in ['0'..'9', '.', '-', char(VK_back), char(VK_Delete)]) then
key:=#0;
inherited KeyPress(Key);
end;
function TCbStrGrid.NonChecked: boolean;
var
i, iMax:integer;
begin
result:=True;
if FDisplaySumRow then IMax:= RowCount-2 else IMax:= RowCount-1;
for i:=1 to iMax do
begin
if Cells[CheckColumnIndex, i]='1' then
begin
result:=false;
exit;
end
end;
end;
procedure TCbStrGrid.SetOnSumValueChanged(const Value: TSumValueChanged);
begin
FOnSumValueChanged := Value;
end;
end.