The ListView internal class TListColumns maintains a mapping between the columns TCollection and the column index in the underlying windows LISTVIEW class.
Each LVCOLUMN is represented in Delphi as a TListColumn object, and the TListColumn maintains an FOrderTag, which indicates which "real" column this TListColumn maps to:
This tag is used internally in methods get TListColumn.GetWidth:
function TListColumn.GetWidth: TWidth;
var
lvColumn : TLVColumn;
begin
lvColumn.mask := LVCF_WIDTH;
if ListView_GetColumn(LOwner.Handle, FOrderTag, {var}lvColumn) then
FWidth := lvColumn.cx;
end;
Deleting columns breaks the FOrderTag
If a column from the TListColumns collection is deleted, then the FOrderTag of all subsequent columns is not recalculated. Ideally TListColumns would override the Notify method, and if the Action is cnDeleting, it would fix all the order tags:
procedure TListColumns.Notify(Item: TCollectionItem; Action: TCollectionNotification);
begin
case Action of
cnDeleting: FixOrderTags(Item as TListColumn);
end;
inherited;
end;
procedure TListColumnsFixed.FixOrderTags(Item: TListColumn);
var
nIndex : Integer;
listColumn: TListColumn;
begin
// The Windows ListView adjusts the LVColumn.iOrder when a column is deleted.
// FOrderTag is not adjusted in the original TListColumns collection so it gets out of sync.
// This class fixes that.
for nIndex := 0 to Count - 1 do
begin
listColumn := Items[nIndex];
// if FOrderTag > FOrderTag of the item being deleted then adjust it
if ListColumn.FOrderTag > Item.FOrderTag then
Dec(ListColumn.FOrderTag);
end;
end;
The VCL source fix
That works great when you're Imprise, because then you can just fix the Vcl source. But we're not allowed to make any changes that affect the interface section of a unit. Because of that we instead create our own TListColumns descendant hidden in the implementation section:
type
TListColumnsFixed = class(TListColumns)
private
procedure FixOrderTags(Item : TListColumn);
protected
procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
end;
{ TListColumnsFixed }
procedure TListColumnsFixed.FixOrderTags(Item: TListColumn);
var
nIndex : Integer;
listColumn: TListColumn;
begin
// The Windows ListView adjusts the LVColumn.iOrder when a column is deleted.
// FOrderTag is not adjusted in the original TListColumns collection so it gets out of sync.
// This class fixes that.
for nIndex := 0 to Count - 1 do
begin
listColumn := Items[nIndex];
// if FOrderTag > FOrderTag of the item being deleted then adjust it
if ListColumn.FOrderTag > Item.FOrderTag then
Dec(ListColumn.FOrderTag);
end;
end;
procedure TListColumnsFixed.Notify(Item: TCollectionItem; Action: TCollectionNotification);
begin
case Action of
cnDeleting: FixOrderTags(Item as TListColumn);
end;
inherited;
end;
And now we just have the TListView use our correctly working TListColumns descendant:
constructor TCustomListView.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
//...snip...
// FListColumns := TListColumns.Create(Self);
FListColumns := TListColumnsFixed.Create(Self); // Create the version that fixes the FOrderTag issue.
//...snip...
end;
Tested
The ListView internal class TListColumns maintains a mapping between the columns TCollection and the column index in the underlying windows LISTVIEW class.
Each LVCOLUMN is represented in Delphi as a TListColumn object, and the TListColumn maintains an
FOrderTag
, which indicates which "real" column thisTListColumn
maps to:This tag is used internally in methods get TListColumn.GetWidth:
Deleting columns breaks the FOrderTag
If a column from the
TListColumns
collection is deleted, then theFOrderTag
of all subsequent columns is not recalculated. Ideally TListColumns would override the Notify method, and if the Action is cnDeleting, it would fix all the order tags:The VCL source fix
That works great when you're Imprise, because then you can just fix the Vcl source. But we're not allowed to make any changes that affect the
interface
section of a unit. Because of that we instead create our ownTListColumns
descendant hidden in theimplementation
section:And now we just have the
TListView
use our correctly workingTListColumns
descendant: