lazarus/lcl/valedit.pas
maxim a62dfe4241 Merged revision(s) 58773 #828f4e74ef, 59030 #2ddfb4072a from trunk:
TValueListEditor: implement sort. Based on a patch by Jesus Reyes. Issue #0034141.
........
TValueListEditor: fix DeleteRow (Issue #0034208)
........

git-svn-id: branches/fixes_1_8@59121 -
2018-09-21 22:43:11 +00:00

1432 lines
41 KiB
ObjectPascal

unit ValEdit;
{$mode objfpc}{$H+}
interface
uses
ContNrs, SysUtils, Classes, Variants,
LazUtf8, Controls, StdCtrls, Grids, LResources, Dialogs, LCLType;
type
TValueListEditor = class; // Forward declaration
TValueListStrings = class;
TEditStyle = (esSimple, esEllipsis, esPickList);
TVleSortCol = (colKey, colValue);
{ TItemProp }
TItemProp = class(TPersistent)
private
FGrid: TValueListEditor;
FEditMask: string;
FEditStyle: TEditStyle;
FPickList: TStrings;
FMaxLength: Integer;
FReadOnly: Boolean;
FKeyDesc: string;
function GetPickList: TStrings;
procedure PickListChange(Sender: TObject);
procedure SetEditMask(const AValue: string);
procedure SetMaxLength(const AValue: Integer);
procedure SetReadOnly(const AValue: Boolean);
procedure SetEditStyle(const AValue: TEditStyle);
procedure SetPickList(const AValue: TStrings);
procedure SetKeyDesc(const AValue: string);
protected
procedure AssignTo(Dest: TPersistent); override;
public
constructor Create(AOwner: TValueListEditor);
destructor Destroy; override;
// function HasPickList: Boolean;
published
property EditMask: string read FEditMask write SetEditMask;
property EditStyle: TEditStyle read FEditStyle write SetEditStyle;
property KeyDesc: string read FKeyDesc write SetKeyDesc;
property PickList: TStrings read GetPickList write SetPickList;
property MaxLength: Integer read FMaxLength write SetMaxLength;
property ReadOnly: Boolean read FReadOnly write SetReadOnly;
end;
{ TItemPropList }
TItemPropList = class
private
FList: TFPObjectList;
FStrings: TValueListStrings;
function GetCount: Integer;
function GetItem(Index: Integer): TItemProp;
procedure SetItem(Index: Integer; AValue: TItemProp);
protected
public
procedure Add(AValue: TItemProp);
procedure Assign(Source: TItemPropList);
procedure Clear;
procedure Delete(Index: Integer);
procedure Exchange(Index1, Index2: Integer);
procedure Insert(Index: Integer; AValue: TItemProp);
public
constructor Create(AOwner: TValueListStrings);
destructor Destroy; override;
public
property Count: Integer read GetCount;
property Items[Index: Integer]: TItemProp read GetItem write SetItem; default;
end;
{ TValueListStrings }
TValueListStrings = class(TStringList)
private
FGrid: TValueListEditor;
FItemProps: TItemPropList;
function GetItemProp(const AKeyOrIndex: Variant): TItemProp;
procedure QuickSortStringsAndItemProps(L, R: Integer; CompareFn: TStringListSortCompare);
function CanHideShowingEditorAtIndex(Index: Integer): Boolean;
protected
procedure InsertItem(Index: Integer; const S: string; AObject: TObject); override;
procedure InsertItem(Index: Integer; const S: string); override;
procedure Put(Index: Integer; const S: String); override;
public
constructor Create(AOwner: TValueListEditor);
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure Clear; override;
procedure CustomSort(Compare: TStringListSortCompare); override;
procedure Delete(Index: Integer); override;
procedure Exchange(Index1, Index2: Integer); override;
end;
TKeyValuePair = record
Key, Value: String;
end;
TDisplayOption = (doColumnTitles, doAutoColResize, doKeyColFixed);
TDisplayOptions = set of TDisplayOption;
TKeyOption = (keyEdit, keyAdd, keyDelete, keyUnique);
TKeyOptions = set of TKeyOption;
TGetPickListEvent = procedure(Sender: TObject; const KeyName: string;
Values: TStrings) of object;
TOnValidateEvent = procedure(Sender: TObject; ACol, ARow: Longint;
const KeyName, KeyValue: string) of object;
{ TValueListEditor }
TValueListEditor = class(TCustomStringGrid)
private
FTitleCaptions: TStrings;
FStrings: TValueListStrings;
FKeyOptions: TKeyOptions;
FDisplayOptions: TDisplayOptions;
FDropDownRows: Integer;
FOnGetPickList: TGetPickListEvent;
FOnStringsChange: TNotifyEvent;
FOnStringsChanging: TNotifyEvent;
FOnValidate: TOnValidateEvent;
FRowTextOnEnter: TKeyValuePair;
FLastEditedRow: Integer;
FUpdatingKeyOptions: Boolean;
function GetItemProp(const AKeyOrIndex: Variant): TItemProp;
procedure SetItemProp(const AKeyOrIndex: Variant; AValue: TItemProp);
procedure StringsChange(Sender: TObject);
procedure StringsChanging(Sender: TObject);
function GetOptions: TGridOptions;
function GetKey(Index: Integer): string;
function GetValue(const Key: string): string;
procedure SetDisplayOptions(const AValue: TDisplayOptions);
procedure SetDropDownRows(const AValue: Integer);
procedure SetKeyOptions(AValue: TKeyOptions);
procedure SetKey(Index: Integer; const Value: string);
procedure SetValue(const Key: string; AValue: string);
procedure SetOptions(AValue: TGridOptions);
procedure SetStrings(const AValue: TValueListStrings);
procedure SetTitleCaptions(const AValue: TStrings);
protected
class procedure WSRegisterClass; override;
procedure SetFixedCols(const AValue: Integer); override;
procedure ShowColumnTitles;
procedure AdjustRowCount; virtual;
procedure ColRowExchanged(IsColumn: Boolean; index, WithIndex: Integer); override;
procedure ColRowDeleted(IsColumn: Boolean; index: Integer); override;
procedure DefineCellsProperty(Filer: TFiler); override;
procedure InvalidateCachedRow;
procedure GetAutoFillColumnInfo(const Index: Integer; var aMin,aMax,aPriority: Integer); override;
function GetEditText(ACol, ARow: Integer): string; override;
function GetCells(ACol, ARow: Integer): string; override;
function GetDefaultEditor(Column: Integer): TWinControl; override;
function GetRowCount: Integer;
procedure KeyDown(var Key : Word; Shift : TShiftState); override;
procedure ResetDefaultColWidths; override;
procedure SetCells(ACol, ARow: Integer; const AValue: string); override;
procedure SetEditText(ACol, ARow: Longint; const Value: string); override;
procedure SetFixedRows(const AValue: Integer); override;
procedure SetRowCount(AValue: Integer);
procedure Sort(ColSorting: Boolean; index,IndxFrom,IndxTo:Integer); override;
procedure TitlesChanged(Sender: TObject);
function ValidateEntry(const ACol,ARow:Integer; const OldValue:string; var NewValue:string): boolean; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Clear;
procedure DeleteColRow(IsColumn: Boolean; index: Integer);
procedure DeleteRow(Index: Integer); override;
procedure DeleteCol(Index: Integer); override;
function FindRow(const KeyName: string; out aRow: Integer): Boolean;
procedure InsertColRow(IsColumn: boolean; index: integer);
function InsertRow(const KeyName, Value: string; Append: Boolean): Integer;
procedure InsertRowWithValues(Index: Integer; Values: array of String);
procedure ExchangeColRow(IsColumn: Boolean; index, WithIndex: Integer); override;
function IsEmptyRow: Boolean; {Delphi compatible function}
function IsEmptyRow(aRow: Integer): Boolean; {This for makes more sense to me}
procedure MoveColRow(IsColumn: Boolean; FromIndex, ToIndex: Integer);
function RestoreCurrentRow: Boolean;
procedure Sort(Index, IndxFrom, IndxTo: Integer);
procedure Sort(ACol: TVleSortCol = colKey);
property Modified;
property Keys[Index: Integer]: string read GetKey write SetKey;
property Values[const Key: string]: string read GetValue write SetValue;
property ItemProps[const AKeyOrIndex: Variant]: TItemProp read GetItemProp write SetItemProp;
published
// Same as in TStringGrid
property Align;
property AlternateColor;
property Anchors;
property AutoAdvance;
property AutoEdit;
property BiDiMode;
property BorderSpacing;
property BorderStyle;
property Color;
property Constraints;
property DefaultColWidth;
property DefaultDrawing;
property DefaultRowHeight;
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property ExtendedSelect;
property FixedColor;
property FixedCols;
property Flat;
property Font;
property GridLineWidth;
property HeaderHotZones;
property HeaderPushZones;
property MouseWheelOption;
property ParentBiDiMode;
property ParentColor default false;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property RowCount: Integer read GetRowCount write SetRowCount;
property ScrollBars;
property ShowHint;
property TabOrder;
property TabStop;
property TitleFont;
property TitleImageList;
property TitleStyle;
property UseXORFeatures;
property Visible;
property VisibleColCount;
property VisibleRowCount;
property OnBeforeSelection;
property OnButtonClick;
property OnChangeBounds;
property OnCheckboxToggled;
property OnClick;
property OnColRowDeleted;
property OnColRowExchanged;
property OnColRowInserted;
property OnColRowMoved;
property OnCompareCells;
property OnContextPopup;
property OnDragDrop;
property OnDragOver;
property OnDblClick;
property OnDrawCell;
property OnEditButtonClick; deprecated;
property OnEditingDone;
property OnEndDock;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnGetEditMask;
property OnGetEditText;
property OnHeaderClick;
property OnHeaderSized;
property OnHeaderSizing;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnPickListSelect;
property OnPrepareCanvas;
property OnResize;
property OnSelectEditor;
property OnSelection;
property OnSelectCell;
property OnSetEditText;
property OnShowHint;
property OnStartDock;
property OnStartDrag;
property OnTopLeftChanged;
property OnUserCheckboxBitmap;
property OnUTF8KeyPress;
property OnValidateEntry;
// Compatible with Delphi TValueListEditor:
property DisplayOptions: TDisplayOptions read FDisplayOptions
write SetDisplayOptions default [doColumnTitles, doAutoColResize, doKeyColFixed];
property DoubleBuffered;
property DropDownRows: Integer read FDropDownRows write SetDropDownRows default 8;
property KeyOptions: TKeyOptions read FKeyOptions write SetKeyOptions default [];
property Options: TGridOptions read GetOptions write SetOptions default
[goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing,
goEditing, goAlwaysShowEditor, goThumbTracking];
property Strings: TValueListStrings read FStrings write SetStrings;
property TitleCaptions: TStrings read FTitleCaptions write SetTitleCaptions;
property OnGetPickList: TGetPickListEvent read FOnGetPickList write FOnGetPickList;
property OnStringsChange: TNotifyEvent read FOnStringsChange write FOnStringsChange;
property OnStringsChanging: TNotifyEvent read FOnStringsChanging write FOnStringsChanging;
property OnValidate: TOnValidateEvent read FOnValidate write FOnValidate;
end;
const
//ToDo: Make this a resourcestring in lclstrconsts unit, once we are satisfied with the implementation of validating
rsVLEDuplicateKey = 'Duplicate Key:'+LineEnding+'A key with name "%s" already exists at column %d';
//ToDo: Make this a resourcestring in lclstrconsts unit, once we are satisfied with ShowColumnTitles
rsVLEKey = 'Key';
rsVLEValue = 'Value';
rsVLEInvalidRowColOperation = 'The operation %s is not allowed on a TValueListEditor%s.';
procedure Register;
implementation
type
TCompositeCellEditorAccess = class(TCompositeCellEditor);
{ TItemProp }
constructor TItemProp.Create(AOwner: TValueListEditor);
begin
inherited Create;
FGrid := AOwner;
end;
destructor TItemProp.Destroy;
begin
FPickList.Free;
inherited Destroy;
end;
function TItemProp.GetPickList: TStrings;
begin
if FPickList = Nil then
begin
FPickList := TStringList.Create;
TStringList(FPickList).OnChange := @PickListChange;
end;
Result := FPickList;
end;
procedure TItemProp.PickListChange(Sender: TObject);
begin
if PickList.Count > 0 then begin
if EditStyle = esSimple then
EditStyle := esPickList;
end
else begin
if EditStyle = esPickList then
EditStyle := esSimple;
end;
end;
procedure TItemProp.SetEditMask(const AValue: string);
begin
FEditMask := AValue;
with FGrid do
if EditorMode and (FStrings.UpdateCount = 0) then
InvalidateCell(Col, Row);
end;
procedure TItemProp.SetMaxLength(const AValue: Integer);
begin
FMaxLength := AValue;
with FGrid do
if EditorMode and (FStrings.UpdateCount = 0) then
InvalidateCell(Col, Row);
end;
procedure TItemProp.SetReadOnly(const AValue: Boolean);
begin
FReadOnly := AValue;
with FGrid do
if EditorMode and (FStrings.UpdateCount = 0) then
InvalidateCell(Col, Row);
end;
procedure TItemProp.SetEditStyle(const AValue: TEditStyle);
begin
FEditStyle := AValue;
with FGrid do
if EditorMode and (FStrings.UpdateCount = 0) then
InvalidateCell(Col, Row);
end;
procedure TItemProp.SetPickList(const AValue: TStrings);
begin
GetPickList.Assign(AValue);
with FGrid do
if EditorMode and (FStrings.UpdateCount = 0) then
InvalidateCell(Col, Row);
end;
procedure TItemProp.SetKeyDesc(const AValue: string);
begin
FKeyDesc := AValue;
end;
procedure TItemProp.AssignTo(Dest: TPersistent);
begin
if not (Dest is TItemProp) then
inherited AssignTo(Dest)
else
begin
TItemProp(Dest).EditMask := Self.EditMask;
TItemProp(Dest).EditStyle := Self.EditStyle;
TItemProp(Dest).KeyDesc := Self.KeyDesc;
TItemProp(Dest).PickList.Assign(Self.PickList);
TItemProp(Dest).MaxLength := Self.MaxLength;
TItemProp(Dest).ReadOnly := Self.ReadOnly;
end;
end;
{ TItemPropList }
function TItemPropList.GetItem(Index: Integer): TItemProp;
begin
Result := TItemProp(FList.Items[Index]);
end;
function TItemPropList.GetCount: Integer;
begin
Result := FList.Count;
end;
procedure TItemPropList.SetItem(Index: Integer; AValue: TItemProp);
begin
FList.Items[Index] := AValue;
end;
procedure TItemPropList.Insert(Index: Integer; AValue: TItemProp);
begin
FList.Insert(Index, AValue);
end;
procedure TItemPropList.Add(AValue: TItemProp);
begin
FList.Add(AValue);
end;
procedure TItemPropList.Assign(Source: TItemPropList);
var
Index: Integer;
Prop: TItemProp;
begin
Clear;
if not Assigned(Source) then Exit;
for Index := 0 to Source.Count - 1 do
begin
Prop := TItemProp.Create(FStrings.FGrid);
Prop.Assign(Source.Items[Index]);
Add(Prop);
end;
end;
procedure TItemPropList.Delete(Index: Integer);
begin
FList.Delete(Index);
end;
procedure TItemPropList.Exchange(Index1, Index2: Integer);
begin
FList.Exchange(Index1, index2);
end;
procedure TItemPropList.Clear;
begin
FList.Clear;
end;
constructor TItemPropList.Create(AOwner: TValueListStrings);
begin
FStrings := AOwner;
FList := TFPObjectList.Create(True);
end;
destructor TItemPropList.Destroy;
begin
FList.Free;
inherited Destroy;
end;
{ TValueListStrings }
procedure TValueListStrings.InsertItem(Index: Integer; const S: string; AObject: TObject);
var
MustHideShowingEditor: Boolean;
begin
// ToDo: Check validity of key
//debugln('TValueListStrings.InsertItem: Index = ',dbgs(index),' S = "',S,'" AObject = ',dbgs(aobject));
FGrid.InvalidateCachedRow;
MustHideShowingEditor := CanHideShowingEditorAtIndex(Index);
if MustHideShowingEditor then FGrid.Options := FGrid.Options - [goAlwaysShowEditor];
inherited InsertItem(Index, S, AObject);
FItemProps.Insert(Index, TItemProp.Create(FGrid));
//only restore this _after_ FItemProps is updated!
if MustHideShowingEditor then FGrid.Options := FGrid.Options + [goAlwaysShowEditor];
end;
procedure TValueListStrings.InsertItem(Index: Integer; const S: string);
begin
InsertItem(Index, S, nil);
end;
procedure TValueListStrings.Put(Index: Integer; const S: String);
var
MustHideShowingEditor: Boolean;
begin
// ToDo: Check validity of key
MustHideShowingEditor := CanHideShowingEditorAtIndex(Index);
//debugln('TValueListStrings.Put: MustHideShowingEditor=',DbgS(MustHideShowingEditor));
if MustHideShowingEditor then FGrid.Options := FGrid.Options - [goAlwaysShowEditor];
inherited Put(Index, S);
if MustHideShowingEditor then FGrid.Options := FGrid.Options + [goAlwaysShowEditor];
end;
constructor TValueListStrings.Create(AOwner: TValueListEditor);
begin
inherited Create;
FGrid := AOwner;
FItemProps := TItemPropList.Create(Self);
end;
destructor TValueListStrings.Destroy;
begin
FItemProps.Free;
inherited Destroy;
end;
procedure TValueListStrings.Assign(Source: TPersistent);
begin
FGrid.InvalidateCachedRow;
Clear; //if this is not done, and a TValueListEditor.Sort() is done and then later a Strings.Assign, an exception will occur.
inherited Assign(Source);
if (Source is TValueListStrings) then
FItemProps.Assign(TValueListStrings(Source).FItemProps);
end;
procedure TValueListStrings.Clear;
var
IsShowingEditor: Boolean;
begin
FGrid.InvalidateCachedRow;
IsShowingEditor := goAlwaysShowEditor in FGrid.Options;
if IsShowingEditor then FGrid.Options := FGrid.Options - [goAlwaysShowEditor];
inherited Clear;
FItemProps.Clear;
if IsShowingEditor then FGrid.Options := FGrid.Options + [goAlwaysShowEditor];
end;
{
Duplicates the functionality of TStringList.QuickSort, but also
sorts the ItemProps.
}
procedure TValueListStrings.QuickSortStringsAndItemProps(L, R: Integer;
CompareFn: TStringListSortCompare);
var
Pivot, vL, vR: Integer;
begin
if R - L <= 1 then
begin // a little bit of time saver
if L < R then
if CompareFn(Self, L, R) > 0 then
//Exchange also exchanges FItemProps
Exchange(L, R);
Exit;
end;
vL := L;
vR := R;
Pivot := L + Random(R - L); // they say random is best
while vL < vR do
begin
while (vL < Pivot) and (CompareFn(Self, vL, Pivot) <= 0) do
Inc(vL);
while (vR > Pivot) and (CompareFn(Self, vR, Pivot) > 0) do
Dec(vR);
//Exchange also exchanges FItemProps
Exchange(vL, vR);
if Pivot = vL then // swap pivot if we just hit it from one side
Pivot := vR
else if Pivot = vR then
Pivot := vL;
end;
if Pivot - 1 >= L then
QuickSortStringsAndItemProps(L, Pivot - 1, CompareFn);
if Pivot + 1 <= R then
QuickSortStringsAndItemProps(Pivot + 1, R, CompareFn);
end;
function TValueListStrings.CanHideShowingEditorAtIndex(Index: Integer): Boolean;
var
IndexToRow: Integer;
WC: TWinControl;
EditorHasFocus: Boolean;
begin
IndexToRow := Index + FGrid.FixedRows;
if (FGrid.Editor is TCompositeCellEditor) then
begin
WC := TCompositeCellEditorAccess(FGrid.Editor).GetActiveControl;
if (WC is TCustomEdit) then
EditorHasFocus := TCustomEdit(WC).Focused
else
EditorHasFocus := False;
end
else
EditorHasFocus := Assigned(FGrid.Editor) and FGrid.Editor.Focused;
//debugln('CanHideShowingEditor:');
//debugln(' Assigned(FGrid.Editor) = ',DbgS(Assigned(FGrid.Editor)));
//debugln(' (goAlwaysShowEditor in FGrid.Options) = ',DbgS(goAlwaysShowEditor in FGrid.Options));
//if Assigned(FGrid.Editor) then
// debugln(' FGrid.Editor.Visible = ',DbgS(FGrid.Editor.Visible));
//debugln(' IndexToRow = ',DbgS(IndextoRow));
//debugln(' Count = ',DbgS(Count));
//debugln(' EditorHasFocus = ',DbgS(EditorHasFocus));
Result := Assigned(FGrid.Editor) and
(goAlwaysShowEditor in FGrid.Options) and
FGrid.Editor.Visible and
((IndexToRow = FGrid.Row) or (Count = 0)) and //if Count = 0 we still have an editable row
//if editor is Focussed, we are editing a cell, so we cannot hide!
(not EditorHasFocus);
//debugln('CanHideShowingEditor: Result = ',DbgS(Result));
end;
procedure TValueListStrings.CustomSort(Compare: TStringListSortCompare);
{
Re-implement it, because we need it to call our own QuickSortStringsAndItemProps
and so we cannot use inherited CustomSort
Use BeginUpdate/EndUpdate to avoid numerous Changing/Changed calls
}
begin
If not Sorted and (Count>1) then
begin
try
BeginUpdate;
FGrid.InvalidateCachedRow;
QuickSortStringsAndItemProps(0,Count-1, Compare);
finally
EndUpdate;
end;
end;
end;
procedure TValueListStrings.Delete(Index: Integer);
var
IsShowingEditor: Boolean;
begin
FGrid.InvalidateCachedRow;
IsShowingEditor := CanHideShowingEditorAtIndex(Index);
if IsShowingEditor then FGrid.Options := FGrid.Options - [goAlwaysShowEditor];
inherited Delete(Index);
// Delete also ItemProps
FItemProps.Delete(Index);
//only restore this _after_ FItemProps is updated!
if IsShowingEditor then FGrid.Options := FGrid.Options + [goAlwaysShowEditor];
end;
procedure TValueListStrings.Exchange(Index1, Index2: Integer);
var
MustHideShowingEditor: Boolean;
begin
FGrid.InvalidateCachedRow;
MustHideShowingEditor := CanHideShowingEditorAtIndex(Index1) or CanHideShowingEditorAtIndex(Index2);
if MustHideShowingEditor then FGrid.Options := FGrid.Options - [goAlwaysShowEditor];
inherited Exchange(Index1, Index2);
FItemProps.Exchange(Index1, Index2);
if MustHideShowingEditor then FGrid.Options := FGrid.Options + [goAlwaysShowEditor];
end;
function TValueListStrings.GetItemProp(const AKeyOrIndex: Variant): TItemProp;
var
i: Integer;
s: string;
begin
Result := Nil;
if (Count > 0) and (UpdateCount = 0) then
begin
if VarIsOrdinal(AKeyOrIndex) then
i := AKeyOrIndex
else begin
s := AKeyOrIndex;
i := IndexOfName(s);
if i = -1 then
raise Exception.Create('TValueListStrings.GetItemProp: Key not found: '+s);
end;
if i < FItemProps.Count then
begin
Result := FItemProps.Items[i];
if not Assigned(Result) then
Raise Exception.Create(Format('TValueListStrings.GetItemProp: Index=%d Result=Nil',[i]));
end;
end;
end;
{ TValueListEditor }
constructor TValueListEditor.Create(AOwner: TComponent);
begin
//need FStrings before inherited Create, because they are needed in overridden SelectEditor
FStrings := TValueListStrings.Create(Self);
FStrings.NameValueSeparator := '=';
FTitleCaptions := TStringList.Create;
inherited Create(AOwner);
FStrings.OnChange := @StringsChange;
FStrings.OnChanging := @StringsChanging;
TStringList(FTitleCaptions).OnChange := @TitlesChanged;
//Don't use Columns.Add, it interferes with setting FixedCols := 1 (it will then insert an extra column)
{
with Columns.Add do
Title.Caption := 'Key';
with Columns.Add do begin
Title.Caption := 'Value';
DropDownRows := 8;
end;
}
ColCount:=2;
{inherited} RowCount := 2;
FixedCols := 0;
// DefaultColWidth := 150;
// DefaultRowHeight := 18;
// Width := 306;
// Height := 300;
Options := [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine,
goColSizing, goEditing, goAlwaysShowEditor, goThumbTracking];
FDisplayOptions := [doColumnTitles, doAutoColResize, doKeyColFixed];
Col := 1;
FLastEditedRow := -1;
FDropDownRows := 8;
ShowColumnTitles;
AutoFillColumns := true;
end;
destructor TValueListEditor.Destroy;
begin
FTitleCaptions.Free;
FStrings.Free;
inherited Destroy;
end;
procedure TValueListEditor.Clear;
begin
Strings.Clear;
end;
procedure TValueListEditor.DeleteColRow(IsColumn: Boolean; index: Integer);
begin
if not IsColumn then
DeleteRow(Index)
else
DeleteCol(Index);
end;
procedure TValueListEditor.DeleteRow(Index: Integer);
begin
//If we have only one row, it may be empty and we cannot remove
if not ((Index - FixedRows = 0) and (Strings.Count = 0)) then inherited DeleteRow(Index) ;
end;
procedure TValueListEditor.DeleteCol(Index: Integer);
begin
Raise EGridException.CreateFmt(rsVLEInvalidRowColOperation,['DeleteCol','']);
end;
function TValueListEditor.FindRow(const KeyName: string; out aRow: Integer): Boolean;
var
Index: Integer;
begin
Index := Strings.IndexOfName(KeyName);
Result := (Index > -1);
if Result then aRow := Index + FixedRows;
end;
procedure TValueListEditor.InsertColRow(IsColumn: boolean; index: integer);
begin
if not IsColumn then
Strings.InsertItem(Index - FixedRows,'')
else
Raise EGridException.CreateFmt(rsVLEInvalidRowColOperation,['InsertColRow',' on columns']);
end;
function TValueListEditor.InsertRow(const KeyName, Value: string; Append: Boolean): Integer;
var
NewInd, NewRow: Integer;
Line: String;
begin
if not ((KeyName = '') and (Value = '')) then
Line := KeyName + Strings.NameValueSeparator + Value
else
Line := '';
if (Row > Strings.Count) or ((Row - FixedRows) >= Strings.Count)
or (Cells[0, Row] <> '') or (Cells[1, Row] <> '') then
begin // Add a new Key=Value pair
Strings.BeginUpdate;
try
if Append then
begin
if (Strings.Count = 0) then //empty grid
NewInd := 0
else
NewInd := Row - FixedRows + 1 //append after current row
end
else
NewInd := Row - FixedRows; //insert it at current row
Strings.InsertItem(NewInd, Line, Nil);
finally
Strings.EndUpdate;
end;
end
else begin // Use an existing row, just update the Key and Value.
Cells[0, Row] := KeyName;
Cells[1, Row] := Value;
NewInd := Row - FixedRows;
end;
Result := NewInd;
NewRow := NewInd + FixedRows;
if (NewRow <> Row) then Row := NewRow;
end;
procedure TValueListEditor.InsertRowWithValues(Index: Integer; Values: array of String);
var
AKey, AValue: String;
begin
AKey := '';
AValue := '';
if (Length(Values) > 1) then
begin
AKey := Values[0];
AValue := Values[1];
end
else if (Length(Values) = 1) then
AKey := Values[0];
Strings.InsertItem(Index, AKey + Strings.NameValueSeparator + AValue);
end;
procedure TValueListEditor.ExchangeColRow(IsColumn: Boolean; index, WithIndex: Integer);
begin
if not IsColumn then
inherited ExchangeColRow(IsColumn, index, WithIndex)
else
Raise EGridException.CreateFmt(rsVLEInvalidRowColOperation,['ExchangeColRow',' on columns']);
end;
function TValueListEditor.IsEmptyRow: Boolean;
{As per help text on Embarcadero: the function does not have a parameter for row, so assume current one?}
begin
Result := IsEmptyRow(Row);
end;
function TValueListEditor.IsEmptyRow(aRow: Integer): Boolean;
begin
if (Strings.Count = 0) and (aRow - FixedRows = 0) then
//special case: we have just one row, and it is empty
Result := True
else if (aRow = 0) and (FixedRows = 0) then
Result := ((inherited GetCells(0,0)) = EmptyStr) and ((inherited GetCells(1,0)) = EmptyStr)
else
Result := Strings.Strings[aRow - FixedRows] = EmptyStr;
end;
procedure TValueListEditor.MoveColRow(IsColumn: Boolean; FromIndex,
ToIndex: Integer);
var
Line: String;
begin
if not IsColumn then
begin
try
Strings.BeginUpdate;
Line := Strings.Strings[FromIndex - FixedRows];
Strings.Delete(FromIndex - FixedRows);
Strings.InsertItem(ToIndex - FixedRows, Line);
finally
Strings.EndUpdate;
end;
end
else
Raise EGridException.CreateFmt(rsVLEInvalidRowColOperation,['MoveColRow',' on columns']);
end;
function TValueListEditor.RestoreCurrentRow: Boolean;
begin
//DbgOut('RestoreCurrentRow: Row=',DbgS(Row),' FLastEditedRow=',DbgS(FLastEditedRow),' SavedKey=',FRowTextOnEnter.Key,' SavedValue=',FRowTextOnEnter.Value);
Result := False;
if (Row = FLastEditedRow) and Assigned(Editor) and Editor.Focused then
begin
if (Cells[0,Row] <> FRowTextOnEnter.Key) or (Cells[1,Row] <> FRowTextOnEnter.Value) then
begin
try
EditorHide;
if (Cells[0,Row] <> FRowTextOnEnter.Key) then Cells[0,Row] := FRowTextOnEnter.Key;
if (Cells[1,Row] <> FRowTextOnEnter.Value) then Cells[1,Row] := FRowTextOnEnter.Value;
finally
EditorShow(True);
end;
Result := True;
end;
end;
end;
procedure TValueListEditor.Sort(ACol: TVleSortCol = colKey);
begin
SortColRow(True, Ord(ACol));
end;
procedure TValueListEditor.Sort(Index, IndxFrom, IndxTo: Integer);
begin
Sort(True, Index, IndxFrom, IndxTo);
end;
procedure TValueListEditor.StringsChange(Sender: TObject);
begin
Modified := True;
AdjustRowCount;
Invalidate;
if Assigned(OnStringsChange) then
OnStringsChange(Self);
end;
procedure TValueListEditor.StringsChanging(Sender: TObject);
begin
if Assigned(OnStringsChanging) then
OnStringsChanging(Self);
end;
procedure TValueListEditor.SetFixedCols(const AValue: Integer);
begin
if (AValue in [0,1]) then
inherited SetFixedCols(AValue);
end;
procedure TValueListEditor.SetFixedRows(const AValue: Integer);
begin
if AValue in [0,1] then begin // No other values are allowed
if AValue = 0 then // Typically DisplayOptions are changed directly
DisplayOptions := DisplayOptions - [doColumnTitles]
else
DisplayOptions := DisplayOptions + [doColumnTitles]
end;
end;
function TValueListEditor.GetItemProp(const AKeyOrIndex: Variant): TItemProp;
begin
Result := FStrings.GetItemProp(AKeyOrIndex);
end;
procedure TValueListEditor.SetItemProp(const AKeyOrIndex: Variant; AValue: TItemProp);
begin
FStrings.GetItemProp(AKeyOrIndex).Assign(AValue);
end;
function TValueListEditor.GetOptions: TGridOptions;
begin
Result := inherited Options;
end;
procedure TValueListEditor.SetDisplayOptions(const AValue: TDisplayOptions);
// Set number of fixed rows to 1 if titles are shown (based on DisplayOptions).
// Set the local options value, then Adjust Column Widths and Refresh the display.
begin
BeginUpdate;
if (doColumnTitles in DisplayOptions) <> (doColumnTitles in AValue) then
if doColumnTitles in AValue then begin
if RowCount < 2 then
{inherited} RowCount := 2;
inherited SetFixedRows(1);// don't do FixedRows := 1 here, it wil cause infinite recursion (Issue 0029993)
end else
inherited SetFixedRows(0);
if (doAutoColResize in DisplayOptions) <> (doAutoColResize in AValue) then
AutoFillColumns := (doAutoColResize in AValue);
FDisplayOptions := AValue;
ShowColumnTitles;
AdjustRowCount;
EndUpdate;
end;
procedure TValueListEditor.SetDropDownRows(const AValue: Integer);
begin
FDropDownRows := AValue;
// ToDo: If edit list for inplace editing is implemented, set its handler, too.
end;
procedure TValueListEditor.SetKeyOptions(AValue: TKeyOptions);
begin
FUpdatingKeyOptions := True;
// KeyAdd requires KeyEdit, KeyAdd oddly enough does not according to Delphi specs
if KeyAdd in AValue then
Include(AValue, keyEdit);
FKeyOptions := AValue;
if (KeyAdd in FKeyOptions) then
Options := Options + [goAutoAddRows]
else
Options := Options - [goAutoAddRows];
FUpdatingKeyOptions := False;
end;
procedure TValueListEditor.SetOptions(AValue: TGridOptions);
begin
//cannot allow goColMoving
if goColMoving in AValue then
Exclude(AValue, goColMoving);
//temporarily disable this, it causes crashes
if (goAutoAddRowsSkipContentCheck in AValue) then
Exclude(AValue, goAutoAddRowsSkipContentCheck);
inherited Options := AValue;
// Enable also the required KeyOptions for goAutoAddRows
if not FUpdatingKeyOptions and not (csLoading in ComponentState)
and (goAutoAddRows in AValue) then
KeyOptions := KeyOptions + [keyEdit, keyAdd];
end;
procedure TValueListEditor.SetStrings(const AValue: TValueListStrings);
begin
FStrings.Assign(AValue);
end;
procedure TValueListEditor.SetTitleCaptions(const AValue: TStrings);
begin
FTitleCaptions.Assign(AValue);
end;
function TValueListEditor.GetKey(Index: Integer): string;
begin
Result:=Cells[0,Index];
end;
procedure TValueListEditor.SetKey(Index: Integer; const Value: string);
begin
Cells[0,Index]:=Value;
end;
function TValueListEditor.GetValue(const Key: string): string;
var
I: Integer;
begin
Result := '';
I := Strings.IndexOfName(Key);
if I > -1 then begin
Inc(I, FixedRows);
Result:=Cells[1,I];
end;
end;
procedure TValueListEditor.SetValue(const Key: string; AValue: string);
var
I: Integer;
begin
I := Strings.IndexOfName(Key);
if I > -1 then begin
Inc(I, FixedRows);
Cells[1,I]:=AValue;
end
else begin
Insert(Strings.NameValueSeparator, AValue, 1);
Insert(Key, AValue, 1);
Strings.Add(AValue);
end;
end;
procedure TValueListEditor.ShowColumnTitles;
var
KeyCap, ValCap: String;
begin
if (doColumnTitles in DisplayOptions) then
begin
KeyCap := rsVLEKey;
ValCap := rsVLEValue;
if (TitleCaptions.Count > 0) then KeyCap := TitleCaptions[0];
if (TitleCaptions.Count > 1) then ValCap := TitleCaptions[1];
//Columns[0].Title.Caption := KeyCap;
//Columns[1].Title.Caption := ValCap;
//or:
Cells[0,0] := KeyCap;
Cells[1,0] := ValCap;
end;
end;
procedure TValueListEditor.AdjustRowCount;
// Change the number of rows based on the number of items in Strings collection.
// Sets Row and RowCount of parent TCustomDrawGrid class.
var
NewC: Integer;
begin
NewC:=FixedRows+1;
if Strings.Count>0 then
NewC:=Strings.Count+FixedRows;
if NewC<>RowCount then
begin
if NewC<Row then
Row:=NewC-1;
if Row = 0 then
if doColumnTitles in DisplayOptions then
Row:=1;
inherited RowCount:=NewC;
end;
end;
procedure TValueListEditor.ColRowExchanged(IsColumn: Boolean; index,
WithIndex: Integer);
begin
Strings.Exchange(Index - FixedRows, WithIndex - FixedRows);
inherited ColRowExchanged(IsColumn, index, WithIndex);
end;
procedure TValueListEditor.ColRowDeleted(IsColumn: Boolean; index: Integer);
begin
EditorMode := False;
Strings.Delete(Index-FixedRows);
inherited ColRowDeleted(IsColumn, index);
end;
procedure TValueListEditor.DefineCellsProperty(Filer: TFiler);
begin
end;
procedure TValueListEditor.InvalidateCachedRow;
begin
if (Strings.Count = 0) then
begin
FLastEditedRow := FixedRows;
FRowTextOnEnter.Key := '';
FRowTextOnEnter.Value := '';
end
else
FLastEditedRow := -1;
end;
procedure TValueListEditor.GetAutoFillColumnInfo(const Index: Integer;
var aMin, aMax, aPriority: Integer);
begin
if Index=1 then
aPriority := 1
else
begin
if doKeyColFixed in FDisplayOptions then
aPriority := 0
else
aPriority := 1;
end;
end;
function TValueListEditor.GetCells(ACol, ARow: Integer): string;
var
I: Integer;
begin
Result:='';
if (ARow=0) and (doColumnTitles in DisplayOptions) then
begin
Result := Inherited GetCells(ACol, ARow);
end
else
begin
I:=ARow-FixedRows;
if (I >= Strings.Count) then
//Either empty grid, or a row has been added and Strings hasn't been update yet
//the latter happens when rows are auto-added (issue #0025166)
Exit;
if ACol=0 then
Result:=Strings.Names[I]
else if ACol=1 then
Result:=Strings.ValueFromIndex[I];
end;
end;
procedure SetGridEditorReadOnly(Ed: TwinControl; RO: Boolean);
begin
//debugln('SetEditorReadOnly: Ed is ',DbgSName(Ed),' ReadOnly=',DbgS(RO));
if (Ed is TCustomEdit) then
TCustomEdit(Ed).ReadOnly := RO
else if (Ed is TCustomComboBox) then
if RO then
TCustomComboBox(Ed).Style := csDropDownList
else
TCustomComboBox(Ed).Style := csDropDown;
end;
function TValueListEditor.GetDefaultEditor(Column: Integer): TWinControl;
var
ItemProp: TItemProp;
begin
if (Row <> FLastEditedRow) then
//save current contents for RestoreCurrentRow
begin
FLastEditedRow := Row;
FRowTextOnEnter.Key := Cells[0,Row];
FRowTextOnEnter.Value := Cells[1,Row];
end;
Result:=inherited GetDefaultEditor(Column);
//Need this to be able to intercept VK_Delete in the editor
if (KeyDelete in KeyOptions) then
EditorOptions := EditorOptions or EO_HOOKKEYDOWN
else
EditorOptions := EditorOptions and (not EO_HOOKKEYDOWN);
if Column=1 then
begin
ItemProp := nil;
//debugln('**** A Col=',dbgs(col),' Row=',dbgs(row),' (',dbgs(itemprop),')');
ItemProp := Strings.GetItemProp(Row-FixedRows);
if Assigned(ItemProp) then
begin
case ItemProp.EditStyle of
esSimple: begin
result := EditorByStyle(cbsAuto);
SetGridEditorReadOnly(result, ItemProp.ReadOnly);
end;
esEllipsis: begin
result := EditorByStyle(cbsEllipsis);
SetGridEditorReadOnly(TCompositeCellEditorAccess(result).GetActiveControl, ItemProp.ReadOnly);
end;
esPickList: begin
result := EditorByStyle(cbsPickList);
(result as TCustomComboBox).Items.Assign(ItemProp.PickList);
(result as TCustomComboBox).DropDownCount := DropDownRows;
SetGridEditorReadOnly(result, ItemProp.ReadOnly);
if Assigned(FOnGetPickList) then
FOnGetPickList(Self, Strings.Names[Row - FixedRows], (result as TCustomComboBox).Items);
//Style := csDropDown, default = csDropDownList;
end;
end; //case
end
else SetGridEditorReadOnly(result, False);
end
else
begin
//First column is only editable if KeyEdit is in KeyOptions
if not (KeyEdit in KeyOptions) then
Result := nil
else
SetGridEditorReadOnly(result, False);
end;
end;
function TValueListEditor.GetRowCount: Integer;
begin
Result := inherited RowCount;
end;
procedure TValueListEditor.KeyDown(var Key: Word; Shift: TShiftState);
begin
inherited KeyDown(Key, Shift);
if (KeyAdd in KeyOptions) then
begin
if (Key = VK_INSERT) and (Shift = []) then
begin
//Insert a row in the current position
InsertRow('', '', False);
Key := 0;
end;
end;
if (KeyDelete in KeyOptions) then
begin
//Although Delphi help says this happens if user presses Delete, testers report it only happens with Ctrl+Delete
if (Key = VK_DELETE) and (Shift = [ssModifier]) then
begin
DeleteRow(Row);
Key := 0;
end;
end;
if (Key = VK_ESCAPE) and (Shift = []) then
if RestoreCurrentRow then Key := 0;
end;
procedure TValueListEditor.ResetDefaultColWidths;
begin
if not AutoFillColumns then
inherited ResetDefaultColWidths
else if doKeyColFixed in DisplayOptions then
begin
SetRawColWidths(0, -1);
VisualChange;
end;
end;
procedure TValueListEditor.SetCells(ACol, ARow: Integer; const AValue: string);
var
I: Integer;
Key, KeyValue, Line: string;
begin
if (ARow = 0) and (doColumnTitles in DisplayOptions) then
begin
Inherited SetCells(ACol, ARow, AValue);
end
else
begin
I:=ARow-FixedRows;
if ACol=0 then
begin
Key := AValue;
KeyValue := Cells[1,ARow]
end
else
begin
KeyValue := AValue;
Key := Cells[0,ARow];
end;
//If cells are empty don't store '=' in Strings
if (Key = '') and (KeyValue = '') then
Line := ''
else begin
Line := KeyValue;
system.Insert(Strings.NameValueSeparator, Line, 1);
system.Insert(Key, Line, 1);
end;
// Empty grid: don't add a the line '' to Strings!
if (Strings.Count = 0) and (Line = '') then Exit;
if I>=Strings.Count then
Strings.Insert(I,Line)
else
if (Line <> Strings[I]) then Strings[I]:=Line;
end;
end;
function TValueListEditor.GetEditText(ACol, ARow: Integer): string;
begin
Result:= Cells[ACol, ARow];
if Assigned(OnGetEditText) then
OnGetEditText(Self, ACol, ARow, Result);
end;
procedure TValueListEditor.SetEditText(ACol, ARow: Longint; const Value: string);
begin
inherited SetEditText(ACol, ARow, Value);
Cells[ACol, ARow] := Value;
end;
procedure TValueListEditor.SetRowCount(AValue: Integer);
var
OldValue, NewCount: Integer;
begin
//debugln('TValueListEditor.SetRowCount: AValue=',DbgS(AValue));
OldValue := inherited RowCount;
if OldValue = AValue then Exit;
NewCount := AValue - FixedRows;
if (NewCount > Strings.Count) then
begin
Strings.BeginUpdate;
while (Strings.Count < NewCount) do Strings.Add('');
Strings.EndUpdate;
end
else if (NewCount < Strings.Count) then
begin
Strings.BeginUpdate;
while (NewCount < Strings.Count) do Strings.Delete(Strings.Count - 1);
Strings.EndUpdate;
end;
end;
procedure TValueListEditor.Sort(ColSorting: Boolean; index, IndxFrom,
IndxTo: Integer);
var
HideEditor: Boolean;
begin
HideEditor := goAlwaysShowEditor in Options;
if HideEditor then Options := Options - [goAlwaysShowEditor];
Strings.BeginUpdate;
try
inherited Sort(True, index, IndxFrom, IndxTo);
finally
Strings.EndUpdate;
end;
if HideEditor then Options := Options + [goAlwaysShowEditor];
end;
procedure TValueListEditor.TitlesChanged(Sender: TObject);
begin
// Refresh the display.
ShowColumnTitles;
AdjustRowCount;
Invalidate;
end;
function TValueListEditor.ValidateEntry(const ACol, ARow: Integer;
const OldValue: string; var NewValue: string): boolean;
var
Index, i: Integer;
begin
Result := inherited ValidateEntry(ACol, ARow, OldValue, NewValue);
//Check for duplicate key names (only in "Key" column), if KeyUnique is set
if ((ACol - FixedCols) = 0) and (KeyUnique in KeyOptions) then
begin
Index := ARow - FixedRows;
for i := 0 to FStrings.Count - 1 do
begin
if (Index <> i) and (FStrings.Names[i] <> '') then
begin
if (LazUTF8.Utf8CompareText(FStrings.Names[i], NewValue) = 0) then
begin
Result := False;
ShowMessage(Format(rsVLEDuplicateKey,[NewValue, i + FixedRows]));
if Editor is TStringCellEditor then TStringCelleditor(Editor).SelectAll;
Break;
end;
end;
end;
end;
end;
class procedure TValueListEditor.WSRegisterClass;
begin
// RegisterPropertyToSkip(Self, 'SomeProperty', 'VCL compatibility property', '');
inherited WSRegisterClass;
end;
procedure Register;
begin
RegisterComponents('Additional',[TValueListEditor]);
end;
end.