123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746 |
- unit ListViewColProperties;
- interface
- uses
- Classes, ComCtrls, Contnrs;
- type
- TCustomListViewColProperty = class(TObject)
- Alignment: TAlignment;
- Caption: string;
- Width: Integer;
- MaxWidth: Integer;
- MinWidth: Integer;
- Visible: Boolean;
- Order: Integer;
- constructor Create(AOrder: Integer);
- end;
- type
- TCustomListViewColProperties = class(TPersistent)
- private
- FChanged: Boolean;
- FOnChange: TNotifyEvent;
- FUpdating: Integer;
- FProperties: TObjectList;
- FCreated: Boolean;
- function GetColumns: TListColumns;
- function GetCount: Integer;
- function GetOrderStr: string;
- procedure CheckBounds(Index: Integer);
- procedure SetWidthsStr(Value: string; PixelsPerInch: Integer);
- function GetWidthsStr: string;
- procedure SetOrderStr(Value: string);
- protected
- FListView: TCustomListView;
- FListViewManaged: Boolean;
- FConstraintsInitialized: Boolean;
- function GetAlignments(Index: Integer): TAlignment;
- function GetParamsStr: string; virtual;
- function GetVisible(Index: Integer): Boolean;
- function GetWidths(Index: Integer): Integer;
- procedure SetAlignments(Index: Integer; Value: TAlignment);
- procedure SetVisibleInternal(Index: Integer; Value: Boolean; SaveWidth: Boolean);
- procedure SetVisible(Index: Integer; Value: Boolean);
- procedure SetWidths(Index: Integer; Value: Integer);
- function GetCaptions(Index: Integer): string;
- procedure Changed; virtual;
- procedure SetCaptions(Index: Integer; Value: string); virtual;
- procedure SetParamsStr(Value: string); virtual;
- procedure UpdateListView;
- procedure UpdateFromListView;
- procedure UpdateOrderFromListView;
- procedure UpdateListViewOrder;
- function GetProperties(Index: Integer): TCustomListViewColProperty;
- function GetIndexByOrder(Order: Integer): Integer;
- function ColumnsExists: Boolean;
- procedure SetRuntimeVisible(Index: Integer; Value: Boolean; SaveWidth: Boolean);
- function GetColumn(Index: Integer): TListColumn;
- procedure CreateProperties(ACount: Integer);
- function DefaultConstraint(Value: Integer; Visible: Boolean; Def: Integer): Integer;
- property Columns: TListColumns read GetColumns stored False;
- public
- constructor Create(ListView: TCustomListView; ColCount: Integer);
- destructor Destroy; override;
- procedure EndUpdate;
- procedure BeginUpdate;
- procedure ListViewWndCreated;
- procedure ListViewWndDestroying;
- procedure ListViewWndDestroyed;
- procedure ChangeScale(M, D: Integer);
- property Count: Integer read GetCount stored False;
- property Alignments[Index: Integer]: TAlignment read GetAlignments write SetAlignments;
- property Captions[Index: Integer]: string read GetCaptions write SetCaptions;
- property Widths[Index: Integer]: Integer read GetWidths write SetWidths;
- property Visible[Index: Integer]: Boolean read GetVisible write SetVisible;
- procedure RecreateColumns;
- property OnChange: TNotifyEvent read FOnChange write FOnChange;
- property ParamsStr: string read GetParamsStr write SetParamsStr stored False;
- end; { TCustomListViewColProperties }
- type
- TListViewColProperties = class(TCustomListViewColProperties)
- published
- end; { TListViewColProperties }
- implementation
- uses
- SysUtils, CommCtrl, Windows, PasTools, Controls, Forms;
- const
- DefaultListViewMaxWidth = 1000;
- DefaultListViewMinWidth = 20;
- { TODO : V ListView zamezit zmenu velikosti neviditelnych sloupecku }
- constructor TCustomListViewColProperty.Create(AOrder: Integer);
- begin
- Alignment := taLeftJustify;
- Caption := '';
- Width := 50;
- Visible := True;
- Order := AOrder;
- end;
- { TCustomListViewColProperties }
- constructor TCustomListViewColProperties.Create(
- ListView: TCustomListView; ColCount: Integer);
- var
- ACount: Integer;
- begin
- // This contructor (and constructors of descendants)
- // is only even called from implementations of
- // TCustomNortonLikeListView.NewColProperties
- inherited Create;
- FConstraintsInitialized := False;
- FCreated := False;
- FUpdating := 0;
- FChanged := False;
- // ColCount is not 0 for file panels (TDirView and TCustomUnixDirView).
- // It is 0 otherwise.
- FListViewManaged := (ColCount = 0);
- FListView := ListView;
- FProperties := TObjectList.Create;
- if FListViewManaged then ACount := GetColumns.Count
- else ACount := ColCount;
- CreateProperties(ACount);
- if not Assigned(FListView) then
- raise Exception.Create('NIL ListView pointer.');
- end;
- destructor TCustomListViewColProperties.Destroy;
- begin
- inherited;
- FProperties.Free;
- end;
- procedure TCustomListViewColProperties.SetWidthsStr(Value: string; PixelsPerInch: Integer);
- var
- ColStr: string;
- Index: Integer;
- NeedInvalidate, NewVisible: Boolean;
- NewWidth: Integer;
- begin
- Index := 0;
- NeedInvalidate := False;
- BeginUpdate;
- try
- while (Value <> '') and (Index < Count) do
- begin
- ColStr := CutToChar(Value, ';', True);
- NewWidth := LoadDimension(StrToInt(CutToChar(ColStr, ',', True)), PixelsPerInch, FListView);
- Widths[Index] := NewWidth;
- NewVisible := Boolean(StrToInt(CutToChar(ColStr, ',', True)));
- if Visible[Index] <> NewVisible then
- begin
- // As we are within BeginUpdate guard, the width set just above is not propadated to WinAPI.
- // As reading width retrieves it from WinAPI (if the control is already allocated), we actually save wrong width.
- SetVisibleInternal(Index, NewVisible, False);
- NeedInvalidate := True;
- end;
- Inc(Index);
- end;
- finally
- EndUpdate;
- end;
- // When visibility changes (particularly while reseting layout) redraw is needed
- // (Invalidate is called in SetVisible too, but maybe it has no effect there, because it is within BeginUpdate/EndUpdate)
- if NeedInvalidate and FListView.HandleAllocated then
- FListView.Invalidate;
- end;
- function TCustomListViewColProperties.GetWidthsStr: string;
- var
- Index: Integer;
- begin
- Result := '';
- for Index := 0 to Count-1 do
- begin
- Result := Format('%s;%d,%d', [Result, SaveDimension(Widths[Index]), Integer(Visible[Index])]);
- end;
- Delete(Result, 1, 1);
- end;
- procedure TCustomListViewColProperties.BeginUpdate;
- begin
- Columns.BeginUpdate;
- Inc(FUpdating);
- end;
- procedure TCustomListViewColProperties.EndUpdate;
- begin
- Columns.EndUpdate;
- Dec(FUpdating);
- if FUpdating = 0 then
- begin
- // call Changed() even when FChange is false
- Changed;
- FChanged := False;
- end;
- end;
- procedure TCustomListViewColProperties.Changed;
- begin
- if FUpdating > 0 then FChanged := True
- else
- if Assigned(FOnChange) then FOnChange(Self);
- end;
- procedure TCustomListViewColProperties.CheckBounds(Index: Integer);
- begin
- if (Index < 0) or (Index >= Count) then
- raise Exception.Create('Index out of bounds.');
- end;
- function TCustomListViewColProperties.GetProperties(Index: Integer): TCustomListViewColProperty;
- begin
- Result := TCustomListViewColProperty(FProperties.Items[Index]);
- end;
- function TCustomListViewColProperties.GetIndexByOrder(Order: Integer): Integer;
- var
- I: Integer;
- begin
- for I := 0 to Count - 1 do
- begin
- if GetProperties(I).Order = Order then
- begin
- Result := I;
- Exit;
- end;
- end;
- raise Exception.Create('Column order out of bounds');
- end;
- function TCustomListViewColProperties.ColumnsExists: Boolean;
- begin
- Result := FListView.HandleAllocated;
- if Result and (not FCreated) and (not FListViewManaged) then
- UpdateListView;
- end;
- procedure TCustomListViewColProperties.SetAlignments(Index: Integer; Value: TAlignment);
- begin
- CheckBounds(Index);
- if Alignments[Index] <> Value then
- begin
- GetProperties(Index).Alignment := Value;
- if ColumnsExists then GetColumn(Index).Alignment := Value;
- Changed;
- end;
- end;
- procedure TCustomListViewColProperties.SetCaptions(Index: Integer; Value: string);
- begin
- CheckBounds(Index);
- if Captions[Index] <> Value then
- begin
- if ColumnsExists then GetColumn(Index).Caption := Value
- else GetProperties(Index).Caption := Value;
- Changed;
- end;
- end;
- function TCustomListViewColProperties.GetAlignments(Index: Integer): TAlignment;
- begin
- CheckBounds(Index);
- if ColumnsExists then Result := GetColumn(Index).Alignment
- else Result := GetProperties(Index).Alignment;
- end;
- function TCustomListViewColProperties.GetCaptions(Index: Integer): string;
- begin
- CheckBounds(Index);
- if ColumnsExists then Result := GetColumn(Index).Caption
- else Result := GetProperties(Index).Caption;
- end;
- procedure TCustomListViewColProperties.SetOrderStr(Value: string);
- var
- Order, Index: Integer;
- Properties: TCustomListViewColProperty;
- STemp: string;
- Phase: Boolean;
- begin
- BeginUpdate;
- try
- for Index := 0 to Count - 1 do
- GetProperties(Index).Order := -1;
- // First order invisible columns (not True), then visible (not not True)
- Phase := True;
- Order := 0;
- repeat
- Phase := not Phase;
- STemp := Value;
- while (STemp <> '') and (Order < Count) do
- begin
- Index := StrToInt(CutToChar(STemp, ';', True));
- Properties := GetProperties(Index);
- if (Properties.Visible = Phase) and
- (Properties.Order < 0) { robustness } then
- begin
- Properties.Order := Order;
- Inc(Order);
- end;
- end;
- // add missing columns from the same visibility class
- for Index := 0 to Count - 1 do
- begin
- Properties := GetProperties(Index);
- if (Properties.Visible = Phase) and
- (Properties.Order < 0) then
- begin
- Properties.Order := Order;
- Inc(Order);
- end;
- end;
- until Phase;
- if ColumnsExists then
- UpdateListViewOrder;
- finally
- EndUpdate;
- end;
- end;
- procedure TCustomListViewColProperties.SetParamsStr(Value: string);
- var
- S: string;
- WidthsStr: string;
- OrderStr: string;
- PixelsPerInch: Integer;
- begin
- // TFileFindDialog uses / as separator of its settings
- S := CutToChar(Value, '|', True);
- WidthsStr := CutToChar(S, '@', True);
- PixelsPerInch := LoadPixelsPerInch(S, FListView);
- SetWidthsStr(WidthsStr, PixelsPerInch);
- // Have to set order after visibility, otherwise we lost ordering of columns that are invisible by default,
- // but visible by configuration (as they would get ordered to the front)
- OrderStr := CutToChar(Value, '|', True);
- SetOrderStr(OrderStr);
- end;
- procedure TCustomListViewColProperties.SetVisibleInternal(Index: Integer; Value: Boolean; SaveWidth: Boolean);
- var
- I: Integer;
- Properties: TCustomListViewColProperty;
- begin
- CheckBounds(Index);
- if Visible[Index] <> Value then
- begin
- Properties := GetProperties(Index);
- if ColumnsExists then
- UpdateOrderFromListView;
- if Value then
- begin
- // shown column is moved to the back
- for I := 0 to Count - 1 do
- begin
- if GetProperties(I).Order > Properties.Order then
- Dec(GetProperties(I).Order);
- end;
- Properties.Order := Count - 1;
- if ColumnsExists then
- UpdateListViewOrder;
- // show only after reordering column
- Properties.Visible := True;
- if ColumnsExists then
- SetRuntimeVisible(Index, True, SaveWidth);
- end
- else
- begin
- // hide before reordering column
- Properties.Visible := False;
- if ColumnsExists then
- SetRuntimeVisible(Index, False, SaveWidth);
- // hidden column is moved to the front,
- // unless column to the left is not hidden already
- // (or unless it is first already, in which case the
- // condition in the loop is never satisfied)
- if (Properties.Order > 0) and
- GetProperties(GetIndexByOrder(Properties.Order - 1)).Visible then
- begin
- for I := 0 to Count - 1 do
- begin
- if GetProperties(I).Order < Properties.Order then
- Inc(GetProperties(I).Order);
- end;
- Properties.Order := 0;
- end;
- if ColumnsExists then
- UpdateListViewOrder;
- end;
- Changed;
- // It does not refresh itself at last one on Win7 when DoubleBuffered
- if FListView.HandleAllocated then
- FListView.Invalidate;
- end;
- end;
- procedure TCustomListViewColProperties.SetVisible(Index: Integer; Value: Boolean);
- begin
- SetVisibleInternal(Index, Value, True);
- end;
- procedure TCustomListViewColProperties.SetRuntimeVisible(
- Index: Integer; Value: Boolean; SaveWidth: Boolean);
- var
- Properties: TCustomListViewColProperty;
- begin
- // This is probably only ever called from file panels (DirViews)
- // as other uses ("sychronization checklist" and "file find")
- // have FListViewManaged = False and never change Visible property
- // (though user can hide some columns manually in configuration storage)
- with GetColumn(Index) do
- begin
- Properties := GetProperties(Index);
- if Value then
- begin
- MaxWidth := Properties.MaxWidth;
- MinWidth := Properties.MinWidth;
- Width := Properties.Width;
- end
- else
- begin
- if SaveWidth then
- Properties.Width := Width;
- MaxWidth := 1;
- MinWidth := 0;
- Width := 0
- end;
- end;
- end;
- procedure TCustomListViewColProperties.SetWidths(Index: Integer; Value: Integer);
- var
- Properties: TCustomListViewColProperty;
- begin
- CheckBounds(Index);
- Properties := GetProperties(Index);
- if (Properties.MinWidth > 0) and (Value < Properties.MinWidth) then
- begin
- Value := Properties.MinWidth;
- end
- else
- if (Properties.MaxWidth > 0) and (Value > Properties.MaxWidth) then
- begin
- Value := Properties.MaxWidth;
- end;
- if Widths[Index] <> Value then
- begin
- Properties.Width := Value;
- if ColumnsExists and Visible[Index] then GetColumn(Index).Width := Value;
- Changed;
- end;
- end;
- function TCustomListViewColProperties.GetColumns: TListColumns;
- begin
- Result := TListView(FListView).Columns;
- end;
- function TCustomListViewColProperties.GetColumn(Index: Integer): TListColumn;
- begin
- Result := Columns[Index];
- end;
- function TCustomListViewColProperties.GetCount: Integer;
- begin
- Result := FProperties.Count;
- end;
- function TCustomListViewColProperties.GetOrderStr: string;
- var
- Index: Integer;
- begin
- Result := '';
- if ColumnsExists then
- UpdateOrderFromListView;
- for Index := 0 to Count - 1 do
- Result := Format('%s;%d', [Result, GetIndexByOrder(Index)]);
- Delete(Result, 1, 1);
- end;
- function TCustomListViewColProperties.GetParamsStr: string;
- begin
- // WORKAROUND
- // Adding an additional semicolon after the list,
- // to ensure that old versions that did not expect the pixels-per-inch part,
- // stop at the semicolon, otherwise they try to parse the
- // "last-column-width|pixels-per-inch" as integer and throw.
- // For the other instance of this hack, see GetListViewStr.
- // The new pixels-per-inch part is inserted after the widths part
- // as parsing of this was always robust to stop at "count" elements,
- // what order part was not (due to its logic of skipping hidden columns)
- Result := Format('%s;@%s|%s', [GetWidthsStr, SavePixelsPerInch(FListView), GetOrderStr]);
- end;
- function TCustomListViewColProperties.GetVisible(Index: Integer): Boolean;
- begin
- CheckBounds(Index);
- Result := GetProperties(Index).Visible;
- end;
- function TCustomListViewColProperties.GetWidths(Index: Integer): Integer;
- begin
- CheckBounds(Index);
- if ColumnsExists and Visible[Index] then
- begin
- Result := GetColumn(Index).Width;
- end
- else
- begin
- Result := GetProperties(Index).Width;
- end;
- end;
- procedure TCustomListViewColProperties.RecreateColumns;
- var
- Copy: TListColumns;
- begin
- Copy := TListColumns.Create(nil);
- try
- Copy.Assign(Columns);
- Columns.Assign(Copy);
- finally
- Copy.Free;
- end;
- end;
- procedure TCustomListViewColProperties.CreateProperties(ACount: Integer);
- var
- Index: Integer;
- Properties: TCustomListViewColProperty;
- begin
- for Index := 0 to ACount - 1 do
- begin
- Properties := TCustomListViewColProperty.Create(Index);
- FProperties.Add(Properties);
- end;
- end;
- function TCustomListViewColProperties.DefaultConstraint(Value: Integer; Visible: Boolean; Def: Integer): Integer;
- begin
- if (Value > 0) and Visible then Result := Value
- else Result := Def;
- end;
- procedure TCustomListViewColProperties.ListViewWndCreated;
- var
- Index: Integer;
- Properties: TCustomListViewColProperty;
- Column: TListColumn;
- begin
- if FListViewManaged then
- begin
- if (FProperties.Count = 0) and (Columns.Count > 0) then
- CreateProperties(Columns.Count);
- UpdateFromListView;
- end
- else
- begin
- UpdateListView;
- end;
- Assert(ColumnsExists);
- for Index := 0 to Count - 1 do
- begin
- Column := GetColumn(Index);
- Properties := GetProperties(Index);
- if not FConstraintsInitialized then
- begin
- Properties.MaxWidth := DefaultConstraint(Column.MaxWidth, Properties.Visible, DefaultListViewMaxWidth);
- Properties.MinWidth := DefaultConstraint(Column.MinWidth, Properties.Visible, DefaultListViewMinWidth);
- end;
- // To apply the default constraints to columns that do not have their own
- if Properties.Visible then
- begin
- Column.MaxWidth := Properties.MaxWidth;
- if Column.Width > Column.MaxWidth then Column.Width := Column.MaxWidth;
- Column.MinWidth := Properties.MinWidth;
- if Column.Width < Column.MinWidth then Column.Width := Column.MinWidth;
- end;
- end;
- FConstraintsInitialized := True;
- end;
- procedure TCustomListViewColProperties.ListViewWndDestroying;
- begin
- UpdateFromListView;
- end;
- procedure TCustomListViewColProperties.ListViewWndDestroyed;
- begin
- if not FListViewManaged then
- FCreated := False;
- end;
- procedure TCustomListViewColProperties.ChangeScale(M, D: Integer);
- var
- Index: Integer;
- Properties: TCustomListViewColProperty;
- Column: TListColumn;
- begin
- for Index := 0 to Count - 1 do
- begin
- Properties := GetProperties(Index);
- // This is not perfect, as the constraints apply on re-scaled width before they are re-scaled themselves
- if Properties.Visible and ColumnsExists then
- begin
- Column := GetColumn(Index);
- Column.MaxWidth := MulDiv(Column.MaxWidth, M, D);
- Column.MinWidth := MulDiv(Column.MinWidth, M, D);
- end
- else
- begin
- Properties.MaxWidth := MulDiv(Properties.MaxWidth, M, D);
- Properties.MinWidth := MulDiv(Properties.MinWidth, M, D);
- end;
- end;
- end;
- procedure TCustomListViewColProperties.UpdateListViewOrder;
- var
- Index: Integer;
- Properties: TCustomListViewColProperty;
- Temp: array of Integer;
- begin
- SetLength(Temp, Count);
- // Seemingly useless,
- // but probably only because we swallow HDN_ENDDRAG in TCustomIEListView.WMNotify,
- // what prevents VLC from actually reordering columns collection
- ListView_GetColumnOrderArray(FListView.Handle, Count, PInteger(Temp));
- for Index := 0 to Count - 1 do
- begin
- Properties := GetProperties(Index);
- Temp[Properties.Order] := Index;
- end;
- ListView_SetColumnOrderArray(FListView.Handle, Count, PInteger(Temp));
- end;
- procedure TCustomListViewColProperties.UpdateListView;
- var
- Index: Integer;
- Column: TListColumn;
- Properties: TCustomListViewColProperty;
- begin
- // Only called when FListViewManaged = False
- BeginUpdate;
- try
- for Index := 0 to Count-1 do
- begin
- if Index < Columns.Count then
- Column := GetColumn(Index)
- else
- Column := Columns.Add;
- Properties := GetProperties(Index);
- Column.Alignment := Properties.Alignment;
- Column.Caption := Properties.Caption;
- SetRuntimeVisible(Index, Properties.Visible, False);
- end;
- UpdateListViewOrder;
- finally
- FCreated := True;
- EndUpdate;
- end;
- end;
- procedure TCustomListViewColProperties.UpdateOrderFromListView;
- var
- Index: Integer;
- Temp: array of Integer;
- begin
- SetLength(Temp, Count);
- ListView_GetColumnOrderArray(FListView.Handle, Count, PInteger(Temp));
- for Index := 0 to Count - 1 do
- begin
- GetProperties(Temp[Index]).Order := Index;
- end;
- end;
- procedure TCustomListViewColProperties.UpdateFromListView;
- var
- Index: Integer;
- Column: TListColumn;
- Properties: TCustomListViewColProperty;
- begin
- Assert(FProperties.Count = Columns.Count);
- for Index := 0 to Count-1 do
- begin
- Column := GetColumn(Index);
- Properties := GetProperties(Index);
- Properties.Alignment := Column.Alignment;
- Properties.Caption := Column.Caption;
- if Properties.Visible then
- begin
- Properties.Width := Column.Width;
- if Column.MaxWidth > 0 then
- Properties.MaxWidth := Column.MaxWidth;
- if Column.MinWidth > 0 then
- Properties.MinWidth := Column.MinWidth;
- end;
- end;
- UpdateOrderFromListView;
- end;
- end.
|