home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Chip 1997 April
/
Chip_1997-04_cd.bin
/
prezent
/
cb
/
data.z
/
GRIDS.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1997-01-16
|
140KB
|
4,741 lines
{*******************************************************}
{ }
{ Delphi Visual Component Library }
{ }
{ Copyright (c) 1995,96 Borland International }
{ }
{*******************************************************}
unit Grids;
{$R-}
interface
uses SysUtils, Messages, Windows, Classes, Graphics, Menus, Controls, Forms,
StdCtrls, Mask;
const
MaxCustomExtents = MaxListSize;
MaxShortInt = High(ShortInt);
type
EInvalidGridOperation = class(Exception);
{ Internal grid types }
TGetExtentsFunc = function(Index: Longint): Integer of object;
TGridAxisDrawInfo = record
EffectiveLineWidth: Integer;
FixedBoundary: Integer;
GridBoundary: Integer;
GridExtent: Integer;
LastFullVisibleCell: Longint;
FullVisBoundary: Integer;
FixedCellCount: Integer;
FirstGridCell: Integer;
GridCellCount: Integer;
GetExtent: TGetExtentsFunc;
end;
TGridDrawInfo = record
Horz, Vert: TGridAxisDrawInfo;
end;
TGridState = (gsNormal, gsSelecting, gsRowSizing, gsColSizing,
gsRowMoving, gsColMoving);
{ TInplaceEdit }
{ The inplace editor is not intended to be used outside the grid }
TCustomGrid = class;
TInplaceEdit = class(TCustomMaskEdit)
private
FGrid: TCustomGrid;
FClickTime: Longint;
procedure InternalMove(const Loc: TRect; Redraw: Boolean);
procedure SetGrid(Value: TCustomGrid);
procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
procedure WMPaste(var Message); message WM_PASTE;
procedure WMCut(var Message); message WM_CUT;
procedure WMClear(var Message); message WM_CLEAR;
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure DblClick; override;
function EditCanModify: Boolean; override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyPress(var Key: Char); override;
procedure KeyUp(var Key: Word; Shift: TShiftState); override;
procedure BoundsChanged; virtual;
procedure UpdateContents; virtual;
procedure WndProc(var Message: TMessage); override;
property Grid: TCustomGrid read FGrid;
public
constructor Create(AOwner: TComponent); override;
procedure Deselect;
procedure Hide;
procedure Invalidate;
procedure Move(const Loc: TRect);
function PosEqual(const Rect: TRect): Boolean;
procedure SetFocus;
procedure UpdateLoc(const Loc: TRect);
function Visible: Boolean;
end;
{ TCustomGrid }
{ TCustomGrid is an abstract base class that can be used to implement
general purpose grid style controls. The control will call DrawCell for
each of the cells allowing the derived class to fill in the contents of
the cell. The base class handles scrolling, selection, cursor keys, and
scrollbars.
DrawCell
Called by Paint. If DefaultDrawing is true the font and brush are
intialized to the control font and cell color. The cell is prepainted
in the cell color and a focus rect is drawn in the focused cell after
DrawCell returns. The state passed will reflect whether the cell is
a fixed cell, the focused cell or in the selection.
SizeChanged
Called when the size of the grid has changed.
BorderStyle
Allows a single line border to be drawn around the control.
Col
The current column of the focused cell (runtime only).
ColCount
The number of columns in the grid.
ColWidths
The width of each column (up to a maximum MaxCustomExtents, runtime
only).
DefaultColWidth
The default column width. Changing this value will throw away any
customization done either visually or through ColWidths.
DefaultDrawing
Indicates whether the Paint should do the drawing talked about above in
DrawCell.
DefaultRowHeight
The default row height. Changing this value will throw away any
customization done either visually or through RowHeights.
FixedCols
The number of non-scrolling columns. This value must be at least one
below ColCount.
FixedRows
The number of non-scrolling rows. This value must be at least one
below RowCount.
GridLineWidth
The width of the lines drawn between the cells.
LeftCol
The index of the left most displayed column (runtime only).
Options
The following options are available:
goFixedHorzLine: Draw horizontal grid lines in the fixed cell area.
goFixedVertLine: Draw veritical grid lines in the fixed cell area.
goHorzLine: Draw horizontal lines between cells.
goVertLine: Draw vertical lines between cells.
goRangeSelect: Allow a range of cells to be selected.
goDrawFocusSelected: Draw the focused cell in the selected color.
goRowSizing: Allows rows to be individually resized.
goColSizing: Allows columns to be individually resized.
goRowMoving: Allows rows to be moved with the mouse
goColMoving: Allows columns to be moved with the mouse.
goEditing: Places an edit control over the focused cell.
goAlwaysShowEditor: Always shows the editor in place instead of
waiting for a keypress or F2 to display it.
goTabs: Enables the tabbing between columns.
goRowSelect: Selection and movement is done a row at a time.
Row
The row of the focused cell (runtime only).
RowCount
The number of rows in the grid.
RowHeights
The hieght of each row (up to a maximum MaxCustomExtents, runtime
only).
ScrollBars
Determines whether the control has scrollbars.
Selection
A TGridRect of the current selection.
TopLeftChanged
Called when the TopRow or LeftCol change.
TopRow
The index of the top most row displayed (runtime only)
VisibleColCount
The number of columns fully displayed. There could be one more column
partially displayed.
VisibleRowCount
The number of rows fully displayed. There could be one more row
partially displayed. }
TGridOption = (goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine,
goRangeSelect, goDrawFocusSelected, goRowSizing, goColSizing, goRowMoving,
goColMoving, goEditing, goTabs, goRowSelect,
goAlwaysShowEditor, goThumbTracking);
TGridOptions = set of TGridOption;
TGridDrawState = set of (gdSelected, gdFocused, gdFixed);
TGridScrollDirection = set of (sdLeft, sdRight, sdUp, sdDown);
TGridCoord = record
X: Longint;
Y: Longint;
end;
TGridRect = record
case Integer of
0: (Left, Top, Right, Bottom: Longint);
1: (TopLeft, BottomRight: TGridCoord);
end;
TSelectCellEvent = procedure (Sender: TObject; Col, Row: Longint;
var CanSelect: Boolean) of object;
TDrawCellEvent = procedure (Sender: TObject; Col, Row: Longint;
Rect: TRect; State: TGridDrawState) of object;
TCustomGrid = class(TCustomControl)
private
FAnchor: TGridCoord;
FBorderStyle: TBorderStyle;
FCanEditModify: Boolean;
FColCount: Longint;
FColWidths: Pointer;
FTabStops: Pointer;
FCurrent: TGridCoord;
FDefaultColWidth: Integer;
FDefaultRowHeight: Integer;
FFixedCols: Integer;
FFixedRows: Integer;
FFixedColor: TColor;
FGridLineWidth: Integer;
FOptions: TGridOptions;
FRowCount: Longint;
FRowHeights: Pointer;
FScrollBars: TScrollStyle;
FTopLeft: TGridCoord;
FSizingIndex: Longint;
FSizingPos, FSizingOfs: Integer;
FMoveIndex, FMovePos: Longint;
FHitTest: TPoint;
FInplaceEdit: TInplaceEdit;
FInplaceCol, FInplaceRow: Longint;
FDefaultDrawing: Boolean;
FEditorMode: Boolean;
FColOffset: Integer;
function CalcCoordFromPoint(X, Y: Integer;
const DrawInfo: TGridDrawInfo): TGridCoord;
procedure CalcDrawInfo(var DrawInfo: TGridDrawInfo);
procedure CalcDrawInfoXY(var DrawInfo: TGridDrawInfo;
UseWidth, UseHeight: Integer);
procedure CalcFixedInfo(var DrawInfo: TGridDrawInfo);
function CalcMaxTopLeft(const Coord: TGridCoord;
const DrawInfo: TGridDrawInfo): TGridCoord;
procedure CalcSizingState(X, Y: Integer; var State: TGridState;
var Index: Longint; var SizingPos, SizingOfs: Integer;
var FixedInfo: TGridDrawInfo);
procedure ChangeSize(NewColCount, NewRowCount: Longint);
procedure ClampInView(const Coord: TGridCoord);
procedure DrawSizingLine(const DrawInfo: TGridDrawInfo);
procedure DrawMove;
procedure FocusCell(ACol, ARow: Longint; MoveAnchor: Boolean);
procedure GridRectToScreenRect(GridRect: TGridRect;
var ScreenRect: TRect; IncludeLine: Boolean);
procedure HideEdit;
procedure Initialize;
procedure InvalidateGrid;
procedure InvalidateRect(ARect: TGridRect);
procedure InvertRect(const Rect: TRect);
procedure ModifyScrollBar(ScrollBar, ScrollCode, Pos: Cardinal);
procedure MoveAdjust(var CellPos: Longint; FromIndex, ToIndex: Longint);
procedure MoveAnchor(const NewAnchor: TGridCoord);
procedure MoveAndScroll(Mouse, CellHit: Integer; var DrawInfo: TGridDrawInfo;
var Axis: TGridAxisDrawInfo; Scrollbar: Integer);
procedure MoveCurrent(ACol, ARow: Longint; MoveAnchor, Show: Boolean);
procedure MoveTopLeft(ALeft, ATop: Longint);
procedure ResizeCol(Index: Longint; OldSize, NewSize: Integer);
procedure ResizeRow(Index: Longint; OldSize, NewSize: Integer);
procedure SelectionMoved(const OldSel: TGridRect);
procedure ScrollDataInfo(DX, DY: Integer; var DrawInfo: TGridDrawInfo);
procedure TopLeftMoved(const OldTopLeft: TGridCoord);
procedure UpdateScrollPos;
procedure UpdateScrollRange;
function GetColWidths(Index: Longint): Integer;
function GetRowHeights(Index: Longint): Integer;
function GetSelection: TGridRect;
function GetTabStops(Index: Longint): Boolean;
function GetVisibleColCount: Integer;
function GetVisibleRowCount: Integer;
procedure ReadColWidths(Reader: TReader);
procedure ReadRowHeights(Reader: TReader);
procedure SetBorderStyle(Value: TBorderStyle);
procedure SetCol(Value: Longint);
procedure SetColCount(Value: Longint);
procedure SetColWidths(Index: Longint; Value: Integer);
procedure SetDefaultColWidth(Value: Integer);
procedure SetDefaultRowHeight(Value: Integer);
procedure SetEditorMode(Value: Boolean);
procedure SetFixedColor(Value: TColor);
procedure SetFixedCols(Value: Integer);
procedure SetFixedRows(Value: Integer);
procedure SetGridLineWidth(Value: Integer);
procedure SetLeftCol(Value: Longint);
procedure SetOptions(Value: TGridOptions);
procedure SetRow(Value: Longint);
procedure SetRowCount(Value: Longint);
procedure SetRowHeights(Index: Longint; Value: Integer);
procedure SetScrollBars(Value: TScrollStyle);
procedure SetSelection(Value: TGridRect);
procedure SetTabStops(Index: Longint; Value: Boolean);
procedure SetTopRow(Value: Longint);
procedure UpdateEdit;
procedure UpdateText;
procedure WriteColWidths(Writer: TWriter);
procedure WriteRowHeights(Writer: TWriter);
procedure CMCancelMode(var Msg: TMessage); message CM_CANCELMODE;
procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
procedure CMCtl3DChanged(var Message: TMessage); message CM_CTL3DCHANGED;
procedure CMDesignHitTest(var Msg: TCMDesignHitTest); message CM_DESIGNHITTEST;
procedure CMWantSpecialKey(var Msg: TCMWantSpecialKey); message CM_WANTSPECIALKEY;
procedure WMChar(var Msg: TWMChar); message WM_CHAR;
procedure WMCommand(var Message: TWMCommand); message WM_COMMAND;
procedure WMGetDlgCode(var Msg: TWMGetDlgCode); message WM_GETDLGCODE;
procedure WMHScroll(var Msg: TWMHScroll); message WM_HSCROLL;
procedure WMKillFocus(var Msg: TWMKillFocus); message WM_KILLFOCUS;
procedure WMLButtonDown(var Message: TMessage); message WM_LBUTTONDOWN;
procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
procedure WMSetCursor(var Msg: TWMSetCursor); message WM_SETCURSOR;
procedure WMSetFocus(var Msg: TWMSetFocus); message WM_SETFOCUS;
procedure WMSize(var Msg: TWMSize); message WM_SIZE;
procedure WMTimer(var Msg: TWMTimer); message WM_TIMER;
procedure WMVScroll(var Msg: TWMVScroll); message WM_VSCROLL;
protected
FGridState: TGridState;
FSaveCellExtents: Boolean;
function CreateEditor: TInplaceEdit; virtual;
procedure CreateParams(var Params: TCreateParams); override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyPress(var Key: Char); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure AdjustSize(Index, Amount: Longint; Rows: Boolean); dynamic;
function BoxRect(ALeft, ATop, ARight, ABottom: Longint): TRect;
procedure DoExit; override;
function CellRect(ACol, ARow: Longint): TRect;
function CanEditAcceptKey(Key: Char): Boolean; dynamic;
function CanGridAcceptKey(Key: Word; Shift: TShiftState): Boolean; dynamic;
function CanEditModify: Boolean; dynamic;
function CanEditShow: Boolean; virtual;
function GetEditText(ACol, ARow: Longint): string; dynamic;
procedure SetEditText(ACol, ARow: Longint; const Value: string); dynamic;
function GetEditMask(ACol, ARow: Longint): string; dynamic;
function GetEditLimit: Integer; dynamic;
function GetGridWidth: Integer;
function GetGridHeight: Integer;
procedure HideEditor;
procedure ShowEditor;
procedure ShowEditorChar(Ch: Char);
procedure InvalidateEditor;
procedure MoveColumn(FromIndex, ToIndex: Longint);
procedure ColumnMoved(FromIndex, ToIndex: Longint); dynamic;
procedure MoveRow(FromIndex, ToIndex: Longint);
procedure RowMoved(FromIndex, ToIndex: Longint); dynamic;
procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState); virtual; abstract;
procedure DefineProperties(Filer: TFiler); override;
function MouseCoord(X, Y: Integer): TGridCoord;
procedure MoveColRow(ACol, ARow: Longint; MoveAnchor, Show: Boolean);
function SelectCell(ACol, ARow: Longint): Boolean; virtual;
procedure SizeChanged(OldColCount, OldRowCount: Longint); dynamic;
function Sizing(X, Y: Integer): Boolean;
procedure ScrollData(DX, DY: Integer);
procedure InvalidateCell(ACol, ARow: Longint);
procedure InvalidateCol(ACol: Longint);
procedure InvalidateRow(ARow: Longint);
procedure TopLeftChanged; dynamic;
procedure TimedScroll(Direction: TGridScrollDirection); dynamic;
procedure Paint; override;
procedure ColWidthsChanged; dynamic;
procedure RowHeightsChanged; dynamic;
procedure DeleteColumn(ACol: Longint);
procedure DeleteRow(ARow: Longint);
procedure UpdateDesigner;
property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle default bsSingle;
property Col: Longint read FCurrent.X write SetCol;
property Color default clWindow;
property ColCount: Longint read FColCount write SetColCount default 5;
property ColWidths[Index: Longint]: Integer read GetColWidths write SetColWidths;
property DefaultColWidth: Integer read FDefaultColWidth write SetDefaultColWidth default 64;
property DefaultDrawing: Boolean read FDefaultDrawing write FDefaultDrawing default True;
property DefaultRowHeight: Integer read FDefaultRowHeight write SetDefaultRowHeight default 24;
property EditorMode: Boolean read FEditorMode write SetEditorMode;
property FixedColor: TColor read FFixedColor write SetFixedColor default clBtnFace;
property FixedCols: Integer read FFixedCols write SetFixedCols default 1;
property FixedRows: Integer read FFixedRows write SetFixedRows default 1;
property GridHeight: Integer read GetGridHeight;
property GridLineWidth: Integer read FGridLineWidth write SetGridLineWidth default 1;
property GridWidth: Integer read GetGridWidth;
property InplaceEditor: TInplaceEdit read FInplaceEdit;
property LeftCol: Longint read FTopLeft.X write SetLeftCol;
property Options: TGridOptions read FOptions write SetOptions
default [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine,
goRangeSelect];
property ParentColor default False;
property Row: Longint read FCurrent.Y write SetRow;
property RowCount: Longint read FRowCount write SetRowCount default 5;
property RowHeights[Index: Longint]: Integer read GetRowHeights write SetRowHeights;
property ScrollBars: TScrollStyle read FScrollBars write SetScrollBars default ssBoth;
property Selection: TGridRect read GetSelection write SetSelection;
property TabStops[Index: Longint]: Boolean read GetTabStops write SetTabStops;
property TopRow: Longint read FTopLeft.Y write SetTopRow;
property VisibleColCount: Integer read GetVisibleColCount;
property VisibleRowCount: Integer read GetVisibleRowCount;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property TabStop default True;
end;
{ TDrawGrid }
{ A grid relies on the OnDrawCell event to display the cells.
CellRect
This method returns control relative screen coordinates of the cell or
an empty rectangle if the cell is not visible.
EditorMode
Setting to true shows the editor, as if the F2 key was pressed, when
goEditing is turned on and goAlwaysShowEditor is turned off.
MouseToCell
Takes control relative screen X, Y location and fills in the column and
row that contain that point.
OnColumnMoved
Called when the user request to move a column with the mouse when
the goColMoving option is on.
OnDrawCell
This event is passed the same information as the DrawCell method
discussed above.
OnGetEditMask
Called to retrieve edit mask in the inplace editor when goEditing is
turned on.
OnGetEditText
Called to retrieve text to edit when goEditing is turned on.
OnRowMoved
Called when the user request to move a row with the mouse when
the goRowMoving option is on.
OnSetEditText
Called when goEditing is turned on to reflect changes to the text
made by the editor.
OnTopLeftChanged
Invoked when TopRow or LeftCol change. }
TGetEditEvent = procedure (Sender: TObject; ACol, ARow: Longint; var Value: string) of object;
TSetEditEvent = procedure (Sender: TObject; ACol, ARow: Longint; const Value: string) of object;
TMovedEvent = procedure (Sender: TObject; FromIndex, ToIndex: Longint) of object;
TDrawGrid = class(TCustomGrid)
private
FOnColumnMoved: TMovedEvent;
FOnDrawCell: TDrawCellEvent;
FOnGetEditMask: TGetEditEvent;
FOnGetEditText: TGetEditEvent;
FOnRowMoved: TMovedEvent;
FOnSelectCell: TSelectCellEvent;
FOnSetEditText: TSetEditEvent;
FOnTopLeftChanged: TNotifyEvent;
protected
procedure ColumnMoved(FromIndex, ToIndex: Longint); override;
procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState); override;
function GetEditMask(ACol, ARow: Longint): string; override;
function GetEditText(ACol, ARow: Longint): string; override;
procedure RowMoved(FromIndex, ToIndex: Longint); override;
function SelectCell(ACol, ARow: Longint): Boolean; override;
procedure SetEditText(ACol, ARow: Longint; const Value: string); override;
procedure TopLeftChanged; override;
public
function CellRect(ACol, ARow: Longint): TRect;
procedure MouseToCell(X, Y: Integer; var ACol, ARow: Longint);
property Canvas;
property Col;
property ColWidths;
property EditorMode;
property GridHeight;
property GridWidth;
property LeftCol;
property Selection;
property Row;
property RowHeights;
property TabStops;
property TopRow;
published
property Align;
property BorderStyle;
property Color;
property ColCount;
property Ctl3D;
property DefaultColWidth;
property DefaultRowHeight;
property DefaultDrawing;
property DragCursor;
property DragMode;
property Enabled;
property FixedColor;
property FixedCols;
property RowCount;
property FixedRows;
property Font;
property GridLineWidth;
property Options;
property ParentColor;
property ParentCtl3D;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ScrollBars;
property ShowHint;
property TabOrder;
property TabStop;
property Visible;
property VisibleColCount;
property VisibleRowCount;
property OnClick;
property OnColumnMoved: TMovedEvent read FOnColumnMoved write FOnColumnMoved;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnDrawCell: TDrawCellEvent read FOnDrawCell write FOnDrawCell;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnGetEditMask: TGetEditEvent read FOnGetEditMask write FOnGetEditMask;
property OnGetEditText: TGetEditEvent read FOnGetEditText write FOnGetEditText;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnRowMoved: TMovedEvent read FOnRowMoved write FOnRowMoved;
property OnSelectCell: TSelectCellEvent read FOnSelectCell write FOnSelectCell;
property OnSetEditText: TSetEditEvent read FOnSetEditText write FOnSetEditText;
property OnStartDrag;
property OnTopLeftChanged: TNotifyEvent read FOnTopLeftChanged write FOnTopLeftChanged;
end;
{ TStringGrid }
{ TStringGrid adds to TDrawGrid the ability to save a string and associated
object (much like TListBox). It also adds to the DefaultDrawing the drawing
of the string associated with the current cell.
Cells
A ColCount by RowCount array of strings which are associated with each
cell. By default, the string is drawn into the cell before OnDrawCell
is called. This can be turned off (along with all the other default
drawing) by setting DefaultDrawing to false.
Cols
A TStrings object that contains the strings and objects in the column
indicated by Index. The TStrings will always have a count of RowCount.
If a another TStrings is assigned to it, the strings and objects beyond
RowCount are ignored.
Objects
A ColCount by Rowcount array of TObject's associated with each cell.
Object put into this array will *not* be destroyed automatically when
the grid is destroyed.
Rows
A TStrings object that contains the strings and objects in the row
indicated by Index. The TStrings will always have a count of ColCount.
If a another TStrings is assigned to it, the strings and objects beyond
ColCount are ignored. }
TStringGrid = class;
TStringGridStrings = class(TStrings)
private
FGrid: TStringGrid;
FIndex: Integer;
procedure CalcXY(Index: Integer; var X, Y: Integer);
protected
procedure Clear; override;
function Add(const S: string): Integer; override;
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 SetUpdateState(Updating: Boolean); override;
public
constructor Create(AGrid: TStringGrid; AIndex: Longint);
procedure Assign(Source: TPersistent); override;
end;
TStringGrid = class(TDrawGrid)
private
FData: Pointer;
FRows: Pointer;
FCols: Pointer;
FUpdating: Boolean;
FNeedsUpdating: Boolean;
FEditUpdate: Integer;
procedure DisableEditUpdate;
procedure EnableEditUpdate;
procedure Initialize;
procedure Update(ACol, ARow: Integer);
procedure SetUpdateState(Updating: Boolean);
function GetCells(ACol, ARow: Integer): string;
function GetCols(Index: Integer): TStrings;
function GetObjects(ACol, ARow: Integer): TObject;
function GetRows(Index: Integer): TStrings;
procedure SetCells(ACol, ARow: Integer; const Value: string);
procedure SetCols(Index: Integer; Value: TStrings);
procedure SetObjects(ACol, ARow: Integer; Value: TObject);
procedure SetRows(Index: Integer; Value: TStrings);
function EnsureColRow(Index: Integer; IsCol: Boolean): TStringGridStrings;
function EnsureDataRow(ARow: Integer): Pointer;
protected
procedure ColumnMoved(FromIndex, ToIndex: Longint); override;
procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState); override;
function GetEditText(ACol, ARow: Longint): string; override;
procedure SetEditText(ACol, ARow: Longint; const Value: string); override;
procedure RowMoved(FromIndex, ToIndex: Longint); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Cells[ACol, ARow: Integer]: string read GetCells write SetCells;
property Cols[Index: Integer]: TStrings read GetCols write SetCols;
property Objects[ACol, ARow: Integer]: TObject read GetObjects write SetObjects;
property Rows[Index: Integer]: TStrings read GetRows write SetRows;
end;
implementation
uses Consts;
type
PIntArray = ^TIntArray;
TIntArray = array[0..MaxCustomExtents] of Integer;
procedure InvalidOp(id: Integer);
begin
raise EInvalidGridOperation.CreateRes(id);
end;
function IMin(A, B: Integer): Integer;
begin
Result := B;
if A < B then Result := A;
end;
function IMax(A, B: Integer): Integer;
begin
Result := B;
if A > B then Result := A;
end;
function CoordInRect(const ACoord: TGridCoord; const ARect: TGridRect): Boolean;
begin
with ACoord, ARect do
Result := (X >= Left) and (X <= Right) and (Y >= Top) and (Y <= Bottom);
end;
function GridRect(Coord1, Coord2: TGridCoord): TGridRect;
begin
with Result do
begin
Left := Coord2.X;
if Coord1.X < Coord2.X then Left := Coord1.X;
Right := Coord1.X;
if Coord1.X < Coord2.X then Right := Coord2.X;
Top := Coord2.Y;
if Coord1.Y < Coord2.Y then Top := Coord1.Y;
Bottom := Coord1.Y;
if Coord1.Y < Coord2.Y then Bottom := Coord2.Y;
end;
end;
function GridRectUnion(const ARect1, ARect2: TGridRect): TGridRect;
begin
with Result do
begin
Left := ARect1.Left;
if ARect1.Left > ARect2.Left then Left := ARect2.Left;
Right := ARect1.Right;
if ARect1.Right < ARect2.Right then Right := ARect2.Right;
Top := ARect1.Top;
if ARect1.Top > ARect2.Top then Top := ARect2.Top;
Bottom := ARect1.Bottom;
if ARect1.Bottom < ARect2.Bottom then Bottom := ARect2.Bottom;
end;
end;
function PointInGridRect(Col, Row: Longint; const Rect: TGridRect): Boolean;
begin
Result := (Col >= Rect.Left) and (Col <= Rect.Right) and (Row >= Rect.Top)
and (Row <= Rect.Bottom);
end;
type
TXorRects = array[0..3] of TRect;
procedure XorRects(const R1, R2: TRect; var XorRects: TXorRects);
var
Intersect, Union: TRect;
function PtInRect(X, Y: Integer; const Rect: TRect): Boolean;
begin
with Rect do Result := (X >= Left) and (X <= Right) and (Y >= Top) and
(Y <= Bottom);
end;
function Includes(const P1: TPoint; var P2: TPoint): Boolean;
begin
with P1 do
begin
Result := PtInRect(X, Y, R1) or PtInRect(X, Y, R2);
if Result then P2 := P1;
end;
end;
function Build(var R: TRect; const P1, P2, P3: TPoint): Boolean;
begin
Build := True;
with R do
if Includes(P1, TopLeft) then
begin
if not Includes(P3, BottomRight) then BottomRight := P2;
end
else if Includes(P2, TopLeft) then BottomRight := P3
else Build := False;
end;
begin
FillChar(XorRects, SizeOf(XorRects), 0);
if not Bool(IntersectRect(Intersect, R1, R2)) then
begin
{ Don't intersect so its simple }
XorRects[0] := R1;
XorRects[1] := R2;
end
else
begin
UnionRect(Union, R1, R2);
if Build(XorRects[0],
Point(Union.Left, Union.Top),
Point(Union.Left, Intersect.Top),
Point(Union.Left, Intersect.Bottom)) then
XorRects[0].Right := Intersect.Left;
if Build(XorRects[1],
Point(Intersect.Left, Union.Top),
Point(Intersect.Right, Union.Top),
Point(Union.Right, Union.Top)) then
XorRects[1].Bottom := Intersect.Top;
if Build(XorRects[2],
Point(Union.Right, Intersect.Top),
Point(Union.Right, Intersect.Bottom),
Point(Union.Right, Union.Bottom)) then
XorRects[2].Left := Intersect.Right;
if Build(XorRects[3],
Point(Union.Left, Union.Bottom),
Point(Intersect.Left, Union.Bottom),
Point(Intersect.Right, Union.Bottom)) then
XorRects[3].Top := Intersect.Bottom;
end;
end;
procedure ModifyExtents(var Extents: Pointer; Index, Amount: Longint;
Default: Integer);
var
LongSize: LongInt;
NewSize: Cardinal;
OldSize: Cardinal;
I: Cardinal;
begin
if Amount <> 0 then
begin
if not Assigned(Extents) then OldSize := 0
else OldSize := PIntArray(Extents)^[0];
if (Index < 0) or (OldSize < Index) then InvalidOp(SIndexOutOfRange);
LongSize := OldSize + Amount;
if LongSize < 0 then InvalidOp(STooManyDeleted)
else if LongSize >= MaxListSize - 1 then InvalidOp(SGridTooLarge);
NewSize := Cardinal(LongSize);
if NewSize > 0 then Inc(NewSize);
ReallocMem(Extents, NewSize * SizeOf(Integer));
if Assigned(Extents) then
begin
I := Index;
while I < NewSize do
begin
PIntArray(Extents)^[I] := Default;
Inc(I);
end;
PIntArray(Extents)^[0] := NewSize-1;
end;
end;
end;
procedure UpdateExtents(var Extents: Pointer; NewSize: Longint;
Default: Integer);
var
OldSize: Integer;
begin
OldSize := 0;
if Assigned(Extents) then OldSize := PIntArray(Extents)^[0];
ModifyExtents(Extents, OldSize, NewSize - OldSize, Default);
end;
procedure MoveExtent(var Extents: Pointer; FromIndex, ToIndex: Longint);
var
Extent: Integer;
begin
if Assigned(Extents) then
begin
Extent := PIntArray(Extents)^[FromIndex];
if FromIndex < ToIndex then
Move(PIntArray(Extents)^[FromIndex + 1], PIntArray(Extents)^[FromIndex],
(ToIndex - FromIndex) * SizeOf(Integer))
else if FromIndex > ToIndex then
Move(PIntArray(Extents)^[ToIndex], PIntArray(Extents)^[ToIndex + 1],
(FromIndex - ToIndex) * SizeOf(Integer));
PIntArray(Extents)^[ToIndex] := Extent;
end;
end;
function CompareExtents(E1, E2: Pointer): Boolean;
var
I: Integer;
begin
Result := False;
if E1 <> nil then
begin
if E2 <> nil then
begin
for I := 0 to PIntArray(E1)^[0] do
if PIntArray(E1)^[I] <> PIntArray(E2)^[I] then Exit;
Result := True;
end
end
else Result := E2 = nil;
end;
{ Private. LongMulDiv multiplys the first two arguments and then
divides by the third. This is used so that real number
(floating point) arithmetic is not necessary. This routine saves
the possible 64-bit value in a temp before doing the divide. Does
not do error checking like divide by zero. Also assumes that the
result is in the 32-bit range (Actually 31-bit, since this algorithm
is for unsigned). }
function LongMulDiv(Mult1, Mult2, Div1: Longint): Longint; stdcall;
external 'kernel32.dll' name 'MulDiv';
type
TSelection = record
StartPos, EndPos: Integer;
end;
constructor TInplaceEdit.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ParentCtl3D := False;
Ctl3D := False;
TabStop := False;
BorderStyle := bsNone;
end;
procedure TInplaceEdit.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.Style := Params.Style or ES_MULTILINE;
end;
procedure TInplaceEdit.SetGrid(Value: TCustomGrid);
begin
FGrid := Value;
end;
procedure TInplaceEdit.CMShowingChanged(var Message: TMessage);
begin
{ Ignore showing using the Visible property }
end;
procedure TInplaceEdit.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
inherited;
if goTabs in Grid.Options then
Message.Result := Message.Result or DLGC_WANTTAB;
end;
procedure TInplaceEdit.WMPaste(var Message);
begin
if not EditCanModify then Exit;
inherited
end;
procedure TInplaceEdit.WMClear(var Message);
begin
if not EditCanModify then Exit;
inherited;
end;
procedure TInplaceEdit.WMCut(var Message);
begin
if not EditCanModify then Exit;
inherited;
end;
procedure TInplaceEdit.DblClick;
begin
Grid.DblClick;
end;
function TInplaceEdit.EditCanModify: Boolean;
begin
Result := Grid.CanEditModify;
end;
procedure TInplaceEdit.KeyDown(var Key: Word; Shift: TShiftState);
procedure SendToParent;
begin
Grid.KeyDown(Key, Shift);
Key := 0;
end;
procedure ParentEvent;
var
GridKeyDown: TKeyEvent;
begin
GridKeyDown := Grid.OnKeyDown;
if Assigned(GridKeyDown) then GridKeyDown(Grid, Key, Shift);
end;
function ForwardMovement: Boolean;
begin
Result := goAlwaysShowEditor in Grid.Options;
end;
function Ctrl: Boolean;
begin
Result := ssCtrl in Shift;
end;
function Selection: TSelection;
begin
SendMessage(Handle, EM_GETSEL, Longint(@Result.StartPos), Longint(@Result.EndPos));
end;
function RightSide: Boolean;
begin
with Selection do
Result := ((StartPos = 0) or (EndPos = StartPos)) and
(EndPos = GetTextLen);
end;
function LeftSide: Boolean;
begin
with Selection do
Result := (StartPos = 0) and ((EndPos = 0) or (EndPos = GetTextLen));
end;
begin
case Key of
VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT, VK_ESCAPE: SendToParent;
VK_INSERT:
if Shift = [] then SendToParent
else if (Shift = [ssShift]) and not Grid.CanEditModify then Key := 0;
VK_LEFT: if ForwardMovement and (Ctrl or LeftSide) then SendToParent;
VK_RIGHT: if ForwardMovement and (Ctrl or RightSide) then SendToParent;
VK_HOME: if ForwardMovement and (Ctrl or LeftSide) then SendToParent;
VK_END: if ForwardMovement and (Ctrl or RightSide) then SendToParent;
VK_F2:
begin
ParentEvent;
if Key = VK_F2 then
begin
Deselect;
Exit;
end;
end;
VK_TAB: if not (ssAlt in Shift) then SendToParent;
end;
if (Key = VK_DELETE) and not Grid.CanEditModify then Key := 0;
if Key <> 0 then
begin
ParentEvent;
inherited KeyDown(Key, Shift);
end;
end;
procedure TInplaceEdit.KeyPress(var Key: Char);
var
Selection: TSelection;
begin
Grid.KeyPress(Key);
if (Key in [#32..#255]) and not Grid.CanEditAcceptKey(Key) then
begin
Key := #0;
MessageBeep(0);
end;
case Key of
#9, #27: Key := #0;
#13:
begin
SendMessage(Handle, EM_GETSEL, Longint(@Selection.StartPos), Longint(@Selection.EndPos));
if (Selection.StartPos = 0) and (Selection.EndPos = GetTextLen) then
Deselect else
SelectAll;
Key := #0;
end;
^H, ^V, ^X, #32..#255:
if not Grid.CanEditModify then Key := #0;
end;
if Key <> #0 then inherited KeyPress(Key);
end;
procedure TInplaceEdit.KeyUp(var Key: Word; Shift: TShiftState);
begin
Grid.KeyUp(Key, Shift);
end;
procedure TInplaceEdit.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_SETFOCUS:
begin
if GetParentForm(Self).SetFocusedControl(Grid) then Dispatch(Message);
Exit;
end;
WM_LBUTTONDOWN:
begin
if GetMessageTime - FClickTime < GetDoubleClickTime then
Message.Msg := WM_LBUTTONDBLCLK;
FClickTime := 0;
end;
end;
inherited WndProc(Message);
end;
procedure TInplaceEdit.Deselect;
begin
SendMessage(Handle, EM_SETSEL, $7FFFFFFF, Longint($FFFFFFFF));
end;
procedure TInplaceEdit.Invalidate;
var
Cur: TRect;
begin
ValidateRect(Handle, nil);
InvalidateRect(Handle, nil, True);
Windows.GetClientRect(Handle, Cur);
MapWindowPoints(Handle, Grid.Handle, Cur, 2);
ValidateRect(Grid.Handle, @Cur);
InvalidateRect(Grid.Handle, @Cur, False);
end;
procedure TInplaceEdit.Hide;
begin
if HandleAllocated and IsWindowVisible(Handle) then
begin
Invalidate;
SetWindowPos(Handle, 0, 0, 0, 0, 0, SWP_HIDEWINDOW or SWP_NOZORDER or
SWP_NOREDRAW);
if Focused then Windows.SetFocus(Grid.Handle);
end;
end;
function TInplaceEdit.PosEqual(const Rect: TRect): Boolean;
var
Cur: TRect;
begin
GetWindowRect(Handle, Cur);
MapWindowPoints(HWND_DESKTOP, Grid.Handle, Cur, 2);
Result := EqualRect(Rect, Cur);
end;
procedure TInplaceEdit.InternalMove(const Loc: TRect; Redraw: Boolean);
begin
if IsRectEmpty(Loc) then Hide
else
begin
CreateHandle;
Redraw := Redraw or not IsWindowVisible(Handle);
Invalidate;
with Loc do
SetWindowPos(Handle, HWND_TOP, Left, Top, Right - Left, Bottom - Top,
SWP_SHOWWINDOW or SWP_NOREDRAW);
BoundsChanged;
if Redraw then Invalidate;
if Grid.Focused then
Windows.SetFocus(Handle);
end;
end;
procedure TInplaceEdit.BoundsChanged;
var
R: TRect;
begin
R := Rect(2, 2, Width - 2, Height);
SendMessage(Handle, EM_SETRECTNP, 0, LongInt(@R));
SendMessage(Handle, EM_SCROLLCARET, 0, 0);
end;
procedure TInplaceEdit.UpdateLoc(const Loc: TRect);
begin
InternalMove(Loc, False);
end;
function TInplaceEdit.Visible: Boolean;
begin
Result := IsWindowVisible(Handle);
end;
procedure TInplaceEdit.Move(const Loc: TRect);
begin
InternalMove(Loc, True);
end;
procedure TInplaceEdit.SetFocus;
begin
if IsWindowVisible(Handle) then
Windows.SetFocus(Handle);
end;
procedure TInplaceEdit.UpdateContents;
begin
Text := '';
EditMask := Grid.GetEditMask(Grid.Col, Grid.Row);
Text := Grid.GetEditText(Grid.Col, Grid.Row);
MaxLength := Grid.GetEditLimit;
end;
{ TCustomGrid }
constructor TCustomGrid.Create(AOwner: TComponent);
const
GridStyle = [csCaptureMouse, csOpaque, csDoubleClicks];
begin
inherited Create(AOwner);
if NewStyleControls then
ControlStyle := GridStyle else
ControlStyle := GridStyle + [csFramed];
FCanEditModify := True;
FColCount := 5;
FRowCount := 5;
FFixedCols := 1;
FFixedRows := 1;
FGridLineWidth := 1;
FOptions := [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine,
goRangeSelect];
FFixedColor := clBtnFace;
FScrollBars := ssBoth;
FBorderStyle := bsSingle;
FDefaultColWidth := 64;
FDefaultRowHeight := 24;
FDefaultDrawing := True;
FSaveCellExtents := True;
FEditorMode := False;
Color := clWindow;
ParentColor := False;
TabStop := True;
SetBounds(Left, Top, FColCount * FDefaultColWidth,
FRowCount * FDefaultRowHeight);
Initialize;
end;
destructor TCustomGrid.Destroy;
begin
FInplaceEdit.Free;
inherited Destroy;
FreeMem(FColWidths);
FreeMem(FRowHeights);
FreeMem(FTabStops);
end;
procedure TCustomGrid.AdjustSize(Index, Amount: Longint; Rows: Boolean);
var
NewCur: TGridCoord;
OldRows, OldCols: Longint;
MovementX, MovementY: Longint;
MoveRect: TGridRect;
ScrollArea: TRect;
AbsAmount: Longint;
function DoSizeAdjust(var Count: Longint; var Extents: Pointer;
DefaultExtent: Integer; var Current: Longint): Longint;
var
I: Integer;
NewCount: Longint;
begin
NewCount := Count + Amount;
if NewCount < Index then InvalidOp(STooManyDeleted);
if (Amount < 0) and Assigned(Extents) then
begin
Result := 0;
for I := Index to Index - Amount - 1 do
Inc(Result, PIntArray(Extents)^[I]);
end
else
Result := Amount * DefaultExtent;
if Extents <> nil then
ModifyExtents(Extents, Index, Amount, DefaultExtent);
Count := NewCount;
if Current >= Index then
if (Amount < 0) and (Current < Index - Amount) then Current := Index
else Inc(Current, Amount);
end;
begin
if Amount = 0 then Exit;
NewCur := FCurrent;
OldCols := ColCount;
OldRows := RowCount;
MoveRect.Left := FixedCols;
MoveRect.Right := ColCount - 1;
MoveRect.Top := FixedRows;
MoveRect.Bottom := RowCount - 1;
MovementX := 0;
MovementY := 0;
AbsAmount := Amount;
if AbsAmount < 0 then AbsAmount := -AbsAmount;
if Rows then
begin
MovementY := DoSizeAdjust(FRowCount, FRowHeights, DefaultRowHeight, NewCur.Y);
MoveRect.Top := Index;
if Index + AbsAmount <= TopRow then MoveRect.Bottom := TopRow - 1;
end
else
begin
MovementX := DoSizeAdjust(FColCount, FColWidths, DefaultColWidth, NewCur.X);
MoveRect.Left := Index;
if Index + AbsAmount <= LeftCol then MoveRect.Right := LeftCol - 1;
end;
GridRectToScreenRect(MoveRect, ScrollArea, True);
if not IsRectEmpty(ScrollArea) then
begin
ScrollWindow(Handle, MovementX, MovementY, @ScrollArea, @ScrollArea);
UpdateWindow(Handle);
end;
SizeChanged(OldCols, OldRows);
if (NewCur.X <> FCurrent.X) or (NewCur.Y <> FCurrent.Y) then
MoveCurrent(NewCur.X, NewCur.Y, True, True);
end;
function TCustomGrid.BoxRect(ALeft, ATop, ARight, ABottom: Longint): TRect;
var
GridRect: TGridRect;
begin
GridRect.Left := ALeft;
GridRect.Right := ARight;
GridRect.Top := ATop;
GridRect.Bottom := ABottom;
GridRectToScreenRect(GridRect, Result, False);
end;
procedure TCustomGrid.DoExit;
begin
inherited DoExit;
if not (goAlwaysShowEditor in Options) then HideEditor;
end;
function TCustomGrid.CellRect(ACol, ARow: Longint): TRect;
begin
Result := BoxRect(ACol, ARow, ACol, ARow);
end;
function TCustomGrid.CanEditAcceptKey(Key: Char): Boolean;
begin
Result := True;
end;
function TCustomGrid.CanGridAcceptKey(Key: Word; Shift: TShiftState): Boolean;
begin
Result := True;
end;
function TCustomGrid.CanEditModify: Boolean;
begin
Result := FCanEditModify;
end;
function TCustomGrid.CanEditShow: Boolean;
begin
Result := ([goRowSelect, goEditing] * Options = [goEditing]) and
FEditorMode and not (csDesigning in ComponentState) and HandleAllocated and
((goAlwaysShowEditor in Options) or (ValidParentForm(Self).ActiveControl = Self));
end;
function TCustomGrid.GetEditMask(ACol, ARow: Longint): string;
begin
Result := '';
end;
function TCustomGrid.GetEditText(ACol, ARow: Longint): string;
begin
Result := '';
end;
procedure TCustomGrid.SetEditText(ACol, ARow: Longint; const Value: string);
begin
end;
function TCustomGrid.GetEditLimit: Integer;
begin
Result := 0;
end;
procedure TCustomGrid.HideEditor;
begin
FEditorMode := False;
HideEdit;
end;
procedure TCustomGrid.ShowEditor;
begin
FEditorMode := True;
UpdateEdit;
end;
procedure TCustomGrid.ShowEditorChar(Ch: Char);
begin
ShowEditor;
if FInplaceEdit <> nil then
PostMessage(FInplaceEdit.Handle, WM_CHAR, Word(Ch), 0);
end;
procedure TCustomGrid.InvalidateEditor;
begin
FInplaceCol := -1;
FInplaceRow := -1;
UpdateEdit;
end;
procedure TCustomGrid.ReadColWidths(Reader: TReader);
var
I: Integer;
begin
with Reader do
begin
ReadListBegin;
for I := 0 to ColCount - 1 do ColWidths[I] := ReadInteger;
ReadListEnd;
end;
end;
procedure TCustomGrid.ReadRowHeights(Reader: TReader);
var
I: Integer;
begin
with Reader do
begin
ReadListBegin;
for I := 0 to RowCount - 1 do RowHeights[I] := ReadInteger;
ReadListEnd;
end;
end;
procedure TCustomGrid.WriteColWidths(Writer: TWriter);
var
I: Integer;
begin
with Writer do
begin
WriteListBegin;
for I := 0 to ColCount - 1 do WriteInteger(ColWidths[I]);
WriteListEnd;
end;
end;
procedure TCustomGrid.WriteRowHeights(Writer: TWriter);
var
I: Integer;
begin
with Writer do
begin
WriteListBegin;
for I := 0 to RowCount - 1 do WriteInteger(RowHeights[I]);
WriteListEnd;
end;
end;
procedure TCustomGrid.DefineProperties(Filer: TFiler);
function DoColWidths: Boolean;
begin
if Filer.Ancestor <> nil then
Result := not CompareExtents(TCustomGrid(Filer.Ancestor).FColWidths, FColWidths)
else
Result := FColWidths <> nil;
end;
function DoRowHeights: Boolean;
begin
if Filer.Ancestor <> nil then
Result := not CompareExtents(TCustomGrid(Filer.Ancestor).FRowHeights, FRowHeights)
else
Result := FRowHeights <> nil;
end;
begin
inherited DefineProperties(Filer);
if FSaveCellExtents then
with Filer do
begin
DefineProperty('ColWidths', ReadColWidths, WriteColWidths, DoColWidths);
DefineProperty('RowHeights', ReadRowHeights, WriteRowHeights, DoRowHeights);
end;
end;
procedure TCustomGrid.MoveColumn(FromIndex, ToIndex: Longint);
var
Rect: TGridRect;
begin
if FromIndex = ToIndex then Exit;
if Assigned(FColWidths) then
begin
MoveExtent(FColWidths, FromIndex + 1, ToIndex + 1);
MoveExtent(FTabStops, FromIndex + 1, ToIndex + 1);
end;
MoveAdjust(FCurrent.X, FromIndex, ToIndex);
MoveAdjust(FAnchor.X, FromIndex, ToIndex);
MoveAdjust(FInplaceCol, FromIndex, ToIndex);
Rect.Top := 0;
Rect.Bottom := VisibleRowCount;
if FromIndex < ToIndex then
begin
Rect.Left := FromIndex;
Rect.Right := ToIndex;
end
else
begin
Rect.Left := ToIndex;
Rect.Right := FromIndex;
end;
InvalidateRect(Rect);
ColumnMoved(FromIndex, ToIndex);
if Assigned(FColWidths) then
ColWidthsChanged;
UpdateEdit;
end;
procedure TCustomGrid.ColumnMoved(FromIndex, ToIndex: Longint);
begin
end;
procedure TCustomGrid.MoveRow(FromIndex, ToIndex: Longint);
begin
if Assigned(FRowHeights) then
MoveExtent(FRowHeights, FromIndex + 1, ToIndex + 1);
MoveAdjust(FCurrent.Y, FromIndex, ToIndex);
MoveAdjust(FAnchor.Y, FromIndex, ToIndex);
MoveAdjust(FInplaceRow, FromIndex, ToIndex);
RowMoved(FromIndex, ToIndex);
if Assigned(FRowHeights) then
RowHeightsChanged;
UpdateEdit;
end;
procedure TCustomGrid.RowMoved(FromIndex, ToIndex: Longint);
begin
end;
function TCustomGrid.MouseCoord(X, Y: Integer): TGridCoord;
var
DrawInfo: TGridDrawInfo;
begin
CalcDrawInfo(DrawInfo);
Result := CalcCoordFromPoint(X, Y, DrawInfo);
if Result.X < 0 then Result.Y := -1
else if Result.Y < 0 then Result.X := -1;
end;
procedure TCustomGrid.MoveColRow(ACol, ARow: Longint; MoveAnchor,
Show: Boolean);
begin
MoveCurrent(ACol, ARow, MoveAnchor, Show);
end;
function TCustomGrid.SelectCell(ACol, ARow: Longint): Boolean;
begin
Result := True;
end;
procedure TCustomGrid.SizeChanged(OldColCount, OldRowCount: Longint);
begin
end;
function TCustomGrid.Sizing(X, Y: Integer): Boolean;
var
FixedInfo: TGridDrawInfo;
State: TGridState;
Index: Longint;
Pos, Ofs: Integer;
begin
State := FGridState;
if State = gsNormal then
begin
CalcFixedInfo(FixedInfo);
CalcSizingState(X, Y, State, Index, Pos, Ofs, FixedInfo);
end;
Result := State <> gsNormal;
end;
procedure TCustomGrid.TopLeftChanged;
begin
if FEditorMode and (FInplaceEdit <> nil) then FInplaceEdit.UpdateLoc(CellRect(Col, Row));
end;
procedure FillDWord(var Dest; Count, Value: Integer); register;
asm
XCHG EDX, ECX
PUSH EDI
MOV EDI, EAX
MOV EAX, EDX
REP STOSD
POP EDI
end;
{ StackAlloc allocates a 'small' block of memory from the stack by
decrementing SP. This provides the allocation speed of a local variable,
but the runtime size flexibility of heap allocated memory. }
function StackAlloc(Size: Integer): Pointer; register;
asm
POP ECX { return address }
MOV EDX, ESP
SUB ESP, EAX
MOV EAX, ESP { function result = low memory address of block }
PUSH EDX { save original SP, for cleanup }
MOV EDX, ESP
SUB EDX, 4
PUSH EDX { save current SP, for sanity check (sp = [sp]) }
PUSH ECX { return to caller }
end;
{ StackFree pops the memory allocated by StackAlloc off the stack.
- Calling StackFree is optional - SP will be restored when the calling routine
exits, but it's a good idea to free the stack allocated memory ASAP anyway.
- StackFree must be called in the same stack context as StackAlloc - not in
a subroutine or finally block.
- Multiple StackFree calls must occur in reverse order of their corresponding
StackAlloc calls.
- Built-in sanity checks guarantee that an improper call to StackFree will not
corrupt the stack. Worst case is that the stack block is not released until
the calling routine exits. }
procedure StackFree(P: Pointer); register;
asm
POP ECX { return address }
MOV EDX, DWORD PTR [ESP]
SUB EAX, 8
CMP EDX, ESP { sanity check #1 (SP = [SP]) }
JNE @@1
CMP EDX, EAX { sanity check #2 (P = this stack block) }
JNE @@1
MOV ESP, DWORD PTR [ESP+4] { restore previous SP }
@@1:
PUSH ECX { return to caller }
end;
procedure TCustomGrid.Paint;
var
LineColor: TColor;
DrawInfo: TGridDrawInfo;
Sel: TGridRect;
UpdateRect: TRect;
FocRect: TRect;
PointsList: PIntArray;
StrokeList: PIntArray;
MaxStroke: Integer;
FrameFlags1, FrameFlags2: DWORD;
procedure DrawLines(DoHorz, DoVert: Boolean; Col, Row: Longint;
const CellBounds: array of Integer; OnColor, OffColor: TColor);
{ Cellbounds is 4 integers: StartX, StartY, StopX, StopY
Horizontal lines: MajorIndex = 0
Vertical lines: MajorIndex = 1 }
const
FlatPenStyle = PS_Geometric or PS_Solid or PS_EndCap_Flat or PS_Join_Miter;
procedure DrawAxisLines(const AxisInfo: TGridAxisDrawInfo;
Cell, MajorIndex: Integer; UseOnColor: Boolean);
var
Line: Integer;
LogBrush: TLOGBRUSH;
Index: Integer;
Points: PIntArray;
StopMajor, StartMinor, StopMinor: Integer;
begin
with Canvas, AxisInfo do
begin
if EffectiveLineWidth <> 0 then
begin
Pen.Width := GridLineWidth;
if UseOnColor then
Pen.Color := OnColor
else
Pen.Color := OffColor;
if Pen.Width > 1 then
begin
LogBrush.lbStyle := BS_Solid;
LogBrush.lbColor := Pen.Color;
LogBrush.lbHatch := 0;
Pen.Handle := ExtCreatePen(FlatPenStyle, Pen.Width, LogBrush, 0, nil);
end;
Points := PointsList;
Line := CellBounds[MajorIndex] + EffectiveLineWidth shr 1 +
GetExtent(Cell);
StartMinor := CellBounds[MajorIndex xor 1];
StopMinor := CellBounds[2 + (MajorIndex xor 1)];
StopMajor := CellBounds[2 + MajorIndex] + EffectiveLineWidth;
Index := 0;
repeat
Points^[Index + MajorIndex] := Line; { MoveTo }
Points^[Index + (MajorIndex xor 1)] := StartMinor;
Inc(Index, 2);
Points^[Index + MajorIndex] := Line; { LineTo }
Points^[Index + (MajorIndex xor 1)] := StopMinor;
Inc(Index, 2);
Inc(Cell);
Inc(Line, GetExtent(Cell) + EffectiveLineWidth);
until Line > StopMajor;
{ 2 integers per point, 2 points per line -> Index div 4 }
PolyPolyLine(Canvas.Handle, Points^, StrokeList^, Index shr 2);
end;
end;
end;
begin
if (CellBounds[0] = CellBounds[2]) or (CellBounds[1] = CellBounds[3]) then Exit;
if not DoHorz then
begin
DrawAxisLines(DrawInfo.Vert, Row, 1, DoHorz);
DrawAxisLines(DrawInfo.Horz, Col, 0, DoVert);
end
else
begin
DrawAxisLines(DrawInfo.Horz, Col, 0, DoVert);
DrawAxisLines(DrawInfo.Vert, Row, 1, DoHorz);
end;
end;
procedure DrawCells(ACol, ARow: Longint; StartX, StartY, StopX, StopY: Integer;
Color: TColor; IncludeDrawState: TGridDrawState);
var
CurCol, CurRow: Longint;
Where, TempRect: TRect;
DrawState: TGridDrawState;
Focused: Boolean;
begin
CurRow := ARow;
Where.Top := StartY;
while (Where.Top < StopY) and (CurRow < RowCount) do
begin
CurCol := ACol;
Where.Left := StartX;
Where.Bottom := Where.Top + RowHeights[CurRow];
while (Where.Left < StopX) and (CurCol < ColCount) do
begin
Where.Right := Where.Left + ColWidths[CurCol];
if RectVisible(Canvas.Handle, Where) then
begin
DrawState := IncludeDrawState;
Focused := ValidParentForm(Self).ActiveControl = Self;
if Focused and (CurRow = Row) and (CurCol = Col) then
Include(DrawState, gdFocused);
if PointInGridRect(CurCol, CurRow, Sel) then
Include(DrawState, gdSelected);
if not (gdFocused in DrawState) or not (goEditing in Options) or
not FEditorMode or (csDesigning in ComponentState) then
begin
if DefaultDrawing or (csDesigning in ComponentState) then
with Canvas do
begin
Font := Self.Font;
if (gdSelected in DrawState) and
(not (gdFocused in DrawState) or
([goDrawFocusSelected, goRowSelect] * Options <> [])) then
begin
Brush.Color := clHighlight;
Font.Color := clHighlightText;
end
else
Brush.Color := Color;
FillRect(Where);
end;
DrawCell(CurCol, CurRow, Where, DrawState);
if DefaultDrawing and (gdFixed in DrawState) and Ctl3D and
((FrameFlags1 or FrameFlags2) <> 0) then
begin
TempRect := Where;
if (FrameFlags1 and BF_RIGHT) = 0 then
Inc(TempRect.Right, DrawInfo.Horz.EffectiveLineWidth)
else if (FrameFlags1 and BF_BOTTOM) = 0 then
Inc(TempRect.Bottom, DrawInfo.Vert.EffectiveLineWidth);
DrawEdge(Canvas.Handle, TempRect, BDR_RAISEDINNER, FrameFlags1);
DrawEdge(Canvas.Handle, TempRect, BDR_RAISEDINNER, FrameFlags2);
end;
if DefaultDrawing and not (csDesigning in ComponentState) and
(gdFocused in DrawState) and
([goEditing, goAlwaysShowEditor] * Options <>
[goEditing, goAlwaysShowEditor])
and not (goRowSelect in Options) then
DrawFocusRect(Canvas.Handle, Where);
end;
end;
Where.Left := Where.Right + DrawInfo.Horz.EffectiveLineWidth;
Inc(CurCol);
end;
Where.Top := Where.Bottom + DrawInfo.Vert.EffectiveLineWidth;
Inc(CurRow);
end;
end;
begin
UpdateRect := Canvas.ClipRect;
CalcDrawInfo(DrawInfo);
with DrawInfo do
begin
if (Horz.EffectiveLineWidth > 0) or (Vert.EffectiveLineWidth > 0) then
begin
{ Draw the grid line in the four areas (fixed, fixed), (variable, fixed),
(fixed, variable) and (variable, variable) }
LineColor := clSilver;
MaxStroke := IMax(Horz.LastFullVisibleCell - LeftCol + FixedCols,
Vert.LastFullVisibleCell - TopRow + FixedRows) + 3;
PointsList := StackAlloc(MaxStroke * sizeof(TPoint) * 2);
StrokeList := StackAlloc(MaxStroke * sizeof(Integer));
FillDWord(StrokeList^, MaxStroke, 2);
if ColorToRGB(Color) = clSilver then LineColor := clGray;
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, 0, [Horz.FixedBoundary, 0, Horz.GridBoundary,
Vert.FixedBoundary], clBlack, FixedColor);
DrawLines(goFixedHorzLine in Options, goFixedVertLine in Options,
0, TopRow, [0, Vert.FixedBoundary, Horz.FixedBoundary,
Vert.GridBoundary], clBlack, FixedColor);
DrawLines(goHorzLine in Options, goVertLine in Options, LeftCol,
TopRow, [Horz.FixedBoundary, Vert.FixedBoundary, Horz.GridBoundary,
Vert.GridBoundary], LineColor, Color);
StackFree(StrokeList);
StackFree(PointsList);
end;
{ Draw the cells in the four areas }
Sel := Selection;
FrameFlags1 := 0;
FrameFlags2 := 0;
if goFixedVertLine in Options then
begin
FrameFlags1 := BF_RIGHT;
FrameFlags2 := BF_LEFT;
end;
if goFixedHorzLine in Options then
begin
FrameFlags1 := FrameFlags1 or BF_BOTTOM;
FrameFlags2 := FrameFlags2 or BF_TOP;
end;
DrawCells(0, 0, 0, 0, Horz.FixedBoundary, Vert.FixedBoundary, FixedColor,
[gdFixed]);
DrawCells(LeftCol, 0, Horz.FixedBoundary - FColOffset, 0, Horz.GridBoundary,
Vert.FixedBoundary, FixedColor, [gdFixed]);
DrawCells(0, TopRow, 0, Vert.FixedBoundary, Horz.FixedBoundary,
Vert.GridBoundary, FixedColor, [gdFixed]);
DrawCells(LeftCol, TopRow, Horz.FixedBoundary - FColOffset,
Vert.FixedBoundary, Horz.GridBoundary, Vert.GridBoundary, Color, []);
if not (csDesigning in ComponentState) and
(goRowSelect in Options) and DefaultDrawing and Focused then
begin
GridRectToScreenRect(GetSelection, FocRect, False);
Canvas.DrawFocusRect(FocRect);
end;
{ Fill in area not occupied by cells }
if Horz.GridBoundary < Horz.GridExtent then
begin
Canvas.Brush.Color := Color;
Canvas.FillRect(Rect(Horz.GridBoundary, 0, Horz.GridExtent, Vert.GridBoundary));
end;
if Vert.GridBoundary < Vert.GridExtent then
begin
Canvas.Brush.Color := Color;
Canvas.FillRect(Rect(0, Vert.GridBoundary, Horz.GridExtent, Vert.GridExtent));
end;
end;
end;
function TCustomGrid.CalcCoordFromPoint(X, Y: Integer;
const DrawInfo: TGridDrawInfo): TGridCoord;
function DoCalc(const AxisInfo: TGridAxisDrawInfo; N: Integer): Integer;
var
I, Start, Stop: Longint;
Line: Integer;
begin
with AxisInfo do
begin
if N < FixedBoundary then
begin
Start := 0;
Stop := FixedCellCount - 1;
Line := 0;
end
else
begin
Start := FirstGridCell;
Stop := GridCellCount - 1;
Line := FixedBoundary;
end;
Result := -1;
for I := Start to Stop do
begin
Inc(Line, GetExtent(I) + EffectiveLineWidth);
if N < Line then
begin
Result := I;
Exit;
end;
end;
end;
end;
begin
Result.X := DoCalc(DrawInfo.Horz, X);
Result.Y := DoCalc(DrawInfo.Vert, Y);
end;
procedure TCustomGrid.CalcDrawInfo(var DrawInfo: TGridDrawInfo);
begin
CalcDrawInfoXY(DrawInfo, ClientWidth, ClientHeight);
end;
procedure TCustomGrid.CalcDrawInfoXY(var DrawInfo: TGridDrawInfo;
UseWidth, UseHeight: Integer);
procedure CalcAxis(var AxisInfo: TGridAxisDrawInfo; UseExtent: Integer);
var
I: Integer;
begin
with AxisInfo do
begin
GridExtent := UseExtent;
GridBoundary := FixedBoundary;
FullVisBoundary := FixedBoundary;
LastFullVisibleCell := FirstGridCell;
for I := FirstGridCell to GridCellCount - 1 do
begin
Inc(GridBoundary, GetExtent(I) + EffectiveLineWidth);
if GridBoundary > GridExtent + EffectiveLineWidth then
begin
GridBoundary := GridExtent;
Break;
end;
LastFullVisibleCell := I;
FullVisBoundary := GridBoundary;
end;
end;
end;
begin
CalcFixedInfo(DrawInfo);
CalcAxis(DrawInfo.Horz, UseWidth);
CalcAxis(DrawInfo.Vert, UseHeight);
end;
procedure TCustomGrid.CalcFixedInfo(var DrawInfo: TGridDrawInfo);
procedure CalcFixedAxis(var Axis: TGridAxisDrawInfo; LineOptions: TGridOptions;
FixedCount, FirstCell, CellCount: Integer; GetExtentFunc: TGetExtentsFunc);
var
I: Integer;
begin
with Axis do
begin
if LineOptions * Options = [] then
EffectiveLineWidth := 0
else
EffectiveLineWidth := GridLineWidth;
FixedBoundary := 0;
for I := 0 to FixedCount - 1 do
Inc(FixedBoundary, GetExtentFunc(I) + EffectiveLineWidth);
FixedCellCount := FixedCount;
FirstGridCell := FirstCell;
GridCellCount := CellCount;
GetExtent := GetExtentFunc;
end;
end;
begin
CalcFixedAxis(DrawInfo.Horz, [goFixedVertLine, goVertLine], FixedCols,
LeftCol, ColCount, GetColWidths);
CalcFixedAxis(DrawInfo.Vert, [goFixedHorzLine, goHorzLine], FixedRows,
TopRow, RowCount, GetRowHeights);
end;
{ Calculates the TopLeft that will put the given Coord in view }
function TCustomGrid.CalcMaxTopLeft(const Coord: TGridCoord;
const DrawInfo: TGridDrawInfo): TGridCoord;
function CalcMaxCell(const Axis: TGridAxisDrawInfo; Start: Integer): Integer;
var
Line: Integer;
I: Longint;
begin
Result := Start;
with Axis do
begin
Line := GridExtent + EffectiveLineWidth;
for I := Start downto FixedCellCount do
begin
Dec(Line, GetExtent(I));
Dec(Line, EffectiveLineWidth);
if Line < FixedBoundary then Break;
Result := I;
end;
end;
end;
begin
Result.X := CalcMaxCell(DrawInfo.Horz, Coord.X);
Result.Y := CalcMaxCell(DrawInfo.Vert, Coord.Y);
end;
procedure TCustomGrid.CalcSizingState(X, Y: Integer; var State: TGridState;
var Index: Longint; var SizingPos, SizingOfs: Integer;
var FixedInfo: TGridDrawInfo);
procedure CalcAxisState(const AxisInfo: TGridAxisDrawInfo; Pos: Integer;
NewState: TGridState);
var
I, Line, Back, Range: Integer;
begin
with AxisInfo do
begin
Line := FixedBoundary;
Range := EffectiveLineWidth;
Back := 0;
if Range < 7 then
begin
Range := 7;
Back := (Range - EffectiveLineWidth) shr 1;
end;
for I := FirstGridCell to GridCellCount - 1 do
begin
Inc(Line, GetExtent(I));
if Line > GridExtent then Break;
if (Pos >= Line - Back) and (Pos <= Line - Back + Range) then
begin
State := NewState;
SizingPos := Line;
SizingOfs := Line - Pos;
Index := I;
Exit;
end;
Inc(Line, EffectiveLineWidth);
end;
if (Pos >= GridExtent - Back) and (Pos <= GridExtent) then
begin
State := NewState;
SizingPos := GridExtent;
SizingOfs := GridExtent - Pos;
Index := I;
end;
end;
end;
var
EffectiveOptions: TGridOptions;
begin
State := gsNormal;
Index := -1;
EffectiveOptions := Options;
if csDesigning in ComponentState then
begin
Include(EffectiveOptions, goColSizing);
Include(EffectiveOptions, goRowSizing);
end;
if [goColSizing, goRowSizing] * EffectiveOptions <> [] then
with FixedInfo do
begin
Vert.GridExtent := ClientHeight;
Horz.GridExtent := ClientWidth;
if (X > Horz.FixedBoundary) and (goColSizing in EffectiveOptions) then
begin
if Y >= Vert.FixedBoundary then Exit;
CalcAxisState(Horz, X, gsColSizing);
end
else if (Y > Vert.FixedBoundary) and (goRowSizing in EffectiveOptions) then
begin
if X >= Horz.FixedBoundary then Exit;
CalcAxisState(Vert, Y, gsRowSizing);
end;
end;
end;
procedure TCustomGrid.ChangeSize(NewColCount, NewRowCount: Longint);
var
OldColCount, OldRowCount: Longint;
OldDrawInfo: TGridDrawInfo;
procedure MinRedraw(const OldInfo, NewInfo: TGridAxisDrawInfo; Axis: Integer);
var
R: TRect;
First: Integer;
begin
if (OldInfo.LastFullVisibleCell = NewInfo.LastFullVisibleCell) then Exit;
First := IMin(OldInfo.LastFullVisibleCell, NewInfo.LastFullVisibleCell);
// Get the rectangle around the leftmost or topmost cell in the target range.
R := CellRect(First and not Axis, First and Axis);
R.Bottom := Height;
R.Right := Width;
Windows.InvalidateRect(Handle, @R, False);
end;
procedure DoChange;
var
Coord: TGridCoord;
NewDrawInfo: TGridDrawInfo;
begin
if FColWidths <> nil then
begin
UpdateExtents(FColWidths, ColCount, DefaultColWidth);
UpdateExtents(FTabStops, ColCount, Integer(True));
end;
if FRowHeights <> nil then
UpdateExtents(FRowHeights, RowCount, DefaultRowHeight);
Coord := FCurrent;
if Row >= RowCount then Coord.Y := RowCount - 1;
if Col >= ColCount then Coord.X := ColCount - 1;
if (FCurrent.X <> Coord.X) or (FCurrent.Y <> Coord.Y) then
MoveCurrent(Coord.X, Coord.Y, True, True);
if (FAnchor.X <> Coord.X) or (FAnchor.Y <> Coord.Y) then
MoveAnchor(Coord);
if (LeftCol <> OldDrawInfo.Horz.FirstGridCell) or
(TopRow <> OldDrawInfo.Vert.FirstGridCell) then
InvalidateGrid
else if HandleAllocated then
begin
CalcDrawInfo(NewDrawInfo);
MinRedraw(OldDrawInfo.Horz, NewDrawInfo.Horz, 0);
MinRedraw(OldDrawInfo.Vert, NewDrawInfo.Vert, -1);
end;
UpdateScrollRange;
SizeChanged(OldColCount, OldRowCount);
end;
begin
if HandleAllocated then
CalcDrawInfo(OldDrawInfo);
OldColCount := FColCount;
OldRowCount := FRowCount;
FColCount := NewColCount;
FRowCount := NewRowCount;
if FixedCols > NewColCount then FFixedCols := NewColCount - 1;
if FixedRows > NewRowCount then FFixedRows := NewRowCount - 1;
try
DoChange;
except
{ Could not change size so try to clean up by setting the size back }
FColCount := OldColCount;
FRowCount := OldRowCount;
DoChange;
InvalidateGrid;
raise;
end;
end;
{ Will move TopLeft so that Coord is in view }
procedure TCustomGrid.ClampInView(const Coord: TGridCoord);
var
DrawInfo: TGridDrawInfo;
MaxTopLeft: TGridCoord;
OldTopLeft: TGridCoord;
begin
if not HandleAllocated then Exit;
CalcDrawInfo(DrawInfo);
with DrawInfo, Coord do
begin
if (X > Horz.LastFullVisibleCell) or
(Y > Vert.LastFullVisibleCell) or (X < LeftCol) or (Y < TopRow) then
begin
OldTopLeft := FTopLeft;
MaxTopLeft := CalcMaxTopLeft(Coord, DrawInfo);
Update;
if X < LeftCol then FTopLeft.X := X
else if X > Horz.LastFullVisibleCell then FTopLeft.X := MaxTopLeft.X;
if Y < TopRow then FTopLeft.Y := Y
else if Y > Vert.LastFullVisibleCell then FTopLeft.Y := MaxTopLeft.Y;
TopLeftMoved(OldTopLeft);
end;
end;
end;
procedure TCustomGrid.DrawSizingLine(const DrawInfo: TGridDrawInfo);
var
OldPen: TPen;
begin
OldPen := TPen.Create;
try
with Canvas, DrawInfo do
begin
OldPen.Assign(Pen);
Pen.Style := psDot;
Pen.Mode := pmXor;
Pen.Width := 1;
try
if FGridState = gsRowSizing then
begin
MoveTo(0, FSizingPos);
LineTo(Horz.GridBoundary, FSizingPos);
end
else
begin
MoveTo(FSizingPos, 0);
LineTo(FSizingPos, Vert.GridBoundary);
end;
finally
Pen := OldPen;
end;
end;
finally
OldPen.Free;
end;
end;
procedure TCustomGrid.DrawMove;
var
OldPen: TPen;
Pos: Integer;
R: TRect;
begin
OldPen := TPen.Create;
try
with Canvas do
begin
OldPen.Assign(Pen);
try
Pen.Style := psDot;
Pen.Mode := pmXor;
Pen.Width := 5;
if FGridState = gsRowMoving then
begin
R := CellRect(0, FMovePos);
if FMovePos > FMoveIndex then
Pos := R.Bottom else
Pos := R.Top;
MoveTo(0, Pos);
LineTo(ClientWidth, Pos);
end
else
begin
R := CellRect(FMovePos, 0);
if FMovePos > FMoveIndex then
Pos := R.Right else
Pos := R.Left;
MoveTo(Pos, 0);
LineTo(Pos, ClientHeight);
end;
finally
Canvas.Pen := OldPen;
end;
end;
finally
OldPen.Free;
end;
end;
procedure TCustomGrid.FocusCell(ACol, ARow: Longint; MoveAnchor: Boolean);
begin
MoveCurrent(ACol, ARow, MoveAnchor, True);
UpdateEdit;
Click;
end;
procedure TCustomGrid.GridRectToScreenRect(GridRect: TGridRect;
var ScreenRect: TRect; IncludeLine: Boolean);
function LinePos(const AxisInfo: TGridAxisDrawInfo; Line: Integer): Integer;
var
Start, I: Longint;
begin
with AxisInfo do
begin
Result := 0;
if Line < FixedCellCount then
Start := 0
else
begin
if Line >= FirstGridCell then
Result := FixedBoundary;
Start := FirstGridCell;
end;
for I := Start to Line - 1 do
begin
Inc(Result, GetExtent(I) + EffectiveLineWidth);
if Result > GridExtent then
begin
Result := 0;
Exit;
end;
end;
end;
end;
function CalcAxis(const AxisInfo: TGridAxisDrawInfo;
GridRectMin, GridRectMax: Integer;
var ScreenRectMin, ScreenRectMax: Integer): Boolean;
begin
Result := False;
with AxisInfo do
begin
if (GridRectMin >= FixedCellCount) and (GridRectMin < FirstGridCell) then
if GridRectMax < FirstGridCell then
begin
FillChar(ScreenRect, SizeOf(ScreenRect), 0); { erase partial results }
Exit;
end
else
GridRectMin := FirstGridCell;
if GridRectMax > LastFullVisibleCell then
begin
GridRectMax := LastFullVisibleCell;
if GridRectMax < GridCellCount - 1 then Inc(GridRectMax);
if LinePos(AxisInfo, GridRectMax) = 0 then
Dec(GridRectMax);
end;
ScreenRectMin := LinePos(AxisInfo, GridRectMin);
ScreenRectMax := LinePos(AxisInfo, GridRectMax);
if ScreenRectMax = 0 then
ScreenRectMax := ScreenRectMin + GetExtent(GridRectMin)
else
Inc(ScreenRectMax, GetExtent(GridRectMax));
if ScreenRectMax > GridExtent then
ScreenRectMax := GridExtent;
if IncludeLine then Inc(ScreenRectMax, EffectiveLineWidth);
end;
Result := True;
end;
var
DrawInfo: TGridDrawInfo;
begin
FillChar(ScreenRect, SizeOf(ScreenRect), 0);
if (GridRect.Left > GridRect.Right) or (GridRect.Top > GridRect.Bottom) then
Exit;
CalcDrawInfo(DrawInfo);
with DrawInfo do
begin
if GridRect.Left > Horz.LastFullVisibleCell + 1 then Exit;
if GridRect.Top > Vert.LastFullVisibleCell + 1 then Exit;
if CalcAxis(Horz, GridRect.Left, GridRect.Right, ScreenRect.Left,
ScreenRect.Right) then
begin
CalcAxis(Vert, GridRect.Top, GridRect.Bottom, ScreenRect.Top,
ScreenRect.Bottom);
end;
end;
end;
procedure TCustomGrid.Initialize;
begin
FTopLeft.X := FixedCols;
FTopLeft.Y := FixedRows;
FCurrent := FTopLeft;
FAnchor := FCurrent;
if goRowSelect in Options then FAnchor.X := ColCount - 1;
end;
procedure TCustomGrid.InvalidateCell(ACol, ARow: Longint);
var
Rect: TGridRect;
begin
Rect.Top := ARow;
Rect.Left := ACol;
Rect.Bottom := ARow;
Rect.Right := ACol;
InvalidateRect(Rect);
end;
procedure TCustomGrid.InvalidateCol(ACol: Longint);
var
Rect: TGridRect;
begin
if not HandleAllocated then Exit;
Rect.Top := 0;
Rect.Left := ACol;
Rect.Bottom := VisibleRowCount+1;
Rect.Right := ACol;
InvalidateRect(Rect);
end;
procedure TCustomGrid.InvalidateRow(ARow: Longint);
var
Rect: TGridRect;
begin
if not HandleAllocated then Exit;
Rect.Top := ARow;
Rect.Left := 0;
Rect.Bottom := ARow;
Rect.Right := VisibleColCount+1;
InvalidateRect(Rect);
end;
procedure TCustomGrid.InvalidateGrid;
begin
Invalidate;
end;
procedure TCustomGrid.InvalidateRect(ARect: TGridRect);
var
InvalidRect: TRect;
begin
if not HandleAllocated then Exit;
GridRectToScreenRect(ARect, InvalidRect, True);
Windows.InvalidateRect(Handle, @InvalidRect, False);
end;
procedure TCustomGrid.InvertRect(const Rect: TRect);
begin
with Rect do
PatBlt(Canvas.Handle, Left, Top, Right - Left, Bottom - Top, DSTINVERT);
end;
procedure TCustomGrid.ModifyScrollBar(ScrollBar, ScrollCode, Pos: Cardinal);
var
NewTopLeft, MaxTopLeft: TGridCoord;
DrawInfo: TGridDrawInfo;
function Min: Longint;
begin
if ScrollBar = SB_HORZ then Result := FixedCols
else Result := FixedRows;
end;
function Max: Longint;
begin
if ScrollBar = SB_HORZ then Result := MaxTopLeft.X
else Result := MaxTopLeft.Y;
end;
function PageUp: Longint;
var
MaxTopLeft: TGridCoord;
begin
MaxTopLeft := CalcMaxTopLeft(FTopLeft, DrawInfo);
if ScrollBar = SB_HORZ then
Result := FTopLeft.X - MaxTopLeft.X else
Result := FTopLeft.Y - MaxTopLeft.Y;
if Result < 1 then Result := 1;
end;
function PageDown: Longint;
var
DrawInfo: TGridDrawInfo;
begin
CalcDrawInfo(DrawInfo);
with DrawInfo do
if ScrollBar = SB_HORZ then
Result := Horz.LastFullVisibleCell - FTopLeft.X else
Result := Vert.LastFullVisibleCell - FTopLeft.Y;
if Result < 1 then Result := 1;
end;
function CalcScrollBar(Value: Longint): Longint;
begin
Result := Value;
case ScrollCode of
SB_LINEUP:
Result := Value - 1;
SB_LINEDOWN:
Result := Value + 1;
SB_PAGEUP:
Result := Value - PageUp;
SB_PAGEDOWN:
Result := Value + PageDown;
SB_THUMBPOSITION, SB_THUMBTRACK:
if (goThumbTracking in Options) or (ScrollCode = SB_THUMBPOSITION) then
Result := Min + LongMulDiv(Pos, Max - Min, MaxShortInt);
SB_BOTTOM:
Result := Min;
SB_TOP:
Result := Min;
end;
end;
procedure ModifyPixelScrollBar(Code, Pos: Cardinal);
var
NewOffset: Integer;
OldOffset: Integer;
R: TGridRect;
begin
NewOffset := FColOffset;
case Code of
SB_LINEUP: Dec(NewOffset, Canvas.TextWidth('0'));
SB_LINEDOWN: Inc(NewOffset, Canvas.TextWidth('0'));
SB_PAGEUP: Dec(NewOffset, ClientWidth);
SB_PAGEDOWN: Inc(NewOffset, ClientWidth);
SB_THUMBPOSITION: NewOffset := Pos;
SB_THUMBTRACK: if goThumbTracking in Options then NewOffset := Pos;
SB_BOTTOM: NewOffset := 0;
SB_TOP: NewOffset := ColWidths[0] - ClientWidth;
end;
if NewOffset < 0 then
NewOffset := 0
else if NewOffset >= ColWidths[0] - ClientWidth then
NewOffset := ColWidths[0] - ClientWidth;
if NewOffset <> FColOffset then
begin
OldOffset := FColOffset;
FColOffset := NewOffset;
ScrollData(OldOffset - NewOffset, 0);
FillChar(R, SizeOf(R), 0);
R.Bottom := FixedRows;
InvalidateRect(R);
Update;
UpdateScrollPos;
end;
end;
begin
if Visible and CanFocus and TabStop and not (csDesigning in ComponentState) then
SetFocus;
if (ScrollBar = SB_HORZ) and (ColCount = 1) then
begin
ModifyPixelScrollBar(ScrollCode, Pos);
Exit;
end;
CalcDrawInfo(DrawInfo);
MaxTopLeft.X := ColCount - 1;
MaxTopLeft.Y := RowCount - 1;
MaxTopLeft := CalcMaxTopLeft(MaxTopLeft, DrawInfo);
NewTopLeft := FTopLeft;
if ScrollBar = SB_HORZ then NewTopLeft.X := CalcScrollBar(NewTopLeft.X)
else NewTopLeft.Y := CalcScrollBar(NewTopLeft.Y);
if NewTopLeft.X < FixedCols then NewTopLeft.X := FixedCols
else if NewTopLeft.X > MaxTopLeft.X then NewTopLeft.X := MaxTopLeft.X;
if NewTopLeft.Y < FixedRows then NewTopLeft.Y := FixedRows
else if NewTopLeft.Y > MaxTopLeft.Y then NewTopLeft.Y := MaxTopLeft.Y;
if (NewTopLeft.X <> FTopLeft.X) or (NewTopLeft.Y <> FTopLeft.Y) then
MoveTopLeft(NewTopLeft.X, NewTopLeft.Y);
end;
procedure TCustomGrid.MoveAdjust(var CellPos: Longint; FromIndex, ToIndex: Longint);
var
Min, Max: Longint;
begin
if CellPos = FromIndex then CellPos := ToIndex
else
begin
Min := FromIndex;
Max := ToIndex;
if FromIndex > ToIndex then
begin
Min := ToIndex;
Max := FromIndex;
end;
if (CellPos >= Min) and (CellPos <= Max) then
if FromIndex > ToIndex then
Inc(CellPos) else
Dec(CellPos);
end;
end;
procedure TCustomGrid.MoveAnchor(const NewAnchor: TGridCoord);
var
OldSel: TGridRect;
begin
if [goRangeSelect, goEditing] * Options = [goRangeSelect] then
begin
OldSel := Selection;
FAnchor := NewAnchor;
if goRowSelect in Options then FAnchor.X := ColCount - 1;
ClampInView(NewAnchor);
SelectionMoved(OldSel);
end
else MoveCurrent(NewAnchor.X, NewAnchor.Y, True, True);
end;
procedure TCustomGrid.MoveCurrent(ACol, ARow: Longint; MoveAnchor,
Show: Boolean);
var
OldSel: TGridRect;
OldCurrent: TGridCoord;
begin
if (ACol < 0) or (ARow < 0) or (ACol >= ColCount) or (ARow >= RowCount) then
InvalidOp(SIndexOutOfRange);
if SelectCell(ACol, ARow) then
begin
OldSel := Selection;
OldCurrent := FCurrent;
FCurrent.X := ACol;
FCurrent.Y := ARow;
if not (goAlwaysShowEditor in Options) then HideEditor;
if MoveAnchor or not (goRangeSelect in Options) then
begin
FAnchor := FCurrent;
if goRowSelect in Options then FAnchor.X := ColCount - 1;
end;
if goRowSelect in Options then FCurrent.X := FixedCols;
if Show then ClampInView(FCurrent);
SelectionMoved(OldSel);
with OldCurrent do InvalidateCell(X, Y);
with FCurrent do InvalidateCell(ACol, ARow);
end;
end;
procedure TCustomGrid.MoveTopLeft(ALeft, ATop: Longint);
var
OldTopLeft: TGridCoord;
begin
if (ALeft = FTopLeft.X) and (ATop = FTopLeft.Y) then Exit;
Update;
OldTopLeft := FTopLeft;
FTopLeft.X := ALeft;
FTopLeft.Y := ATop;
TopLeftMoved(OldTopLeft);
end;
procedure TCustomGrid.ResizeCol(Index: Longint; OldSize, NewSize: Integer);
begin
InvalidateGrid;
end;
procedure TCustomGrid.ResizeRow(Index: Longint; OldSize, NewSize: Integer);
begin
InvalidateGrid;
end;
procedure TCustomGrid.SelectionMoved(const OldSel: TGridRect);
var
OldRect, NewRect: TRect;
AXorRects: TXorRects;
I: Integer;
begin
if not HandleAllocated then Exit;
GridRectToScreenRect(OldSel, OldRect, True);
GridRectToScreenRect(Selection, NewRect, True);
XorRects(OldRect, NewRect, AXorRects);
for I := Low(AXorRects) to High(AXorRects) do
Windows.InvalidateRect(Handle, @AXorRects[I], False);
end;
procedure TCustomGrid.ScrollDataInfo(DX, DY: Integer;
var DrawInfo: TGridDrawInfo);
var
ScrollArea: TRect;
ScrollFlags: Integer;
begin
with DrawInfo do
begin
ScrollFlags := SW_INVALIDATE;
if not DefaultDrawing then
ScrollFlags := ScrollFlags or SW_ERASE;
{ Scroll the area }
if DY = 0 then
begin
{ Scroll both the column titles and data area at the same time }
ScrollArea := Rect(Horz.FixedBoundary, 0, Horz.GridExtent, Vert.GridExtent);
ScrollWindowEx(Handle, DX, 0, @ScrollArea, @ScrollArea, 0, nil, ScrollFlags);
end
else if DX = 0 then
begin
{ Scroll both the row titles and data area at the same time }
ScrollArea := Rect(0, Vert.FixedBoundary, Horz.GridExtent, Vert.GridExtent);
ScrollWindowEx(Handle, 0, DY, @ScrollArea, @ScrollArea, 0, nil, ScrollFlags);
end
else
begin
{ Scroll titles and data area separately }
{ Column titles }
ScrollArea := Rect(Horz.FixedBoundary, 0, Horz.GridExtent, Vert.FixedBoundary);
ScrollWindowEx(Handle, DX, 0, @ScrollArea, @ScrollArea, 0, nil, ScrollFlags);
{ Row titles }
ScrollArea := Rect(0, Vert.FixedBoundary, Horz.FixedBoundary, Vert.GridExtent);
ScrollWindowEx(Handle, 0, DY, @ScrollArea, @ScrollArea, 0, nil, ScrollFlags);
{ Data area }
ScrollArea := Rect(Horz.FixedBoundary, Vert.FixedBoundary, Horz.GridExtent,
Vert.GridExtent);
ScrollWindowEx(Handle, DX, DY, @ScrollArea, @ScrollArea, 0, nil, ScrollFlags);
end;
end;
end;
procedure TCustomGrid.ScrollData(DX, DY: Integer);
var
DrawInfo: TGridDrawInfo;
begin
CalcDrawInfo(DrawInfo);
ScrollDataInfo(DX, DY, DrawInfo);
end;
procedure TCustomGrid.TopLeftMoved(const OldTopLeft: TGridCoord);
function CalcScroll(const AxisInfo: TGridAxisDrawInfo;
OldPos, CurrentPos: Integer; var Amount: Longint): Boolean;
var
Start, Stop: Longint;
I: Longint;
begin
Result := False;
with AxisInfo do
begin
if OldPos < CurrentPos then
begin
Start := OldPos;
Stop := CurrentPos;
end
else
begin
Start := CurrentPos;
Stop := OldPos;
end;
Amount := 0;
for I := Start to Stop - 1 do
begin
Inc(Amount, GetExtent(I) + EffectiveLineWidth);
if Amount > (GridBoundary - FixedBoundary) then
begin
{ Scroll amount too big, redraw the whole thing }
InvalidateGrid;
Exit;
end;
end;
if OldPos < CurrentPos then Amount := -Amount;
end;
Result := True;
end;
var
DrawInfo: TGridDrawInfo;
Delta: TGridCoord;
begin
UpdateScrollPos;
CalcDrawInfo(DrawInfo);
if CalcScroll(DrawInfo.Horz, OldTopLeft.X, FTopLeft.X, Delta.X) and
CalcScroll(DrawInfo.Vert, OldTopLeft.Y, FTopLeft.Y, Delta.Y) then
ScrollDataInfo(Delta.X, Delta.Y, DrawInfo);
TopLeftChanged;
end;
procedure TCustomGrid.UpdateScrollPos;
var
DrawInfo: TGridDrawInfo;
MaxTopLeft: TGridCoord;
procedure SetScroll(Code: Word; Value: Integer);
begin
if GetScrollPos(Handle, Code) <> Value then
SetScrollPos(Handle, Code, Value, True);
end;
begin
if (not HandleAllocated) or (ScrollBars = ssNone) then Exit;
CalcDrawInfo(DrawInfo);
MaxTopLeft.X := ColCount - 1;
MaxTopLeft.Y := RowCount - 1;
MaxTopLeft := CalcMaxTopLeft(MaxTopLeft, DrawInfo);
if ScrollBars in [ssHorizontal, ssBoth] then
if ColCount = 1 then
begin
if (FColOffset > 0) and (ClientWidth > ColWidths[0] - FColOffset) then
ModifyScrollbar(SB_HORZ, SB_THUMBPOSITION, ColWidths[0] - ClientWidth)
else
SetScroll(SB_HORZ, FColOffset)
end
else
SetScroll(SB_HORZ, LongMulDiv(FTopLeft.X - FixedCols, MaxShortInt,
MaxTopLeft.X - FixedCols));
if ScrollBars in [ssVertical, ssBoth] then
SetScroll(SB_VERT, LongMulDiv(FTopLeft.Y - FixedRows, MaxShortInt,
MaxTopLeft.Y - FixedRows));
end;
procedure TCustomGrid.UpdateScrollRange;
var
MaxTopLeft, OldTopLeft: TGridCoord;
DrawInfo: TGridDrawInfo;
OldScrollBars: TScrollStyle;
Updated: Boolean;
procedure DoUpdate;
begin
if not Updated then
begin
Update;
Updated := True;
end;
end;
function ScrollBarVisible(Code: Word): Boolean;
var
Min, Max: Integer;
begin
Result := False;
if (ScrollBars = ssBoth) or
((Code = SB_HORZ) and (ScrollBars = ssHorizontal)) or
((Code = SB_VERT) and (ScrollBars = ssVertical)) then
begin
GetScrollRange(Handle, Code, Min, Max);
Result := Min <> Max;
end;
end;
procedure CalcSizeInfo;
begin
CalcDrawInfoXY(DrawInfo, DrawInfo.Horz.GridExtent, DrawInfo.Vert.GridExtent);
MaxTopLeft.X := ColCount - 1;
MaxTopLeft.Y := RowCount - 1;
MaxTopLeft := CalcMaxTopLeft(MaxTopLeft, DrawInfo);
end;
procedure SetAxisRange(var Max, Old, Current: Longint; Code: Word;
Fixeds: Integer);
begin
CalcSizeInfo;
if Fixeds < Max then
SetScrollRange(Handle, Code, 0, MaxShortInt, True)
else
SetScrollRange(Handle, Code, 0, 0, True);
if Old > Max then
begin
DoUpdate;
Current := Max;
end;
end;
procedure SetHorzRange;
var
Range: Integer;
begin
if OldScrollBars in [ssHorizontal, ssBoth] then
if ColCount = 1 then
begin
Range := ColWidths[0] - ClientWidth;
if Range < 0 then Range := 0;
SetScrollRange(Handle, SB_HORZ, 0, Range, True);
end
else
SetAxisRange(MaxTopLeft.X, OldTopLeft.X, FTopLeft.X, SB_HORZ, FixedCols);
end;
procedure SetVertRange;
begin
if OldScrollBars in [ssVertical, ssBoth] then
SetAxisRange(MaxTopLeft.Y, OldTopLeft.Y, FTopLeft.Y, SB_VERT, FixedRows);
end;
begin
if (ScrollBars = ssNone) or not HandleAllocated then Exit;
with DrawInfo do
begin
Horz.GridExtent := ClientWidth;
Vert.GridExtent := ClientHeight;
{ Ignore scroll bars for initial calculation }
if ScrollBarVisible(SB_HORZ) then
Inc(Vert.GridExtent, GetSystemMetrics(SM_CYHSCROLL));
if ScrollBarVisible(SB_VERT) then
Inc(Horz.GridExtent, GetSystemMetrics(SM_CXVSCROLL));
end;
OldTopLeft := FTopLeft;
{ Temporarily mark us as not having scroll bars to avoid recursion }
OldScrollBars := FScrollBars;
FScrollBars := ssNone;
Updated := False;
try
{ Update scrollbars }
SetHorzRange;
DrawInfo.Vert.GridExtent := ClientHeight;
SetVertRange;
if DrawInfo.Horz.GridExtent <> ClientWidth then
begin
DrawInfo.Horz.GridExtent := ClientWidth;
SetHorzRange;
end;
finally
FScrollBars := OldScrollBars;
end;
UpdateScrollPos;
if (FTopLeft.X <> OldTopLeft.X) or (FTopLeft.Y <> OldTopLeft.Y) then
TopLeftMoved(OldTopLeft);
end;
function TCustomGrid.CreateEditor: TInplaceEdit;
begin
Result := TInplaceEdit.Create(Self);
end;
procedure TCustomGrid.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with Params do
begin
Style := Style or WS_TABSTOP;
if FScrollBars in [ssVertical, ssBoth] then Style := Style or WS_VSCROLL;
if FScrollBars in [ssHorizontal, ssBoth] then Style := Style or WS_HSCROLL;
WindowClass.style := CS_DBLCLKS;
if FBorderStyle = bsSingle then
if NewStyleControls and Ctl3D then
begin
Style := Style and not WS_BORDER;
ExStyle := ExStyle or WS_EX_CLIENTEDGE;
end
else
Style := Style or WS_BORDER;
end;
end;
procedure TCustomGrid.KeyDown(var Key: Word; Shift: TShiftState);
var
NewTopLeft, NewCurrent, MaxTopLeft: TGridCoord;
DrawInfo: TGridDrawInfo;
PageWidth, PageHeight: Integer;
procedure CalcPageExtents;
begin
CalcDrawInfo(DrawInfo);
PageWidth := DrawInfo.Horz.LastFullVisibleCell - LeftCol;
if PageWidth < 1 then PageWidth := 1;
PageHeight := DrawInfo.Vert.LastFullVisibleCell - TopRow;
if PageHeight < 1 then PageHeight := 1;
end;
procedure Restrict(var Coord: TGridCoord; MinX, MinY, MaxX, MaxY: Longint);
begin
with Coord do
begin
if X > MaxX then X := MaxX
else if X < MinX then X := MinX;
if Y > MaxY then Y := MaxY
else if Y < MinY then Y := MinY;
end;
end;
begin
inherited KeyDown(Key, Shift);
if not CanGridAcceptKey(Key, Shift) then Key := 0;
NewCurrent := FCurrent;
NewTopLeft := FTopLeft;
CalcPageExtents;
if ssCtrl in Shift then
case Key of
VK_UP: Dec(NewTopLeft.Y);
VK_DOWN: Inc(NewTopLeft.Y);
VK_LEFT:
if not (goRowSelect in Options) then
begin
Dec(NewCurrent.X, PageWidth);
Dec(NewTopLeft.X, PageWidth);
end;
VK_RIGHT:
if not (goRowSelect in Options) then
begin
Inc(NewCurrent.X, PageWidth);
Inc(NewTopLeft.X, PageWidth);
end;
VK_PRIOR: NewCurrent.Y := TopRow;
VK_NEXT: NewCurrent.Y := DrawInfo.Vert.LastFullVisibleCell;
VK_HOME:
begin
NewCurrent.X := FixedCols;
NewCurrent.Y := FixedRows;
end;
VK_END:
begin
NewCurrent.X := ColCount - 1;
NewCurrent.Y := RowCount - 1;
end;
end
else
case Key of
VK_UP: Dec(NewCurrent.Y);
VK_DOWN: Inc(NewCurrent.Y);
VK_LEFT:
if goRowSelect in Options then
Dec(NewCurrent.Y) else
Dec(NewCurrent.X);
VK_RIGHT:
if goRowSelect in Options then
Inc(NewCurrent.Y) else
Inc(NewCurrent.X);
VK_NEXT:
begin
Inc(NewCurrent.Y, PageHeight);
Inc(NewTopLeft.Y, PageHeight);
end;
VK_PRIOR:
begin
Dec(NewCurrent.Y, PageHeight);
Dec(NewTopLeft.Y, PageHeight);
end;
VK_HOME:
if goRowSelect in Options then
NewCurrent.Y := FixedRows else
NewCurrent.X := FixedCols;
VK_END:
if goRowSelect in Options then
NewCurrent.Y := RowCount - 1 else
NewCurrent.X := ColCount - 1;
VK_TAB:
if not (ssAlt in Shift) then
repeat
if ssShift in Shift then
begin
Dec(NewCurrent.X);
if NewCurrent.X < FixedCols then
begin
NewCurrent.X := ColCount - 1;
Dec(NewCurrent.Y);
if NewCurrent.Y < FixedRows then NewCurrent.Y := RowCount - 1;
end;
Shift := [];
end
else
begin
Inc(NewCurrent.X);
if NewCurrent.X >= ColCount then
begin
NewCurrent.X := FixedCols;
Inc(NewCurrent.Y);
if NewCurrent.Y >= RowCount then NewCurrent.Y := FixedRows;
end;
end;
until TabStops[NewCurrent.X] or (NewCurrent.X = FCurrent.X);
VK_F2: EditorMode := True;
end;
MaxTopLeft.X := ColCount - 1;
MaxTopLeft.Y := RowCount - 1;
MaxTopLeft := CalcMaxTopLeft(MaxTopLeft, DrawInfo);
Restrict(NewTopLeft, FixedCols, FixedRows, MaxTopLeft.X, MaxTopLeft.Y);
if (NewTopLeft.X <> LeftCol) or (NewTopLeft.Y <> TopRow) then
MoveTopLeft(NewTopLeft.X, NewTopLeft.Y);
Restrict(NewCurrent, FixedCols, FixedRows, ColCount - 1, RowCount - 1);
if (NewCurrent.X <> Col) or (NewCurrent.Y <> Row) then
FocusCell(NewCurrent.X, NewCurrent.Y, not (ssShift in Shift));
end;
procedure TCustomGrid.KeyPress(var Key: Char);
begin
inherited KeyPress(Key);
if not (goAlwaysShowEditor in Options) and (Key = #13) then
begin
if FEditorMode then
HideEditor else
ShowEditor;
Key := #0;
end;
end;
procedure TCustomGrid.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
var
CellHit: TGridCoord;
DrawInfo: TGridDrawInfo;
MoveDrawn: Boolean;
begin
MoveDrawn := False;
HideEdit;
if not (csDesigning in ComponentState) and CanFocus then
begin
SetFocus;
if ValidParentForm(Self).ActiveControl <> Self then
begin
MouseCapture := False;
Exit;
end;
end;
if (Button = mbLeft) and (ssDouble in Shift) then
DblClick
else if Button = mbLeft then
begin
CalcDrawInfo(DrawInfo);
{ Check grid sizing }
CalcSizingState(X, Y, FGridState, FSizingIndex, FSizingPos, FSizingOfs,
DrawInfo);
if FGridState <> gsNormal then
begin
DrawSizingLine(DrawInfo);
Exit;
end;
CellHit := CalcCoordFromPoint(X, Y, DrawInfo);
if (CellHit.X >= FixedCols) and (CellHit.Y >= FixedRows) then
begin
if goEditing in Options then
begin
if (CellHit.X = FCurrent.X) and (CellHit.Y = FCurrent.Y) then
ShowEditor
else
begin
MoveCurrent(CellHit.X, CellHit.Y, True, True);
UpdateEdit;
end;
Click;
end
else
begin
FGridState := gsSelecting;
SetTimer(Handle, 1, 60, nil);
if ssShift in Shift then
MoveAnchor(CellHit)
else
MoveCurrent(CellHit.X, CellHit.Y, True, True);
end;
end
else if (goRowMoving in Options) and (CellHit.X >= 0) and
(CellHit.X < FixedCols) and (CellHit.Y >= FixedRows) then
begin
FGridState := gsRowMoving;
FMoveIndex := CellHit.Y;
FMovePos := FMoveIndex;
Update;
DrawMove;
MoveDrawn := True;
SetTimer(Handle, 1, 60, nil);
end
else if (goColMoving in Options) and (CellHit.Y >= 0) and
(CellHit.Y < FixedRows) and (CellHit.X >= FixedCols) then
begin
FGridState := gsColMoving;
FMoveIndex := CellHit.X;
FMovePos := FMoveIndex;
Update;
DrawMove;
MoveDrawn := True;
SetTimer(Handle, 1, 60, nil);
end;
end;
try
inherited MouseDown(Button, Shift, X, Y);
except
if MoveDrawn then DrawMove;
end;
end;
procedure TCustomGrid.MouseMove(Shift: TShiftState; X, Y: Integer);
var
DrawInfo: TGridDrawInfo;
CellHit: TGridCoord;
begin
CalcDrawInfo(DrawInfo);
case FGridState of
gsSelecting, gsColMoving, gsRowMoving:
begin
CellHit := CalcCoordFromPoint(X, Y, DrawInfo);
if (CellHit.X >= FixedCols) and (CellHit.Y >= FixedRows) and
(CellHit.X <= DrawInfo.Horz.LastFullVisibleCell+1) and
(CellHit.Y <= DrawInfo.Vert.LastFullVisibleCell+1) then
case FGridState of
gsSelecting:
if ((CellHit.X <> FAnchor.X) or (CellHit.Y <> FAnchor.Y)) then
MoveAnchor(CellHit);
gsColMoving:
MoveAndScroll(X, CellHit.X, DrawInfo, DrawInfo.Horz, SB_HORZ);
gsRowMoving:
MoveAndScroll(Y, CellHit.Y, DrawInfo, DrawInfo.Vert, SB_VERT);
end;
end;
gsRowSizing, gsColSizing:
begin
DrawSizingLine(DrawInfo); { XOR it out }
if FGridState = gsRowSizing then
FSizingPos := Y + FSizingOfs else
FSizingPos := X + FSizingOfs;
DrawSizingLine(DrawInfo); { XOR it back in }
end;
end;
inherited MouseMove(Shift, X, Y);
end;
procedure TCustomGrid.MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
var
DrawInfo: TGridDrawInfo;
NewSize: Integer;
function ResizeLine(const AxisInfo: TGridAxisDrawInfo): Integer;
var
I: Integer;
begin
with AxisInfo do
begin
Result := FixedBoundary;
for I := FirstGridCell to FSizingIndex - 1 do
Inc(Result, GetExtent(I) + EffectiveLineWidth);
Result := FSizingPos - Result;
end;
end;
begin
try
case FGridState of
gsSelecting:
begin
MouseMove(Shift, X, Y);
KillTimer(Handle, 1);
UpdateEdit;
Click;
end;
gsRowSizing, gsColSizing:
begin
CalcDrawInfo(DrawInfo);
DrawSizingLine(DrawInfo);
if FGridState = gsColSizing then
begin
NewSize := ResizeLine(DrawInfo.Horz);
if NewSize > 1 then
begin
ColWidths[FSizingIndex] := NewSize;
UpdateDesigner;
end;
end
else
begin
NewSize := ResizeLine(DrawInfo.Vert);
if NewSize > 1 then
begin
RowHeights[FSizingIndex] := NewSize;
UpdateDesigner;
end;
end;
end;
gsColMoving, gsRowMoving:
begin
DrawMove;
KillTimer(Handle, 1);
if FMoveIndex <> FMovePos then
begin
if FGridState = gsColMoving then
MoveColumn(FMoveIndex, FMovePos)
else
MoveRow(FMoveIndex, FMovePos);
UpdateDesigner;
end;
UpdateEdit;
end;
else
UpdateEdit;
end;
inherited MouseUp(Button, Shift, X, Y);
finally
FGridState := gsNormal;
end;
end;
procedure TCustomGrid.MoveAndScroll(Mouse, CellHit: Integer;
var DrawInfo: TGridDrawInfo; var Axis: TGridAxisDrawInfo; ScrollBar: Integer);
begin
if (CellHit <> FMovePos) and
not((FMovePos = Axis.FixedCellCount) and (Mouse < Axis.FixedBoundary)) and
not((FMovePos = Axis.GridCellCount-1) and (Mouse > Axis.GridBoundary)) then
begin
DrawMove;
if (Mouse < Axis.FixedBoundary) then
begin
if (FMovePos > Axis.FixedCellCount) then
begin
ModifyScrollbar(ScrollBar, SB_LINEUP, 0);
Update;
CalcDrawInfo(DrawInfo); // this changes contents of Axis var
end;
CellHit := Axis.FirstGridCell;
end
else if (Mouse >= Axis.FullVisBoundary) then
begin
if (FMovePos = Axis.LastFullVisibleCell) and
(FMovePos < Axis.GridCellCount -1) then
begin
ModifyScrollBar(Scrollbar, SB_LINEDOWN, 0);
Update;
CalcDrawInfo(DrawInfo); // this changes contents of Axis var
end;
CellHit := Axis.LastFullVisibleCell;
end
else if CellHit < 0 then CellHit := FMovePos;
FMovePos := CellHit;
DrawMove;
end;
end;
function TCustomGrid.GetColWidths(Index: Longint): Integer;
begin
if (FColWidths = nil) or (Index >= ColCount) then
Result := DefaultColWidth
else
Result := PIntArray(FColWidths)^[Index + 1];
end;
function TCustomGrid.GetRowHeights(Index: Longint): Integer;
begin
if (FRowHeights = nil) or (Index >= RowCount) then
Result := DefaultRowHeight
else
Result := PIntArray(FRowHeights)^[Index + 1];
end;
function TCustomGrid.GetGridWidth: Integer;
var
DrawInfo: TGridDrawInfo;
begin
CalcDrawInfo(DrawInfo);
Result := DrawInfo.Horz.GridBoundary;
end;
function TCustomGrid.GetGridHeight: Integer;
var
DrawInfo: TGridDrawInfo;
begin
CalcDrawInfo(DrawInfo);
Result := DrawInfo.Vert.GridBoundary;
end;
function TCustomGrid.GetSelection: TGridRect;
begin
Result := GridRect(FCurrent, FAnchor);
end;
function TCustomGrid.GetTabStops(Index: Longint): Boolean;
begin
if FTabStops = nil then Result := True
else Result := Boolean(PIntArray(FTabStops)^[Index + 1]);
end;
function TCustomGrid.GetVisibleColCount: Integer;
var
DrawInfo: TGridDrawInfo;
begin
CalcDrawInfo(DrawInfo);
Result := DrawInfo.Horz.LastFullVisibleCell - LeftCol + 1;
end;
function TCustomGrid.GetVisibleRowCount: Integer;
var
DrawInfo: TGridDrawInfo;
begin
CalcDrawInfo(DrawInfo);
Result := DrawInfo.Vert.LastFullVisibleCell - TopRow + 1;
end;
procedure TCustomGrid.SetBorderStyle(Value: TBorderStyle);
begin
if FBorderStyle <> Value then
begin
FBorderStyle := Value;
RecreateWnd;
end;
end;
procedure TCustomGrid.SetCol(Value: Longint);
begin
if Col <> Value then FocusCell(Value, Row, True);
end;
procedure TCustomGrid.SetColCount(Value: Longint);
begin
if FColCount <> Value then
begin
if Value < 1 then Value := 1;
if Value <= FixedCols then FixedCols := Value - 1;
ChangeSize(Value, RowCount);
if goRowSelect in Options then
begin
FAnchor.X := ColCount - 1;
Invalidate;
end;
end;
end;
procedure TCustomGrid.SetColWidths(Index: Longint; Value: Integer);
begin
if FColWidths = nil then
UpdateExtents(FColWidths, ColCount, DefaultColWidth);
if Index >= ColCount then InvalidOp(SIndexOutOfRange);
if Value <> PIntArray(FColWidths)^[Index + 1] then
begin
ResizeCol(Index, PIntArray(FColWidths)^[Index + 1], Value);
PIntArray(FColWidths)^[Index + 1] := Value;
ColWidthsChanged;
end;
end;
procedure TCustomGrid.SetDefaultColWidth(Value: Integer);
begin
if FColWidths <> nil then UpdateExtents(FColWidths, 0, 0);
FDefaultColWidth := Value;
ColWidthsChanged;
InvalidateGrid;
end;
procedure TCustomGrid.SetDefaultRowHeight(Value: Integer);
begin
if FRowHeights <> nil then UpdateExtents(FRowHeights, 0, 0);
FDefaultRowHeight := Value;
RowHeightsChanged;
InvalidateGrid;
end;
procedure TCustomGrid.SetFixedColor(Value: TColor);
begin
if FFixedColor <> Value then
begin
FFixedColor := Value;
InvalidateGrid;
end;
end;
procedure TCustomGrid.SetFixedCols(Value: Integer);
begin
if FFixedCols <> Value then
begin
if Value < 0 then InvalidOp(SIndexOutOfRange);
if Value >= ColCount then InvalidOp(SFixedColTooBig);
FFixedCols := Value;
Initialize;
InvalidateGrid;
end;
end;
procedure TCustomGrid.SetFixedRows(Value: Integer);
begin
if FFixedRows <> Value then
begin
if Value < 0 then InvalidOp(SIndexOutOfRange);
if Value >= RowCount then InvalidOp(SFixedRowTooBig);
FFixedRows := Value;
Initialize;
InvalidateGrid;
end;
end;
procedure TCustomGrid.SetEditorMode(Value: Boolean);
begin
if not Value then
HideEditor
else
begin
ShowEditor;
if FInplaceEdit <> nil then FInplaceEdit.Deselect;
end;
end;
procedure TCustomGrid.SetGridLineWidth(Value: Integer);
begin
if FGridLineWidth <> Value then
begin
FGridLineWidth := Value;
InvalidateGrid;
end;
end;
procedure TCustomGrid.SetLeftCol(Value: Longint);
begin
if FTopLeft.X <> Value then MoveTopLeft(Value, TopRow);
end;
procedure TCustomGrid.SetOptions(Value: TGridOptions);
begin
if FOptions <> Value then
begin
if goRowSelect in Value then
Exclude(Value, goAlwaysShowEditor);
FOptions := Value;
if not FEditorMode then
if goAlwaysShowEditor in Value then
ShowEditor else
HideEditor;
if goRowSelect in Value then MoveCurrent(Col, Row, True, False);
InvalidateGrid;
end;
end;
procedure TCustomGrid.SetRow(Value: Longint);
begin
if Row <> Value then FocusCell(Col, Value, True);
end;
procedure TCustomGrid.SetRowCount(Value: Longint);
begin
if FRowCount <> Value then
begin
if Value < 1 then Value := 1;
if Value <= FixedRows then FixedRows := Value - 1;
ChangeSize(ColCount, Value);
end;
end;
procedure TCustomGrid.SetRowHeights(Index: Longint; Value: Integer);
begin
if FRowHeights = nil then
UpdateExtents(FRowHeights, RowCount, DefaultRowHeight);
if Index >= RowCount then InvalidOp(SIndexOutOfRange);
if Value <> PIntArray(FRowHeights)^[Index + 1] then
begin
ResizeRow(Index, PIntArray(FRowHeights)^[Index + 1], Value);
PIntArray(FRowHeights)^[Index + 1] := Value;
RowHeightsChanged;
end;
end;
procedure TCustomGrid.SetScrollBars(Value: TScrollStyle);
begin
if FScrollBars <> Value then
begin
FScrollBars := Value;
RecreateWnd;
end;
end;
procedure TCustomGrid.SetSelection(Value: TGridRect);
var
OldSel: TGridRect;
begin
OldSel := Selection;
FAnchor := Value.TopLeft;
FCurrent := Value.BottomRight;
SelectionMoved(OldSel);
end;
procedure TCustomGrid.SetTabStops(Index: Longint; Value: Boolean);
begin
if FTabStops = nil then
UpdateExtents(FTabStops, ColCount, Integer(True));
if Index >= ColCount then InvalidOp(SIndexOutOfRange);
PIntArray(FTabStops)^[Index + 1] := Integer(Value);
end;
procedure TCustomGrid.SetTopRow(Value: Longint);
begin
if FTopLeft.Y <> Value then MoveTopLeft(LeftCol, Value);
end;
procedure TCustomGrid.HideEdit;
begin
if FInplaceEdit <> nil then
try
UpdateText;
finally
FInplaceCol := -1;
FInplaceRow := -1;
FInplaceEdit.Hide;
end;
end;
procedure TCustomGrid.UpdateEdit;
procedure UpdateEditor;
begin
FInplaceCol := Col;
FInplaceRow := Row;
FInplaceEdit.UpdateContents;
if FInplaceEdit.MaxLength = -1 then FCanEditModify := False
else FCanEditModify := True;
FInplaceEdit.SelectAll;
end;
begin
if CanEditShow then
begin
if FInplaceEdit = nil then
begin
FInplaceEdit := CreateEditor;
FInplaceEdit.SetGrid(Self);
FInplaceEdit.Parent := Self;
UpdateEditor;
end
else
begin
if (Col <> FInplaceCol) or (Row <> FInplaceRow) then
begin
HideEdit;
UpdateEditor;
end;
end;
if CanEditShow then FInplaceEdit.Move(CellRect(Col, Row));
end;
end;
procedure TCustomGrid.UpdateText;
begin
if (FInplaceCol <> -1) and (FInplaceRow <> -1) then
SetEditText(FInplaceCol, FInplaceRow, FInplaceEdit.Text);
end;
procedure TCustomGrid.WMChar(var Msg: TWMChar);
begin
if (goEditing in Options) and (Char(Msg.CharCode) in [^H, #32..#255]) then
ShowEditorChar(Char(Msg.CharCode))
else
inherited;
end;
procedure TCustomGrid.WMCommand(var Message: TWMCommand);
begin
with Message do
begin
if (FInplaceEdit <> nil) and (Ctl = FInplaceEdit.Handle) then
case NotifyCode of
EN_CHANGE: UpdateText;
end;
end;
end;
procedure TCustomGrid.WMGetDlgCode(var Msg: TWMGetDlgCode);
begin
Msg.Result := DLGC_WANTARROWS;
if goRowSelect in Options then Exit;
if goTabs in Options then Msg.Result := Msg.Result or DLGC_WANTTAB;
if goEditing in Options then Msg.Result := Msg.Result or DLGC_WANTCHARS;
end;
procedure TCustomGrid.WMKillFocus(var Msg: TWMKillFocus);
begin
inherited;
InvalidateRect(Selection);
if (FInplaceEdit <> nil) and (Msg.FocusedWnd <> FInplaceEdit.Handle) then
HideEdit;
end;
procedure TCustomGrid.WMLButtonDown(var Message: TMessage);
begin
inherited;
if FInplaceEdit <> nil then FInplaceEdit.FClickTime := GetMessageTime;
end;
procedure TCustomGrid.WMNCHitTest(var Msg: TWMNCHitTest);
begin
DefaultHandler(Msg);
FHitTest := SmallPointToPoint(Msg.Pos);
end;
procedure TCustomGrid.WMSetCursor(var Msg: TWMSetCursor);
var
FixedInfo: TGridDrawInfo;
State: TGridState;
Index: Longint;
Pos, Ofs: Integer;
Cur: HCURSOR;
begin
Cur := 0;
with Msg do
begin
if HitTest = HTCLIENT then
begin
if FGridState = gsNormal then
begin
FHitTest := ScreenToClient(FHitTest);
CalcFixedInfo(FixedInfo);
CalcSizingState(FHitTest.X, FHitTest.Y, State, Index, Pos, Ofs,
FixedInfo);
end else State := FGridState;
if State = gsRowSizing then
Cur := Screen.Cursors[crVSplit]
else if State = gsColSizing then
Cur := Screen.Cursors[crHSplit]
end;
end;
if Cur <> 0 then SetCursor(Cur)
else inherited;
end;
procedure TCustomGrid.WMSetFocus(var Msg: TWMSetFocus);
begin
inherited;
if (FInplaceEdit = nil) or (Msg.FocusedWnd <> FInplaceEdit.Handle) then
begin
InvalidateRect(Selection);
UpdateEdit;
end;
end;
procedure TCustomGrid.WMSize(var Msg: TWMSize);
begin
inherited;
UpdateScrollRange;
end;
procedure TCustomGrid.WMVScroll(var Msg: TWMVScroll);
begin
ModifyScrollBar(SB_VERT, Msg.ScrollCode, Msg.Pos);
end;
procedure TCustomGrid.WMHScroll(var Msg: TWMHScroll);
begin
ModifyScrollBar(SB_HORZ, Msg.ScrollCode, Msg.Pos);
end;
procedure TCustomGrid.CMCancelMode(var Msg: TMessage);
begin
if Assigned(FInplaceEdit) then FInplaceEdit.WndProc(Msg);
inherited;
end;
procedure TCustomGrid.CMFontChanged(var Message: TMessage);
begin
if FInplaceEdit <> nil then FInplaceEdit.Font := Font;
inherited;
end;
procedure TCustomGrid.CMCtl3DChanged(var Message: TMessage);
begin
inherited;
RecreateWnd;
end;
procedure TCustomGrid.CMDesignHitTest(var Msg: TCMDesignHitTest);
begin
Msg.Result := Longint(BOOL(Sizing(Msg.Pos.X, Msg.Pos.Y)));
end;
procedure TCustomGrid.CMWantSpecialKey(var Msg: TCMWantSpecialKey);
begin
inherited;
if (goEditing in Options) and (Char(Msg.CharCode) = #13) then Msg.Result := 1;
end;
procedure TCustomGrid.TimedScroll(Direction: TGridScrollDirection);
var
MaxAnchor, NewAnchor: TGridCoord;
begin
NewAnchor := FAnchor;
MaxAnchor.X := ColCount - 1;
MaxAnchor.Y := RowCount - 1;
if (sdLeft in Direction) and (FAnchor.X > FixedCols) then Dec(NewAnchor.X);
if (sdRight in Direction) and (FAnchor.X < MaxAnchor.X) then Inc(NewAnchor.X);
if (sdUp in Direction) and (FAnchor.Y > FixedRows) then Dec(NewAnchor.Y);
if (sdDown in Direction) and (FAnchor.Y < MaxAnchor.Y) then Inc(NewAnchor.Y);
if (FAnchor.X <> NewAnchor.X) or (FAnchor.Y <> NewAnchor.Y) then
MoveAnchor(NewAnchor);
end;
procedure TCustomGrid.WMTimer(var Msg: TWMTimer);
var
Point: TPoint;
DrawInfo: TGridDrawInfo;
ScrollDirection: TGridScrollDirection;
CellHit: TGridCoord;
begin
if not (FGridState in [gsSelecting, gsRowMoving, gsColMoving]) then Exit;
GetCursorPos(Point);
Point := ScreenToClient(Point);
CalcDrawInfo(DrawInfo);
ScrollDirection := [];
with DrawInfo do
begin
CellHit := CalcCoordFromPoint(Point.X, Point.Y, DrawInfo);
case FGridState of
gsColMoving:
MoveAndScroll(Point.X, CellHit.X, DrawInfo, Horz, SB_HORZ);
gsRowMoving:
MoveAndScroll(Point.Y, CellHit.Y, DrawInfo, Vert, SB_VERT);
gsSelecting:
begin
if Point.X < Horz.FixedBoundary then Include(ScrollDirection, sdLeft)
else if Point.X > Horz.FullVisBoundary then Include(ScrollDirection, sdRight);
if Point.Y < Vert.FixedBoundary then Include(ScrollDirection, sdUp)
else if Point.Y > Vert.FullVisBoundary then Include(ScrollDirection, sdDown);
if ScrollDirection <> [] then TimedScroll(ScrollDirection);
end;
end;
end;
end;
procedure TCustomGrid.ColWidthsChanged;
begin
UpdateScrollRange;
UpdateEdit;
end;
procedure TCustomGrid.RowHeightsChanged;
begin
UpdateScrollRange;
UpdateEdit;
end;
procedure TCustomGrid.DeleteColumn(ACol: Longint);
begin
MoveColumn(ACol, ColCount-1);
ColCount := ColCount - 1;
end;
procedure TCustomGrid.DeleteRow(ARow: Longint);
begin
MoveRow(ARow, RowCount - 1);
RowCount := RowCount - 1;
end;
procedure TCustomGrid.UpdateDesigner;
var
ParentForm: TForm;
begin
if (csDesigning in ComponentState) and HandleAllocated and
not (csUpdating in ComponentState) then
begin
ParentForm := GetParentForm(Self);
if Assigned(ParentForm) and Assigned(ParentForm.Designer) then
ParentForm.Designer.Modified;
end;
end;
{ TDrawGrid }
function TDrawGrid.CellRect(ACol, ARow: Longint): TRect;
begin
Result := inherited CellRect(ACol, ARow);
end;
procedure TDrawGrid.MouseToCell(X, Y: Integer; var ACol, ARow: Longint);
var
Coord: TGridCoord;
begin
Coord := MouseCoord(X, Y);
ACol := Coord.X;
ARow := Coord.Y;
end;
procedure TDrawGrid.ColumnMoved(FromIndex, ToIndex: Longint);
begin
if Assigned(FOnColumnMoved) then FOnColumnMoved(Self, FromIndex, ToIndex);
end;
function TDrawGrid.GetEditMask(ACol, ARow: Longint): string;
begin
Result := '';
if Assigned(FOnGetEditMask) then FOnGetEditMask(Self, ACol, ARow, Result);
end;
function TDrawGrid.GetEditText(ACol, ARow: Longint): string;
begin
Result := '';
if Assigned(FOnGetEditText) then FOnGetEditText(Self, ACol, ARow, Result);
end;
procedure TDrawGrid.RowMoved(FromIndex, ToIndex: Longint);
begin
if Assigned(FOnRowMoved) then FOnRowMoved(Self, FromIndex, ToIndex);
end;
function TDrawGrid.SelectCell(ACol, ARow: Longint): Boolean;
begin
Result := True;
if Assigned(FOnSelectCell) then FOnSelectCell(Self, ACol, ARow, Result);
end;
procedure TDrawGrid.SetEditText(ACol, ARow: Longint; const Value: string);
begin
if Assigned(FOnSetEditText) then FOnSetEditText(Self, ACol, ARow, Value);
end;
procedure TDrawGrid.DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState);
begin
if Assigned(FOnDrawCell) then FOnDrawCell(Self, ACol, ARow, ARect, AState);
end;
procedure TDrawGrid.TopLeftChanged;
begin
inherited TopLeftChanged;
if Assigned(FOnTopLeftChanged) then FOnTopLeftChanged(Self);
end;
{ StrItem management for TStringSparseList }
type
PStrItem = ^TStrItem;
TStrItem = record
FObject: TObject;
FString: string;
end;
function NewStrItem(const AString: string; AObject: TObject): PStrItem;
begin
New(Result);
Result^.FObject := AObject;
Result^.FString := AString;
end;
procedure DisposeStrItem(P: PStrItem);
begin
Dispose(P);
end;
{ Sparse array classes for TStringGrid }
type
PPointer = ^Pointer;
{ Exception classes }
EStringSparseListError = class(Exception);
{ TSparsePointerArray class}
{ Used by TSparseList. Based on Sparse1Array, but has Pointer elements
and Integer index, just like TPointerList/TList, and less indirection }
{ Apply function for the applicator:
TheIndex Index of item in array
TheItem Value of item (i.e pointer element) in section
Returns: 0 if success, else error code. }
TSPAApply = function(TheIndex: Integer; TheItem: Pointer): Integer;
TSecDir = array[0..4095] of Pointer; { Enough for up to 12 bits of sec }
PSecDir = ^TSecDir;
TSPAQuantum = (SPASmall, SPALarge); { Section size }
TSparsePointerArray = class(TObject)
private
secDir: PSecDir;
slotsInDir: Word;
indexMask, secShift: Word;
FHighBound: Integer;
FSectionSize: Word;
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
constructor Create(Quantum: TSPAQuantum);
destructor Destroy; override;
{ Traverse SPA, calling apply function for each defined non-nil
item. The traversal terminates if the apply function returns
a value other than 0. }
{ NOTE: must be static method so that we can take its address in
TSparseList.ForAll }
function ForAll(ApplyFunction: Pointer {TSPAApply}): Integer;
{ Ratchet down HighBound after a deletion }
procedure ResetHighBound;
property HighBound: Integer read FHighBound;
property SectionSize: Word read FSectionSize;
property Items[Index: Integer]: Pointer read GetAt write PutAt; default;
end;
{ TSparseList class }
TSparseList = class(TObject)
private
FList: TSparsePointerArray;
FCount: Integer; { 1 + HighBound, adjusted for Insert/Delete }
FQuantum: TSPAQuantum;
procedure NewList(Quantum: TSPAQuantum);
protected
procedure Error; virtual;
function Get(Index: Integer): Pointer;
procedure Put(Index: Integer; Item: Pointer);
public
constructor Create(Quantum: TSPAQuantum);
destructor Destroy; override;
function Add(Item: Pointer): Integer;
procedure Clear;
procedure Delete(Index: Integer);
procedure Exchange(Index1, Index2: Integer);
function First: Pointer;
function ForAll(ApplyFunction: Pointer {TSPAApply}): Integer;
function IndexOf(Item: Pointer): Integer;
procedure Insert(Index: Integer; Item: Pointer);
function Last: Pointer;
procedure Move(CurIndex, NewIndex: Integer);
procedure Pack;
function Remove(Item: Pointer): Integer;
property Count: Integer read FCount;
property Items[Index: Integer]: Pointer read Get write Put; default;
end;
{ TStringSparseList class }
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; virtual;
procedure Error; virtual;
public
constructor Create(Quantum: TSPAQuantum);
destructor Destroy; override;
procedure ReadData(Reader: TReader);
procedure WriteData(Writer: TWriter);
procedure DefineProperties(Filer: TFiler); override;
procedure Delete(Index: Integer); override;
procedure Exchange(Index1, Index2: Integer); override;
procedure Insert(Index: Integer; const S: String); override;
procedure Clear; override;
property List: TSparseList read FList;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
{ TSparsePointerArray }
const
SPAIndexMask: array[TSPAQuantum] of Byte = (15, 255);
SPASecShift: array[TSPAQuantum] of Byte = (4, 8);
{ Expand Section Directory to cover at least `newSlots' slots. Returns: Possibly
updated pointer to the Section Directory. }
function ExpandDir(secDir: PSecDir; var slotsInDir: Word;
newSlots: Word): PSecDir;
begin
Result := secDir;
ReallocMem(Result, newSlots * SizeOf(Pointer));
FillChar(Result^[slotsInDir], (newSlots - slotsInDir) * SizeOf(Pointer), 0);
slotsInDir := newSlots;
end;
{ Allocate a section and set all its items to nil. Returns: Pointer to start of
section. }
function MakeSec(SecIndex: Integer; SectionSize: Word): Pointer;
var
SecP: Pointer;
Size: Word;
begin
Size := SectionSize * SizeOf(Pointer);
GetMem(secP, size);
FillChar(secP^, size, 0);
MakeSec := SecP
end;
constructor TSparsePointerArray.Create(Quantum: TSPAQuantum);
begin
SecDir := nil;
SlotsInDir := 0;
FHighBound := -1;
FSectionSize := Word(SPAIndexMask[Quantum]) + 1;
IndexMask := Word(SPAIndexMask[Quantum]);
SecShift := Word(SPASecShift[Quantum]);
CachedIndex := -1
end;
destructor TSparsePointerArray.Destroy;
var
i: Integer;
size: Word;
begin
{ Scan section directory and free each section that exists. }
i := 0;
size := FSectionSize * SizeOf(Pointer);
while i < slotsInDir do begin
if secDir^[i] <> nil then
FreeMem(secDir^[i], size);
Inc(i)
end;
{ Free section directory. }
if secDir <> nil then
FreeMem(secDir, slotsInDir * SizeOf(Pointer));
end;
function TSparsePointerArray.GetAt(Index: Integer): Pointer;
var
byteP: PChar;
secIndex: Cardinal;
begin
{ Index into Section Directory using high order part of
index. Get pointer to Section. If not null, index into
Section using low order part of index. }
if Index = cachedIndex then
Result := cachedPointer
else begin
secIndex := Index shr secShift;
if secIndex >= slotsInDir then
byteP := nil
else begin
byteP := secDir^[secIndex];
if byteP <> nil then begin
Inc(byteP, (Index and indexMask) * SizeOf(Pointer));
end
end;
if byteP = nil then Result := nil else Result := PPointer(byteP)^;
cachedIndex := Index;
cachedPointer := Result
end
end;
function TSparsePointerArray.MakeAt(Index: Integer): PPointer;
var
dirP: PSecDir;
p: Pointer;
byteP: PChar;
secIndex: Word;
begin
{ Expand Section Directory if necessary. }
secIndex := Index shr secShift; { Unsigned shift }
if secIndex >= slotsInDir then
dirP := expandDir(secDir, slotsInDir, secIndex + 1)
else
dirP := secDir;
{ Index into Section Directory using high order part of
index. Get pointer to Section. If null, create new
Section. Index into Section using low order part of index. }
secDir := dirP;
p := dirP^[secIndex];
if p = nil then begin
p := makeSec(secIndex, FSectionSize);
dirP^[secIndex] := p
end;
byteP := p;
Inc(byteP, (Index and indexMask) * SizeOf(Pointer));
if Index > FHighBound then
FHighBound := Index;
Result := PPointer(byteP);
cachedIndex := -1
end;
procedure TSparsePointerArray.PutAt(Index: Integer; Item: Pointer);
begin
if (Item <> nil) or (GetAt(Index) <> nil) then
begin
MakeAt(Index)^ := Item;
if Item = nil then
ResetHighBound
end
end;
function TSparsePointerArray.ForAll(ApplyFunction: Pointer {TSPAApply}):
Integer;
var
itemP: PChar; { Pointer to item in section }
item: Pointer;
i, callerBP: Cardinal;
j, index: Integer;
begin
{ Scan section directory and scan each section that exists,
calling the apply function for each non-nil item.
The apply function must be a far local function in the scope of
the procedure P calling ForAll. The trick of setting up the stack
frame (taken from TurboVision's TCollection.ForEach) allows the
apply function access to P's arguments and local variables and,
if P is a method, the instance variables and methods of P's class }
Result := 0;
i := 0;
asm
mov eax,[ebp] { Set up stack frame for local }
mov callerBP,eax
end;
while (i < slotsInDir) and (Result = 0) do begin
itemP := secDir^[i];
if itemP <> nil then begin
j := 0;
index := i shl SecShift;
while (j < FSectionSize) and (Result = 0) do begin
item := PPointer(itemP)^;
if item <> nil then
{ ret := ApplyFunction(index, item.Ptr); }
asm
mov eax,index
mov edx,item
push callerBP
call ApplyFunction
pop ecx
mov @Result,eax
end;
Inc(itemP, SizeOf(Pointer));
Inc(j);
Inc(index)
end
end;
Inc(i)
end;
end;
procedure TSparsePointerArray.ResetHighBound;
var
NewHighBound: Integer;
function Detector(TheIndex: Integer; TheItem: Pointer): Integer; far;
begin
if TheIndex > FHighBound then
Result := 1
else
begin
Result := 0;
if TheItem <> nil then NewHighBound := TheIndex
end
end;
begin
NewHighBound := -1;
ForAll(@Detector);
FHighBound := NewHighBound
end;
{ TSparseList }
constructor TSparseList.Create(Quantum: TSPAQuantum);
begin
NewList(Quantum)
end;
destructor TSparseList.Destroy;
begin
if FList <> nil then FList.Destroy
end;
function TSparseList.Add(Item: Pointer): Integer;
begin
Result := FCount;
FList[Result] := Item;
Inc(FCount)
end;
procedure TSparseList.Clear;
begin
FList.Destroy;
NewList(FQuantum);
FCount := 0
end;
procedure TSparseList.Delete(Index: Integer);
var
I: Integer;
begin
if (Index < 0) or (Index >= FCount) then Exit;
for I := Index to FCount - 1 do
FList[I] := FList[I + 1];
FList[FCount] := nil;
Dec(FCount);
end;
procedure TSparseList.Error;
begin
raise EListError.CreateRes(SListIndexError);
end;
procedure TSparseList.Exchange(Index1, Index2: Integer);
var
temp: Pointer;
begin
temp := Get(Index1);
Put(Index1, Get(Index2));
Put(Index2, temp);
end;
function TSparseList.First: Pointer;
begin
Result := Get(0)
end;
{ Jump to TSparsePointerArray.ForAll so that it looks like it was called
from our caller, so that the BP trick works. }
function TSparseList.ForAll(ApplyFunction: Pointer {TSPAApply}): Integer; assembler;
asm
MOV EAX,[EAX].TSparseList.FList
JMP TSparsePointerArray.ForAll
end;
function TSparseList.Get(Index: Integer): Pointer;
begin
if Index < 0 then Error;
Result := FList[Index]
end;
function TSparseList.IndexOf(Item: Pointer): Integer;
var
MaxIndex, Index: Integer;
function IsTheItem(TheIndex: Integer; TheItem: Pointer): Integer; far;
begin
if TheIndex > MaxIndex then
Result := -1 { Bail out }
else if TheItem <> Item then
Result := 0
else begin
Result := 1; { Found it, stop traversal }
Index := TheIndex
end
end;
begin
Index := -1;
MaxIndex := FList.HighBound;
FList.ForAll(@IsTheItem);
Result := Index
end;
procedure TSparseList.Insert(Index: Integer; Item: Pointer);
var
i: Integer;
begin
if Index < 0 then Error;
I := FCount;
while I > Index do
begin
FList[i] := FList[i - 1];
Dec(i)
end;
FList[Index] := Item;
if Index > FCount then FCount := Index;
Inc(FCount)
end;
function TSparseList.Last: Pointer;
begin
Result := Get(FCount - 1);
end;
procedure TSparseList.Move(CurIndex, NewIndex: Integer);
var
Item: Pointer;
begin
if CurIndex <> NewIndex then
begin
Item := Get(CurIndex);
Delete(CurIndex);
Insert(NewIndex, Item);
end;
end;
procedure TSparseList.NewList(Quantum: TSPAQuantum);
begin
FQuantum := Quantum;
FList := TSparsePointerArray.Create(Quantum)
end;
procedure TSparseList.Pack;
var
i: Integer;
begin
for i := FCount - 1 downto 0 do if Items[i] = nil then Delete(i)
end;
procedure TSparseList.Put(Index: Integer; Item: Pointer);
begin
if Index < 0 then Error;
FList[Index] := Item;
FCount := FList.HighBound + 1
end;
function TSparseList.Remove(Item: Pointer): Integer;
begin
Result := IndexOf(Item);
if Result <> -1 then Delete(Result)
end;
{ TStringSparseList }
constructor TStringSparseList.Create(Quantum: TSPAQuantum);
begin
FList := TSparseList.Create(Quantum)
end;
destructor TStringSparseList.Destroy;
begin
if FList <> nil then begin
Clear;
FList.Destroy
end
end;
procedure TStringSparseList.ReadData(Reader: TReader);
var
i: Integer;
begin
with Reader do begin
i := Integer(ReadInteger);
while i > 0 do begin
InsertObject(Integer(ReadInteger), ReadString, nil);
Dec(i)
end
end
end;
procedure TStringSparseList.WriteData(Writer: TWriter);
var
itemCount: Integer;
function CountItem(TheIndex: Integer; TheItem: Pointer): Integer; far;
begin
Inc(itemCount);
Result := 0
end;
function StoreItem(TheIndex: Integer; TheItem: Pointer): Integer; far;
begin
with Writer do
begin
WriteInteger(TheIndex); { Item index }
WriteString(PStrItem(TheItem)^.FString);
end;
Result := 0
end;
begin
with Writer do
begin
itemCount := 0;
FList.ForAll(@CountItem);
WriteInteger(itemCount);
FList.ForAll(@StoreItem);
end
end;
procedure TStringSparseList.DefineProperties(Filer: TFiler);
begin
Filer.DefineProperty('List', ReadData, WriteData, True);
end;
function TStringSparseList.Get(Index: Integer): String;
var
p: PStrItem;
begin
p := PStrItem(FList[Index]);
if p = nil then Result := '' else Result := p^.FString
end;
function TStringSparseList.GetCount: Integer;
begin
Result := FList.Count
end;
function TStringSparseList.GetObject(Index: Integer): TObject;
var
p: PStrItem;
begin
p := PStrItem(FList[Index]);
if p = nil then Result := nil else Result := p^.FObject
end;
procedure TStringSparseList.Put(Index: Integer; const S: String);
var
p: PStrItem;
obj: TObject;
begin
p := PStrItem(FList[Index]);
if p = nil then obj := nil else obj := p^.FObject;
if (S = '') and (obj = nil) then { Nothing left to store }
FList[Index] := nil
else
FList[Index] := NewStrItem(S, obj);
if p <> nil then DisposeStrItem(p);
Changed
end;
procedure TStringSparseList.PutObject(Index: Integer; AObject: TObject);
var
p: PStrItem;
begin
p := PStrItem(FList[Index]);
if p <> nil then
p^.FObject := AObject
else if AObject <> nil then
Error;
Changed
end;
procedure TStringSparseList.Changed;
begin
if Assigned(FOnChange) then FOnChange(Self)
end;
procedure TStringSparseList.Error;
begin
raise EStringSparseListError.CreateRes(SPutObjectError);
end;
procedure TStringSparseList.Delete(Index: Integer);
var
p: PStrItem;
begin
p := PStrItem(FList[Index]);
if p <> nil then DisposeStrItem(p);
FList.Delete(Index);
Changed
end;
procedure TStringSparseList.Exchange(Index1, Index2: Integer);
begin
FList.Exchange(Index1, Index2);
end;
procedure TStringSparseList.Insert(Index: Integer; const S: String);
begin
FList.Insert(Index, NewStrItem(S, nil));
Changed
end;
procedure TStringSparseList.Clear;
function ClearItem(TheIndex: Integer; TheItem: Pointer): Integer; far;
begin
DisposeStrItem(PStrItem(TheItem)); { Item guaranteed non-nil }
Result := 0
end;
begin
FList.ForAll(@ClearItem);
FList.Clear;
Changed
end;
{ TStringGridStrings }
{ AIndex < 0 is a column (for column -AIndex - 1)
AIndex > 0 is a row (for row AIndex - 1)
AIndex = 0 denotes an empty row or column }
constructor TStringGridStrings.Create(AGrid: TStringGrid; AIndex: Longint);
begin
inherited Create;
FGrid := AGrid;
FIndex := AIndex;
end;
procedure TStringGridStrings.Assign(Source: TPersistent);
var
I, Max: Integer;
begin
if Source is TStrings then
begin
BeginUpdate;
Max := TStrings(Source).Count - 1;
if Max >= Count then Max := Count - 1;
try
for I := 0 to Max do
begin
Put(I, TStrings(Source).Strings[I]);
PutObject(I, TStrings(Source).Objects[I]);
end;
finally
EndUpdate;
end;
Exit;
end;
inherited Assign(Source);
end;
procedure TStringGridStrings.CalcXY(Index: Integer; var X, Y: Integer);
begin
if FIndex = 0 then
begin
X := -1; Y := -1;
end else if FIndex > 0 then
begin
X := Index;
Y := FIndex - 1;
end else
begin
X := -FIndex - 1;
Y := Index;
end;
end;
{ Changes the meaning of Add to mean copy to the first empty string }
function TStringGridStrings.Add(const S: string): Integer;
var
I: Integer;
begin
for I := 0 to Count - 1 do
if Strings[I] = '' then
begin
Strings[I] := S;
Result := I;
Exit;
end;
Result := -1;
end;
procedure TStringGridStrings.Clear;
var
SSList: TStringSparseList;
I: Integer;
function BlankStr(TheIndex: Integer; TheItem: Pointer): Integer; far;
begin
Objects[TheIndex] := nil;
Strings[TheIndex] := '';
Result := 0;
end;
begin
if FIndex > 0 then
begin
SSList := TStringSparseList(TSparseList(FGrid.FData)[FIndex - 1]);
if SSList <> nil then SSList.List.ForAll(@BlankStr);
end
else if FIndex < 0 then
for I := Count - 1 downto 0 do
begin
Objects[I] := nil;
Strings[I] := '';
end;
end;
function TStringGridStrings.Get(Index: Integer): string;
var
X, Y: Integer;
begin
CalcXY(Index, X, Y);
if X < 0 then Result := '' else Result := FGrid.Cells[X, Y];
end;
function TStringGridStrings.GetCount: Integer;
begin
{ Count of a row is the column count, and vice versa }
if FIndex = 0 then Result := 0
else if FIndex > 0 then Result := Integer(FGrid.ColCount)
else Result := Integer(FGrid.RowCount);
end;
function TStringGridStrings.GetObject(Index: Integer): TObject;
var
X, Y: Integer;
begin
CalcXY(Index, X, Y);
if X < 0 then Result := nil else Result := FGrid.Objects[X, Y];
end;
procedure TStringGridStrings.Put(Index: Integer; const S: string);
var
X, Y: Integer;
begin
CalcXY(Index, X, Y);
FGrid.Cells[X, Y] := S;
end;
procedure TStringGridStrings.PutObject(Index: Integer; AObject: TObject);
var
X, Y: Integer;
begin
CalcXY(Index, X, Y);
FGrid.Objects[X, Y] := AObject;
end;
procedure TStringGridStrings.SetUpdateState(Updating: Boolean);
begin
FGrid.SetUpdateState(Updating);
end;
{ TStringGrid }
constructor TStringGrid.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Initialize;
end;
destructor TStringGrid.Destroy;
function FreeItem(TheIndex: Integer; TheItem: Pointer): Integer; far;
begin
TObject(TheItem).Free;
Result := 0;
end;
begin
if FRows <> nil then
begin
TSparseList(FRows).ForAll(@FreeItem);
TSparseList(FRows).Free;
end;
if FCols <> nil then
begin
TSparseList(FCols).ForAll(@FreeItem);
TSparseList(FCols).Free;
end;
if FData <> nil then
begin
TSparseList(FData).ForAll(@FreeItem);
TSparseList(FData).Free;
end;
inherited Destroy;
end;
procedure TStringGrid.ColumnMoved(FromIndex, ToIndex: Longint);
function MoveColData(Index: Integer; ARow: TStringSparseList): Integer; far;
begin
ARow.Move(FromIndex, ToIndex);
Result := 0;
end;
begin
TSparseList(FData).ForAll(@MoveColData);
Invalidate;
inherited ColumnMoved(FromIndex, ToIndex);
end;
procedure TStringGrid.RowMoved(FromIndex, ToIndex: Longint);
begin
TSparseList(FData).Move(FromIndex, ToIndex);
Invalidate;
inherited RowMoved(FromIndex, ToIndex);
end;
function TStringGrid.GetEditText(ACol, ARow: Longint): string;
begin
Result := Cells[ACol, ARow];
if Assigned(FOnGetEditText) then FOnGetEditText(Self, ACol, ARow, Result);
end;
procedure TStringGrid.SetEditText(ACol, ARow: Longint; const Value: string);
begin
DisableEditUpdate;
try
if Value <> Cells[ACol, ARow] then Cells[ACol, ARow] := Value;
finally
EnableEditUpdate;
end;
inherited SetEditText(ACol, ARow, Value);
end;
procedure TStringGrid.DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState);
procedure DrawCellText;
var
S: string;
begin
S := Cells[ACol, ARow];
ExtTextOut(Canvas.Handle, ARect.Left + 2, ARect.Top + 2, ETO_CLIPPED or
ETO_OPAQUE, @ARect, PChar(S), Length(S), nil);
end;
begin
if DefaultDrawing then DrawCellText;
inherited DrawCell(ACol, ARow, ARect, AState);
end;
procedure TStringGrid.DisableEditUpdate;
begin
Inc(FEditUpdate);
end;
procedure TStringGrid.EnableEditUpdate;
begin
Dec(FEditUpdate);
end;
procedure TStringGrid.Initialize;
var
quantum: TSPAQuantum;
begin
if FCols = nil then
begin
if ColCount > 512 then quantum := SPALarge else quantum := SPASmall;
FCols := TSparseList.Create(quantum);
end;
if RowCount > 256 then quantum := SPALarge else quantum := SPASmall;
if FRows = nil then FRows := TSparseList.Create(quantum);
if FData = nil then FData := TSparseList.Create(quantum);
end;
procedure TStringGrid.SetUpdateState(Updating: Boolean);
begin
FUpdating := Updating;
if not Updating and FNeedsUpdating then
begin
InvalidateGrid;
FNeedsUpdating := False;
end;
end;
procedure TStringGrid.Update(ACol, ARow: Integer);
begin
if not FUpdating then InvalidateCell(ACol, ARow)
else FNeedsUpdating := True;
if (ACol = Col) and (ARow = Row) and (FEditUpdate = 0) then InvalidateEditor;
end;
function TStringGrid.EnsureColRow(Index: Integer; IsCol: Boolean):
TStringGridStrings;
var
RCIndex: Integer;
PList: ^TSparseList;
begin
if IsCol then PList := @FCols else PList := @FRows;
Result := TStringGridStrings(PList^[Index]);
if Result = nil then
begin
if IsCol then RCIndex := -Index - 1 else RCIndex := Index + 1;
Result := TStringGridStrings.Create(Self, RCIndex);
PList^[Index] := Result;
end;
end;
function TStringGrid.EnsureDataRow(ARow: Integer): Pointer;
var
quantum: TSPAQuantum;
begin
Result := TStringSparseList(TSparseList(FData)[ARow]);
if Result = nil then
begin
if ColCount > 512 then quantum := SPALarge else quantum := SPASmall;
Result := TStringSparseList.Create(quantum);
TSparseList(FData)[ARow] := Result;
end;
end;
function TStringGrid.GetCells(ACol, ARow: Integer): string;
var
ssl: TStringSparseList;
begin
ssl := TStringSparseList(TSparseList(FData)[ARow]);
if ssl = nil then Result := '' else Result := ssl[ACol];
end;
function TStringGrid.GetCols(Index: Integer): TStrings;
begin
Result := EnsureColRow(Index, True);
end;
function TStringGrid.GetObjects(ACol, ARow: Integer): TObject;
var
ssl: TStringSparseList;
begin
ssl := TStringSparseList(TSparseList(FData)[ARow]);
if ssl = nil then Result := nil else Result := ssl.Objects[ACol];
end;
function TStringGrid.GetRows(Index: Integer): TStrings;
begin
Result := EnsureColRow(Index, False);
end;
procedure TStringGrid.SetCells(ACol, ARow: Integer; const Value: string);
begin
TStringGridStrings(EnsureDataRow(ARow))[ACol] := Value;
EnsureColRow(ACol, True);
EnsureColRow(ARow, False);
Update(ACol, ARow);
end;
procedure TStringGrid.SetCols(Index: Integer; Value: TStrings);
begin
EnsureColRow(Index, True).Assign(Value);
end;
procedure TStringGrid.SetObjects(ACol, ARow: Integer; Value: TObject);
begin
TStringGridStrings(EnsureDataRow(ARow)).Objects[ACol] := Value;
EnsureColRow(ACol, True);
EnsureColRow(ARow, False);
Update(ACol, ARow);
end;
procedure TStringGrid.SetRows(Index: Integer; Value: TStrings);
begin
EnsureColRow(Index, False).Assign(Value);
end;
end.