lazarus/components/rtticontrols/rttigrids.pas

1318 lines
39 KiB
ObjectPascal

{
*****************************************************************************
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
Author: Mattias Gaertner
Abstract:
Provides LCL controls that access lists of properties of TPersistent objects
via RTTI
- the FreePascal Run Time Type Information.
ToDo:
- better keyboard navigation
- property editor for 'ListObject'
- persistent selected cell after rebuild
- moving objects
- adding, deleting objects
}
unit RTTIGrids;
{$mode objfpc}{$H+}
{$IF FPC_FULLVERSION>30300}
{$WARN 6060 off : }
{$ENDIF}
interface
uses
Classes, SysUtils, TypInfo,
// LCL
LCLType, Controls, Grids,
// LazUtils
LazUtilities, LazLoggerBase, LazUTF8,
// IdeIntf
ObjectInspector, PropEdits,
RTTICtrls;
type
{ TTICustomPropertyGrid }
TTICustomPropertyGrid = class(TCustomPropertiesGrid)
end;
{ TTIPropertyGrid }
TTIPropertyGrid = class(TTICustomPropertyGrid)
published
property Align;
property Anchors;
property BackgroundColor;
property BorderSpacing;
property BorderStyle;
property CheckboxForBoolean;
property Constraints;
property DefaultItemHeight;
property DefaultValueFont;
property Filter;
property Indent;
property NameFont;
property OnChangeBounds;
property OnClick;
property OnEditorFilter;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnModified;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnResize;
property PopupMenu;
property PreferredSplitterX;
property SplitterX;
property Tabstop;
property TIObject;
property ValueFont;
property Visible;
end;
TTICustomGrid = class;
{ TTIGridProperty }
TTIGridProperty = class
private
FEditor: TPropertyEditor;
FEditorControl: TWinControl;
FButtonEditorControl: TWinControl;
FGrid: TTICustomGrid;
FIndex: integer;
FTitle: string;
procedure SetTitle(const AValue: string);
procedure EditorControlKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
public
constructor Create(TheGrid: TTICustomGrid; TheEditor: TPropertyEditor;
TheIndex: integer);
destructor Destroy; override;
function PropInfo: PPropInfo;
function GetEditorControl: TWinControl;
function GetButtonEditorControl: TWinControl;
function PropName: string;
public
property Editor: TPropertyEditor read FEditor;
property Grid: TTICustomGrid read FGrid;
property Index: integer read FIndex;
property Title: string read FTitle write SetTitle;
end;
TTIListDirection = (tldObjectsAsRows, tldObjectsAsColumns);
TTIGridState = (
tgsRebuildTIListNeeded,
tgsRebuildingTIList,
tgsDefaultDrawing // set during default drawing
);
TTIGridStates = set of TTIGridState;
TTIGridCellType = (
tgctNone, // out or undefined
tgctValue, // a normal property cell
tgctPropName, // header cell for property name
tgctPropNameAlt,// header cell for alternative prop name (e.g. FixedRows>1)
tgctObjectName, // header cell for object name
tgctObjectNameAlt,// header cell for alternative obj name (e.g. FixedCols>1)
tgctCorner // corner cell left, top of grid
);
TTIGridCellTypes = set of TTIGridCellType;
TTIGridOption = (
tgoStartIndexAtOne, // start shown object index at 1
tgoShowOnlyProperties // show only properties in ShowOnlyProperties
);
TTIGridOptions = set of TTIGridOption;
TTIGridGetObject = procedure(Sender: TTICustomGrid; Index: integer;
var TIObject: TPersistent) of object;
TTIGridGetObjectCount = procedure(Sender: TTICustomGrid;
ListObject: TObject;
var ObjCount: integer) of object;
TTIGridGetObjectName = procedure(Sender: TObject; Index: integer;
TIObject: TPersistent;
var ObjName: string) of object;
TTIGridCreateCellEditor = procedure(GridProp: TTIGridProperty;
var NewEditorControl: TControl) of object;
TTIGridInitCellEditor = procedure(GridProp: TTIGridProperty;
TheEditorControl: TControl) of object;
{ TTICustomGrid }
TTICustomGrid = class(TCustomGrid)
private
FAliasPropertyNames: TAliasStrings;
FFilter: TTypeKinds;
FHideProperties: TStrings;
FListDirection: TTIListDirection;
FListObject: TObject;
FOnCreateCellEditor: TTIGridCreateCellEditor;
FOnGetObject: TTIGridGetObject;
FOnGetObjectCount: TTIGridGetObjectCount;
FOnGetObjectName: TTIGridGetObjectName;
FOnHeaderClick: THdrEvent;
FOnHeaderSized: THdrEvent;
FHeaderPropHook: TPropertyEditorHook;
FOnInitCellEditor: TTIGridInitCellEditor;
FOnPropertiesCreated: TNotifyEvent;
FPropertyOrder: TStrings;
FShowOnlyProperties: TStrings;
FTIOptions: TTIGridOptions;
FTIStates: TTIGridStates;
FTIObjectCount: integer;
FProperties: TList;
FExtraBtnEditor: TWinControl;
function GetProperties(Index: integer): TTIGridProperty;
function GetPropertyCount: integer;
procedure SetAliasPropertyNames(const AValue: TAliasStrings);
procedure SetFilter(const AValue: TTypeKinds);
procedure SetHideProperties(const AValue: TStrings);
procedure SetListDirection(const AValue: TTIListDirection);
procedure SetListObject(const AValue: TObject);
procedure SetPropertyOrder(const AValue: TStrings);
procedure SetShowOnlyProperties(const AValue: TStrings);
procedure SetTIOptions(const NewOptions: TTIGridOptions);
{$IFDEF DebugEditor}
procedure DebugEditor(msg: String; aEditor: TWinControl);
{$ENDIF}
protected
procedure RebuildGridLayout; virtual;
procedure AddHeaderPropertyEditor(Prop: TPropertyEditor);
procedure BeforeMoveSelection(const DCol,DRow: Integer); override;
procedure CalcCellExtent(aCol, aRow: Integer; var aRect: TRect); override;
procedure DoEditorHide; override;
procedure DoEditorShow; override;
procedure DrawCell(aCol, aRow: Integer; aRect: TRect;
aState: TGridDrawState); override;
procedure EditorPosChanged(aEditor: TWinControl);
procedure EditorWidthChanged(aCol, aWidth: Integer); override;
procedure HeaderClick(IsColumn: Boolean; index: Integer); override;
procedure HeaderSized(IsColumn: Boolean; index: Integer); override;
procedure GetAutoFillColumnInfo(const Index: Integer;
var aMin,aMax,aPriority: Integer); override;
procedure SelectEditor; override;
procedure DoEditorControlKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState); virtual;
procedure WriteCellText(aRect: TRect; const aText: string);
procedure UnlinkPropertyEditor(aEditor: TWinControl);
procedure SetFixedCols(const AValue: Integer); override;
procedure SetFixedRows(const AValue: Integer); override;
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
procedure Loaded; override;
procedure ReloadTIList;
procedure ClearProperties;
procedure DefaultDrawCell(aCol, aRow: Integer; var aRect: TRect;
aState: TGridDrawState); virtual;
procedure DrawObjectName(Index: integer; const aRect: TRect;
aState: TGridDrawState);
procedure GetCellEditor(aCol, aRow: integer;
out aPropEditor: TPropertyEditor;
out aIndependentEditor: boolean);
procedure FreeCellEditor(PropEditor: TPropertyEditor);
function GridStateToPropEditState(GridState: TGridDrawState
): TPropEditDrawState;
function GetTIObject(Index: integer): TPersistent;
procedure MapCell(aCol, aRow: integer;
out ObjectIndex, PropertyIndex: integer;
out CellType: TTIGridCellType);
function GetCurrentGridProperty: TTIGridProperty;
function IndexOfGridProperty(const PropName: string): integer;
function FindGridProperty(const PropName: string): TTIGridProperty;
procedure MoveProperty(FromID, ToID: integer);
public
property AliasPropertyNames: TAliasStrings read FAliasPropertyNames
write SetAliasPropertyNames;
property DefaultRowHeight default 20;
property Filter: TTypeKinds read FFilter write SetFilter default AllTypeKinds;
property HideProperties: TStrings read FHideProperties
write SetHideProperties;
property ListDirection: TTIListDirection read FListDirection
write SetListDirection default tldObjectsAsRows;
property ListObject: TObject read FListObject write SetListObject;
property OnCreateCellEditor: TTIGridCreateCellEditor
read FOnCreateCellEditor write FOnCreateCellEditor;
property OnGetObject: TTIGridGetObject read FOnGetObject write FOnGetObject;
property OnGetObjectCount: TTIGridGetObjectCount read FOnGetObjectCount
write FOnGetObjectCount;
property OnGetObjectName: TTIGridGetObjectName read FOnGetObjectName
write FOnGetObjectName;
property OnHeaderClick: THdrEvent read FOnHeaderClick write FOnHeaderClick;
property OnHeaderSized: THdrEvent read FOnHeaderSized write FOnHeaderSized;
property OnInitCellEditor: TTIGridInitCellEditor read FOnInitCellEditor
write FOnInitCellEditor;
property OnPropertiesCreated: TNotifyEvent read FOnPropertiesCreated
write FOnPropertiesCreated;
property Properties[Index: integer]: TTIGridProperty read GetProperties;
property PropertyCount: integer read GetPropertyCount;
property PropertyOrder: TStrings read FPropertyOrder write SetPropertyOrder;
property ShowOnlyProperties: TStrings read FShowOnlyProperties
write SetShowOnlyProperties;
property TIObjectCount: integer read FTIObjectCount;
property TIOptions: TTIGridOptions read FTIOptions write SetTIOptions;
end;
{ TTIGrid }
TTIGrid = class(TTICustomGrid)
published
property AliasPropertyNames;
property Align;
property Anchors;
property AutoAdvance;
property AutoFillColumns;
property BorderSpacing;
property BorderStyle;
property Color;
property Constraints;
property DefaultDrawing;
property DefaultRowHeight;
property Enabled;
property Filter;
property FixedColor;
property FixedCols;
property FixedRows;
property Flat;
property Font;
property HideProperties;
property ListDirection;
property OnCreateCellEditor;
property OnDblClick;
property OnEditButtonClick;
property OnEnter;
property OnExit;
property OnGetObject;
property OnGetObjectCount;
property OnGetObjectName;
property OnHeaderClick;
property OnHeaderSized;
property OnInitCellEditor;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnPrepareCanvas;
property OnPropertiesCreated;
property Options;
property ParentColor;
property ParentFont;
property PopupMenu;
property PropertyOrder;
property ShowHint;
property ShowOnlyProperties;
property TabOrder;
property TabStop;
property TIOptions;
property TitleFont;
property Visible;
end;
{ TRegisteredTIGridControl }
TRegisteredTIGridControl = class
private
FPropEditorClass: TPropertyEditorClass;
FWinControlClass: TWinControlClass;
public
property PropEditorClass: TPropertyEditorClass read FPropEditorClass
write FPropEditorClass;
property WinControlClass: TWinControlClass read FWinControlClass
write FWinControlClass;
end;
procedure RegisterTIGridControl(PropEditorClass: TPropertyEditorClass;
WinControlClass: TWinControlClass);
function FindTIGridControl(PropEditorClass: TPropertyEditorClass
): TWinControlClass;
procedure Register;
implementation
var
RegisteredTIGridControls: TList;
procedure RegisterTIGridControl(PropEditorClass: TPropertyEditorClass;
WinControlClass: TWinControlClass);
var
NewItem: TRegisteredTIGridControl;
begin
if (PropEditorClass=nil) or (WinControlClass=nil) then exit;
if RegisteredTIGridControls=nil then RegisteredTIGridControls:=TList.Create;
NewItem:=TRegisteredTIGridControl.Create;
if NewItem=nil then ;
NewItem.PropEditorClass:=PropEditorClass;
NewItem.WinControlClass:=WinControlClass;
RegisteredTIGridControls.Add(NewItem);
end;
function FindTIGridControl(PropEditorClass: TPropertyEditorClass
): TWinControlClass;
var
BestItem: TRegisteredTIGridControl;
i: Integer;
CurItem: TRegisteredTIGridControl;
begin
Result:=nil;
if RegisteredTIGridControls=nil then exit;
BestItem:=nil;
for i:=0 to RegisteredTIGridControls.Count-1 do begin
CurItem:=TRegisteredTIGridControl(RegisteredTIGridControls[i]);
debugln('FindTIGridControl PropEditorClass=',PropEditorClass.ClassName,
' CurItem.PropEditorClass=',CurItem.PropEditorClass.ClassName,
' CurItem.WinControlClass=',CurItem.WinControlClass.ClassName,
' Candidate=',dbgs(PropEditorClass.InheritsFrom(CurItem.PropEditorClass))
);
if PropEditorClass.InheritsFrom(CurItem.PropEditorClass)
and ((BestItem=nil)
or (CurItem.PropEditorClass.InheritsFrom(BestItem.PropEditorClass)))
then begin
BestItem:=CurItem;
end;
end;
if BestItem<>nil then
Result:=BestItem.WinControlClass;
end;
procedure FinalizeTIGrids;
var
i: Integer;
begin
if RegisteredTIGridControls=nil then exit;
for i:=0 to RegisteredTIGridControls.Count-1 do
TObject(RegisteredTIGridControls[i]).Free;
FreeAndNil(RegisteredTIGridControls);
end;
procedure Register;
begin
RegisterComponents('RTTI',[TTIPropertyGrid,TTIGrid]);
end;
{ TTICustomStringGrid }
procedure TTICustomGrid.SetListDirection(const AValue: TTIListDirection);
begin
if FListDirection=AValue then exit;
FListDirection:=AValue;
ReloadTIList;
end;
function TTICustomGrid.GetPropertyCount: integer;
begin
if FProperties <> nil then
Result := FProperties.Count
else
Result := 0;
end;
procedure TTICustomGrid.SetAliasPropertyNames(const AValue: TAliasStrings);
begin
if FAliasPropertyNames=AValue then exit;
if FAliasPropertyNames.Equals(AValue) then exit;
FAliasPropertyNames.Assign(AValue);
Invalidate;
end;
function TTICustomGrid.GetProperties(Index: integer): TTIGridProperty;
begin
Result:=TTIGridProperty(FProperties[Index]);
end;
procedure TTICustomGrid.SetFilter(const AValue: TTypeKinds);
begin
if FFilter=AValue then exit;
FFilter:=AValue;
ReloadTIList;
end;
procedure TTICustomGrid.SetHideProperties(const AValue: TStrings);
begin
if FHideProperties=AValue then exit;
if FHideProperties.Equals(AValue) then exit;
FHideProperties.Assign(AValue);
ReloadTIList;
end;
procedure TTICustomGrid.SetListObject(const AValue: TObject);
begin
if FListObject=AValue then exit;
FListObject:=AValue;
ReloadTIList;
end;
procedure TTICustomGrid.SetPropertyOrder(const AValue: TStrings);
begin
if FPropertyOrder=AValue then exit;
if FPropertyOrder.Equals(AValue) then exit;
FPropertyOrder.Assign(AValue);
ReloadTIList;
end;
procedure TTICustomGrid.SetShowOnlyProperties(const AValue: TStrings);
begin
if FShowOnlyProperties=AValue then exit;
if FShowOnlyProperties.Equals(AValue) then exit;
FShowOnlyProperties.Assign(AValue);
if tgoShowOnlyProperties in FTIOptions then
ReloadTIList;
end;
procedure TTICustomGrid.SetTIOptions(const NewOptions: TTIGridOptions);
var
ChangedOptions: TTIGridOptions;
begin
if FTIOptions=NewOptions then exit;
ChangedOptions:=(FTIOptions-NewOptions)+(NewOptions-FTIOptions);
FTIOptions:=NewOptions;
if tgoStartIndexAtOne in ChangedOptions then Invalidate;
if tgoShowOnlyProperties in ChangedOptions then ReloadTIList;
end;
{$IFDEF DebugEditor}
procedure TTICustomGrid.DebugEditor(msg: String; aEditor: TWinControl);
begin
DbgOut(Msg,': Editor=');
if aEditor=nil then DbgOut('nil')
else DbgOut(AEditor.className);
DebugLn;
end;
{$ENDIF}
procedure TTICustomGrid.ReloadTIList;
begin
if tgsRebuildingTIList in FTIStates then exit;
if ([csLoading,csDestroying]*ComponentState)<>[] then begin
Include(FTIStates,tgsRebuildTIListNeeded);
exit;
end;
Exclude(FTIStates,tgsRebuildTIListNeeded);
Include(FTIStates,tgsRebuildingTIList);
try
EditorHide;
ClearProperties;
FTIObjectCount:=0;
if Assigned(OnGetObjectCount) then
OnGetObjectCount(Self,ListObject,FTIObjectCount)
else if FListObject is TCollection then
FTIObjectCount:=TCollection(FListObject).Count
else if FListObject is TList then
FTIObjectCount:=TList(FListObject).Count
else if FListObject is TFPList then
FTIObjectCount:=TFPList(FListObject).Count
else begin
// ListObject is not valid
end;
RebuildGridLayout;
finally
Exclude(FTIStates,tgsRebuildingTIList);
end;
end;
procedure TTICustomGrid.RebuildGridLayout;
var
CurItem: TPersistent;
HeaderLines: LongInt;
PropCount: LongInt;
i: Integer;
ToID: Integer;
FromID: integer;
begin
if FProperties=nil then
exit;
ClearProperties;
// set column/row count for objects
if ListDirection=tldObjectsAsRows then begin
HeaderLines:=FixedRows;
RowCount:=HeaderLines+FTIObjectCount
end else begin
HeaderLines:=FixedCols;
ColCount:=HeaderLines+FTIObjectCount;
end;
// get first object to create the grid header
if FTIObjectCount=0 then exit;
CurItem:=GetTIObject(0);
if not (CurItem is TPersistent) then begin
debugln('TTICustomGrid.LoadCollection First CollectionItem=',dbgsName(CurItem));
exit;
end;
// get header properties
FHeaderPropHook.LookupRoot:=CurItem;
ClearProperties;
GetPersistentProperties(CurItem, FFilter, FHeaderPropHook,
@AddHeaderPropertyEditor,nil);
// reorder
ToID:=0;
for i:=0 to FPropertyOrder.Count-1 do begin
FromID:=IndexOfGridProperty(FPropertyOrder[i]);
if FromID>=0 then begin
MoveProperty(FromID,ToID);
inc(ToID);
end;
end;
// set column/row count for properties
PropCount:=PropertyCount;
if ListDirection=tldObjectsAsRows then begin
ColCount:=FixedCols+PropCount;
end else begin
RowCount:=FixedRows+PropCount;
end;
if Assigned(OnPropertiesCreated) then OnPropertiesCreated(Self);
end;
procedure TTICustomGrid.AddHeaderPropertyEditor(Prop: TPropertyEditor);
var
NewProperty: TTIGridProperty;
PropName: String;
begin
PropName:=Prop.GetName;
if (FHideProperties.IndexOf(PropName)>=0)
or ((tgoShowOnlyProperties in FTIOptions)
and (FShowOnlyProperties.IndexOf(PropName)<0))
then begin
// skip property
Prop.Free;
exit;
end;
NewProperty:=TTIGridProperty.Create(Self,Prop,FProperties.Count);
FProperties.Add(NewProperty);
end;
procedure TTICustomGrid.BeforeMoveSelection(const DCol, DRow: Integer);
begin
inherited BeforeMoveSelection(DCol, DRow);
if (FExtraBtnEditor<>nil) and (FExtraBtnEditor.Visible) then begin
{$IFDEF DebugEditor}
DebugEditor('BeforeMoveSelection: ', FExtraBtnEditor);
{$ENDIF}
LockEditor;
FExtraBtnEditor.Parent := nil;
UnlinkPropertyEditor(FExtraBtnEditor);
FExtraBtnEditor.Visible := false;
FExtraBtnEditor := nil;
UnlockEditor;
end;
end;
procedure TTICustomGrid.DrawCell(aCol, aRow: Integer; aRect: TRect;
aState: TGridDrawState);
begin
if Assigned(OnDrawCell) and not (csDesigning in ComponentState) then begin
PrepareCanvas(aCol, aRow, aState);
if DefaultDrawing then
Canvas.FillRect(aRect);
OnDrawCell(Self,aCol,aRow,aRect,aState)
end else
DefaultDrawCell(aCol,aRow,aRect,aState);
DrawCellGrid(aCol,aRow,aRect,aState);
end;
procedure TTICustomGrid.EditorPosChanged(aEditor: TWinControl);
var
NewRect, ARect: TRect;
begin
// position
NewRect:=CellRect(Col,Row);
if FExtraBtnEditor<>nil then begin
ARect := NewRect;
ARect.Left := ARect.Right-20;
Dec(NewRect.Right,20);
FExtraBtnEditor.BoundsRect := ARect;
end;
aEditor.BoundsRect:=NewRect;
end;
procedure TTICustomGrid.EditorWidthChanged(aCol, aWidth: Integer);
begin
if (aCol=0) and (aWidth=0) then ;
EditorPosChanged(Editor);
end;
procedure TTICustomGrid.CalcCellExtent(aCol, aRow: Integer; var aRect: TRect);
begin
if (aCol=0) and (aRow=0) and (ARect.Left=0) then ;
//
end;
procedure TTICustomGrid.HeaderClick(IsColumn: Boolean; index: Integer);
begin
inherited HeaderClick(IsColumn, index);
if Assigned(OnHeaderClick) then OnHeaderClick(Self, IsColumn, index);
end;
procedure TTICustomGrid.HeaderSized(IsColumn: Boolean; index: Integer);
begin
inherited HeaderSized(IsColumn, index);
if Assigned(OnHeaderSized) then OnHeaderSized(Self, IsColumn, index);
end;
procedure TTICustomGrid.GetAutoFillColumnInfo(const Index: Integer; var aMin,
aMax, aPriority: Integer);
begin
if (aMin=0) and (aMax=0) then ;
if (Index<FixedCols) then
aPriority := 0
else
aPriority := 1;
end;
procedure TTICustomGrid.SelectEditor;
var
NewEditor: TWinControl;
ObjectIndex: integer;
PropertyIndex: integer;
CellType: TTIGridCellType;
PropLink: TCustomPropertyLink;
CurObject: TPersistent;
CurProp: TTIGridProperty;
PropName: String;
begin
if FProperties=nil then begin
// still creating ancestor grid
Editor := nil;
FExtraBtnEditor := nil;
exit;
end;
NewEditor:=nil;
MapCell(Col,Row,ObjectIndex,PropertyIndex,CellType);
if CellType=tgctValue then begin
CurProp:=Properties[PropertyIndex];
NewEditor:=CurProp.GetEditorControl;
FExtraBtnEditor := CurProp.GetButtonEditorControl;
{$IFDEF DebugEditor}
DebugEditor('SelectEditor', NewEditor);
DebugEditor('SelectEditor extra', FExtraBtnEditor);
{$ENDIF}
EditorPosChanged(NewEditor);
// connect to cell property
PropLink:=GetPropertyLinkOfComponent(NewEditor);
if PropLink<>nil then begin
CurObject:=GetTIObject(ObjectIndex);
PropName:=CurProp.PropName;
PropLink.SetObjectAndProperty(CurObject,PropName);
end;
if (FExtraBtnEditor<>nil) then begin
PropLink:=GetPropertyLinkOfComponent(FExtraBtnEditor);
if PropLink<>nil then begin
CurObject:=GetTIObject(ObjectIndex);
PropName:=CurProp.PropName;
PropLink.SetObjectAndProperty(CurObject,PropName);
end;
if FExtraBtnEditor.Parent = nil then
FExtraBtnEditor.Visible := False;
FExtraBtnEditor.Parent := Self;
end;
if Assigned(OnSelectEditor) then
OnSelectEditor(Self,Col,Row,NewEditor);
end else
FExtraBtnEditor := nil;
Editor:=NewEditor;
// options
//EditorOptions := EO_HOOKKEYPRESS or EO_HOOKKEYDOWN or EO_HOOKKEYDOWN;
end;
procedure TTICustomGrid.DoEditorControlKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure MoveSel(Rel: Boolean; aCol,aRow: Integer);
begin
SelectActive:=false;
MoveNextSelectable(Rel, aCol, aRow);
Key:=0;
end;
var
Relaxed: Boolean;
GridProp: TTIGridProperty;
CurLink: TCustomPropertyLink;
begin
if Sender=nil then ;
if (Shift=[ssCtrl]) then begin
Relaxed:=not (goRowSelect in Options) or (goRelaxedRowSelect in Options);
case Key of
VK_UP:
begin
MoveSel(True, 0, -1);
end;
VK_DOWN:
begin
MoveSel(True, 0, 1);
end;
VK_HOME:
begin
if ssCtrl in Shift then MoveSel(False, Col, FixedRows)
else
if Relaxed then MoveSel(False, FixedCols, Row)
else MoveSel(False, Col, FixedRows);
end;
VK_END:
begin
if ssCtrl in Shift then MoveSel(False, Col, RowCount-1)
else
if Relaxed then MoveSel(False, ColCount-1, Row)
else MoveSel(False, Col, RowCount-1);
end;
VK_F2:
begin
GridProp:=GetCurrentGridProperty;
if (GridProp<>nil) and (paDialog in GridProp.Editor.GetAttributes) then
begin
GridProp.Editor.Edit;
CurLink:=GetPropertyLinkOfComponent(Editor);
if CurLink<>nil then
CurLink.LoadFromProperty;
end;
end;
end;
end;
end;
procedure TTICustomGrid.DoEditorHide;
begin
{$IFDEF DebugEditor}
DebugEditor('doEditorHide', Editor);
{$ENDIF}
UnlinkPropertyEditor(Editor);
UnlinkPropertyEditor(FExtraBtnEditor);
inherited DoEditorHide;
end;
procedure TTICustomGrid.DoEditorShow;
begin
{$IFDEF DebugEditor}
DebugEditor('doEditorShow', Editor);
{$ENDIF}
inherited DoEditorShow;
if FExtraBtnEditor<>nil then begin
{$IFDEF DebugEditor}
DebugEditor('doEditorShow Extra', FExtraBtnEditor);
{$ENDIF}
FExtraBtnEditor.Parent := Self;
FExtraBtnEditor.Visible := True;
end;
end;
procedure TTICustomGrid.WriteCellText(aRect: TRect; const aText: string);
begin
if aText='' then exit;
case Canvas.TextStyle.Alignment of
Classes.taLeftJustify: Inc(aRect.Left, 3);
Classes.taRightJustify: Dec(aRect.Right, 3);
end;
Inc(aRect.Top, 2);
Canvas.TextRect(aRect,ARect.Left,ARect.Top,aText);
end;
procedure TTICustomGrid.UnlinkPropertyEditor(aEditor: TWinControl);
var
PropLink: TCustomPropertyLink;
begin
PropLink:=GetPropertyLinkOfComponent(aEditor);
if PropLink<>nil then
PropLink.SetObjectAndProperty(nil,'');
end;
procedure TTICustomGrid.SetFixedCols(const AValue: Integer);
begin
inherited SetFixedCols(AValue);
RebuildGridLayout;
end;
procedure TTICustomGrid.SetFixedRows(const AValue: Integer);
begin
inherited SetFixedRows(AValue);
RebuildGridLayout;
end;
constructor TTICustomGrid.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
FHeaderPropHook:=TPropertyEditorHook.Create(Self);
FFilter:=[{tkUnknown,}tkInteger,tkChar,tkEnumeration,
tkFloat,{tkSet,tkMethod,}tkSString,tkLString,tkAString,
tkWString,tkVariant,{tkArray,tkRecord,tkInterface,}
{tkClass,tkObject,}tkWChar,tkBool,tkInt64,
tkQWord{,tkDynArray,tkInterfaceRaw}];
FProperties:=TList.Create;
FListDirection:=tldObjectsAsRows;
FHideProperties:=TStringListUTF8Fast.Create;
FPropertyOrder:=TStringList.Create;
FShowOnlyProperties:=TStringListUTF8Fast.Create;
FAliasPropertyNames:=TAliasStrings.Create;
end;
destructor TTICustomGrid.Destroy;
begin
ClearProperties;
FreeThenNil(FProperties);
FreeThenNil(FHeaderPropHook);
FreeThenNil(FHideProperties);
FreeThenNil(FPropertyOrder);
FreeThenNil(FShowOnlyProperties);
FreeThenNil(FAliasPropertyNames);
inherited Destroy;
end;
procedure TTICustomGrid.Loaded;
begin
inherited Loaded;
if tgsRebuildTIListNeeded in FTIStates then
ReloadTIList;
end;
procedure TTICustomGrid.ClearProperties;
var
i: Integer;
begin
if FProperties=nil then exit;
for i:=0 to FProperties.Count-1 do begin
TObject(FProperties[i]).Free;
FProperties[i]:=nil;
end;
FProperties.Clear;
Editor := nil;
FExtraBtnEditor := nil;
end;
procedure TTICustomGrid.DefaultDrawCell(aCol, aRow: Integer; var aRect: TRect;
aState: TGridDrawState);
var
OldDefaultDrawing: boolean;
PropEditor: TPropertyEditor;
IndependentEditor: boolean;
ObjectIndex: integer;
PropertyIndex: integer;
CellType: TTIGridCellType;
AliasPropName: String;
PropName: String;
begin
OldDefaultDrawing:=tgsDefaultDrawing in FTIStates;
Include(FTIStates,tgsDefaultDrawing);
try
PrepareCanvas(aCol, aRow, aState);
finally
if OldDefaultDrawing then
Include(FTIStates,tgsDefaultDrawing)
else
Exclude(FTIStates,tgsDefaultDrawing);
end;
if goColSpanning in Options then CalcCellExtent(acol, arow, aRect);
Canvas.FillRect(aRect);
//debugln('TTICustomGrid.DefaultDrawCell A Col=',dbgs(aCol),' Row=',dbgs(aRow));
MapCell(aCol,aRow,ObjectIndex,PropertyIndex,CellType);
if (PropertyIndex=0) then ;
if CellType in [tgctValue,tgctPropName] then begin
// fetch a property editor and draw cell
PropEditor:=nil;
GetCellEditor(aCol,aRow,PropEditor,IndependentEditor);
if PropEditor<>nil then begin
//debugln('TTICustomGrid.DefaultDrawCell B ',dbgsName(PropEditor),' ',PropEditor.GetName,' ',PropEditor.GetValue);
try
if gdFixed in aState then begin
if Properties[PropertyIndex].Title <> '' then
begin
WriteCellText(aRect,Properties[PropertyIndex].Title);
end
else begin
PropName:=PropEditor.GetName;
AliasPropName:=AliasPropertyNames.ValueToAlias(PropName);
if AliasPropName=PropName then begin
PropEditor.PropDrawName(Canvas,aRect,
GridStateToPropEditState(aState));
end else begin
WriteCellText(aRect,AliasPropName);
end;
end;
end else
PropEditor.PropDrawValue(Canvas,aRect,GridStateToPropEditState(aState));
finally
if IndependentEditor then PropEditor.Free;
end;
end;
end else if CellType=tgctObjectName then begin
DrawObjectName(ObjectIndex,aRect,aState);
end;
end;
procedure TTICustomGrid.DrawObjectName(Index: integer; const aRect: TRect;
aState: TGridDrawState);
function GetTIObjectName(ObjIndex: integer): string;
var
ACollectionItem: TCollectionItem;
AnObject: TPersistent;
begin
Result:='';
AnObject:=GetTIObject(ObjIndex);
if Assigned(OnGetObjectName) then begin
OnGetObjectName(Self,Index,AnObject,Result);
exit;
end;
if AnObject is TComponent then
Result:=TComponent(AnObject).Name
else if AnObject is TCollectionItem then begin
ACollectionItem:=TCollectionItem(AnObject);
Result:=ACollectionItem.DisplayName;
// the default DisplayName is the ClassName, which is not informative
if CompareText(Result,ACollectionItem.ClassName)=0 then Result:='';
end;
if Result='' then begin
if tgoStartIndexAtOne in TIOptions then
Result:=IntToStr(ObjIndex+1)
else
Result:=IntToStr(ObjIndex);
end;
end;
var
ObjectName: String;
begin
if aState=[] then ;
if (Index<0) or (Index>=TIObjectCount) then exit;
ObjectName:=GetTIObjectName(Index);
WriteCellText(aRect,ObjectName);
end;
procedure TTICustomGrid.GetCellEditor(aCol, aRow: integer;
out aPropEditor: TPropertyEditor; out aIndependentEditor: boolean);
var
ObjectIndex: Integer;
PropertyIndex: Integer;
EditorClass: TPropertyEditorClass;
GridProperty: TTIGridProperty;
NewEditor: TPropertyEditor;
CurObject: TPersistent;
CellType: TTIGridCellType;
begin
aPropEditor:=nil;
aIndependentEditor:=true;
MapCell(aCol,aRow,ObjectIndex,PropertyIndex,CellType);
if CellType in [tgctValue,tgctPropName] then begin
GridProperty:=Properties[PropertyIndex];
if CellType=tgctPropName then begin
aIndependentEditor:=false;
aPropEditor:=GridProperty.Editor;
end
else begin
CurObject:=GetTIObject(ObjectIndex);
if (CurObject<>nil) then begin
NewEditor:=nil;
try
EditorClass:=TPropertyEditorClass(GridProperty.Editor.ClassType);
NewEditor:=EditorClass.Create(FHeaderPropHook,1);
NewEditor.SetPropEntry(0,CurObject,GridProperty.PropInfo);
NewEditor.Initialize;
aPropEditor := NewEditor;
finally
if aPropEditor = nil then begin
try
NewEditor.Free;
except
end;
end;
end;
end;
end;
end;
end;
procedure TTICustomGrid.FreeCellEditor(PropEditor: TPropertyEditor);
var
Hook: TPropertyEditorHook;
begin
if PropEditor=nil then exit;
Hook:=PropEditor.PropertyHook;
try
PropEditor.free;
except
end;
try
Hook.free;
except
end;
end;
function TTICustomGrid.GridStateToPropEditState(GridState: TGridDrawState
): TPropEditDrawState;
begin
Result:=[];
if gdSelected in GridState then Include(Result,pedsSelected);
if gdFocused in GridState then Include(Result,pedsFocused);
end;
function TTICustomGrid.GetTIObject(Index: integer): TPersistent;
var
List: TList;
AnObject: TObject;
ACollection: TCollection;
FPList: TFPList;
begin
Result:=nil;
if (Index<0) or (Index>=TIObjectCount) then exit;
// use event
if Assigned(OnGetObject) then begin
Result:=nil;
OnGetObject(Self,Index,Result);
exit;
end;
// try standard lists: TCollection and TList
if ListObject is TCollection then begin
ACollection:=TCollection(ListObject);
if csDesigning in ComponentState then begin
try
Result:=ACollection.Items[Index];
except
end;
end else begin
Result:=ACollection.Items[Index];
end;
end else if ListObject is TList then begin
List:=TList(ListObject);
if csDesigning in ComponentState then begin
try
AnObject:=TObject(List[Index]);
Result:=AnObject as TPersistent;
except
end;
end else begin
AnObject:=TObject(List[Index]);
Result:=AnObject as TPersistent;
end;
end else if ListObject is TFPList then begin
FPList:=TFPList(ListObject);
if csDesigning in ComponentState then begin
try
AnObject:=TObject(FPList[Index]);
Result:=AnObject as TPersistent;
except
end;
end else begin
AnObject:=TObject(FPList[Index]);
Result:=AnObject as TPersistent;
end;
end;
end;
procedure TTICustomGrid.MapCell(aCol, aRow: integer; out ObjectIndex,
PropertyIndex: integer; out CellType: TTIGridCellType);
var
PropHeaderLines: LongInt;
ObjectHeaderLines: LongInt;
PropertyIndexValid: Boolean;
ObjectIndexValid: Boolean;
PropertyIndexInHeader: Boolean;
ObjectIndexInHeader: Boolean;
begin
if ListDirection=tldObjectsAsRows then begin
ObjectIndex:=aRow-FixedRows;
PropertyIndex:=aCol-FixedCols;
PropHeaderLines:=FixedRows;
ObjectHeaderLines:=FixedCols;
end else begin
ObjectIndex:=aCol-FixedCols;
PropertyIndex:=aRow-FixedRows;
PropHeaderLines:=FixedCols;
ObjectHeaderLines:=FixedRows;
end;
PropertyIndexValid:=(PropertyIndex>=0) and (PropertyIndex<PropertyCount);
ObjectIndexValid:=(ObjectIndex>=0) and (ObjectIndex<TIObjectCount);
// tldObjectsAsRows:
// PropertyIndex is a Col index, needs to be checked against ObjectHeaderLines (fixedCols)
// ObjectIndex is a Row index, needs to be checked against PropHeaderLines (fixedRows)
// tldObjectsAsColumns:
// PropertyIndex is a Row index, needs to be checked against ObjectHeaderLines (fixedRows)
// ObjectIndex is a Col index, needs to be checked against PropHeaderLines (fixedCols)
PropertyIndexInHeader:=(PropertyIndex<0)
and (PropertyIndex>=-ObjectHeaderLines);
ObjectIndexInHeader:=(ObjectIndex<0)
and (ObjectIndex>=-PropHeaderLines);
//debugln('TTICustomGrid.MapCell A ',dbgs(aCol),',',dbgs(aRow),' ',
// dbgs(PropertyIndex),',',dbgs(ObjectIndex),' ',
// dbgs(PropertyIndexValid),',',dbgs(ObjectIndexValid),
// ' ',dbgs(PropertyIndexInHeader),',',dbgs(ObjectIndexInHeader));
CellType:=tgctNone;
if PropertyIndexValid then begin
if ObjectIndexValid then
CellType:=tgctValue
else if ObjectIndexInHeader then begin
if ObjectIndex=-1 then
CellType:=tgctPropName
else if Objectindex=-2 then
CellType:=tgctPropNameAlt;
end;
end else if ObjectIndexValid then begin
if PropertyIndexInHeader then begin
if PropertyIndex=-1 then
CellType:=tgctObjectName
else if PropertyIndex=-2 then
CellType:=tgctObjectNameAlt;
end;
end else begin
if PropertyIndexInHeader and ObjectIndexInHeader then
CellType:=tgctCorner;
end;
end;
function TTICustomGrid.GetCurrentGridProperty: TTIGridProperty;
var
ObjectIndex: Integer;
PropertyIndex: Integer;
CellType: TTIGridCellType;
begin
Result:=nil;
MapCell(Col,Row,ObjectIndex,PropertyIndex,CellType);
if (ObjectIndex=0) or (PropertyIndex=0) then ;
if CellType=tgctValue then
Result:=Properties[PropertyIndex];
end;
function TTICustomGrid.IndexOfGridProperty(const PropName: string): integer;
begin
Result:=FProperties.Count-1;
while (Result>=0) and (CompareText(Properties[Result].PropName,PropName)<>0)
do dec(Result);
end;
function TTICustomGrid.FindGridProperty(const PropName: string): TTIGridProperty;
var
i: integer;
begin
i:=IndexOfGridProperty(PropName);
if i>=0 then
Result:=Properties[i]
else
Result:=nil;
end;
procedure TTICustomGrid.MoveProperty(FromID, ToID: integer);
begin
if FromID=ToID then exit;
EditorHide;
FProperties.Move(FromID,ToID);
Properties[FromID].FIndex:=FromID;
Properties[ToID].FIndex:=ToID;
Invalidate;
end;
{ TTIGridProperty }
procedure TTIGridProperty.EditorControlKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
Grid.DoEditorControlKeyUp(Sender,Key,Shift);
end;
procedure TTIGridProperty.SetTitle(const AValue: string);
begin
if FTitle=AValue then exit;
FTitle:=AValue;
end;
constructor TTIGridProperty.Create(TheGrid: TTICustomGrid;
TheEditor: TPropertyEditor; TheIndex: integer);
begin
FGrid:=TheGrid;
FEditor:=TheEditor;
FIndex:=TheIndex;
FTitle:=TheEditor.GetName;
end;
destructor TTIGridProperty.Destroy;
begin
FreeThenNil(FButtonEditorControl);
FreeThenNil(FEditorControl);
FreeThenNil(FEditor);
inherited Destroy;
end;
function TTIGridProperty.PropInfo: PPropInfo;
begin
Result:=Editor.GetPropInfo;
end;
function TTIGridProperty.GetEditorControl: TWinControl;
var
EditorClass: TWinControlClass;
Attr: TPropertyAttributes;
begin
if FEditorControl=nil then begin
FButtonEditorControl := nil;
if Assigned(Grid.OnCreateCellEditor) then
Grid.OnCreateCellEditor(Self,FEditorControl);
if FEditorControl=nil then begin
EditorClass:=FindTIGridControl(TPropertyEditorClass(Editor.ClassType));
if EditorClass=nil then begin
Attr:=Editor.GetAttributes;
if (paDialog in Attr) and (paReadOnly in Attr) then
EditorClass:=TTIButton
else if (paValueList in Attr) then begin
EditorClass:=TTIComboBox;
if (paDialog in Attr) then
FButtonEditorControl := TTIButton.Create(FGrid);
end else
EditorClass:=TTIEdit;
end;
FEditorControl:=EditorClass.Create(FGrid);
end;
FEditorControl.OnKeyUp:=@EditorControlKeyUp;
//FEditorControl.AutoSize:=false;
if Assigned(Grid.OnInitCellEditor) then
Grid.OnInitCellEditor(Self,FEditorControl);
if Assigned(Grid.OnInitCellEditor) and (FButtonEditorControl<>nil) then
Grid.OnInitCellEditor(Self,FButtonEditorControl);
end;
Result:=FEditorControl;
end;
function TTIGridProperty.GetButtonEditorControl: TWinControl;
begin
Result := FButtonEditorControl;
end;
function TTIGridProperty.PropName: string;
begin
Result:=PropInfo^.Name;
end;
initialization
RegisteredTIGridControls:=nil;
// property editor for TTICustomPropertyGrid.TIObject
RegisterPropertyEditor(ClassTypeInfo(TPersistent),
TTICustomPropertyGrid, 'TIObject', TTIObjectPropertyEditor);
finalization
FinalizeTIGrids;
end.