Question

I think I found a potential bug in TListView.

Steps to reproduce: Create a new VCL Forms application, add a TListView, set it`s ViewStyle to vsReports. Add two buttons

button1:

procedure TForm1.Button1Click(Sender: TObject);
var
  lCol: TListColumn;
begin
  lcol :=   ListView1.Columns.Add;
  lcol.Caption := 'name';
  lcol :=   ListView1.Columns.Add;
  lcol.Caption := 'name2';
  lcol :=   ListView1.Columns.Add;
  lcol.Caption := 'name3';
end;

button2:

procedure TForm1.Button2Click(Sender: TObject);
begin
  ListView1.Columns.Delete(1);
end;

Result: The column is deleted, but the caption of the last column gets lost. This also happens, when adding more columns and deleting a column that is between others (or deleting the first column). The caption of the last column is always empty.

I'm using XE3. Is there anything I missed?

Thanks

edit: QC link

potential duplicate

Was it helpful?

Solution

There's more to this then what's reported in the question (more at the end).

This is related with a previous question of yours. That question involved the listview control losing the mapping between columns and items/subitems when you moved a column after adding a column. I proposed a possible fix for comctrls.pas which involved preserving FOrderTags of columns when they are moved. The VCL had 'FOrderTag's built from the ground-up whenever a column is moved - disregarding any current positioning of the columns.

What happens then is, you file a bug report, submit the possible fix as a workaround, and it gets checked-in exactly as is. The problem now you discover is, when we preserve FOrderTag of each column, and then remove a column from the middle, we create a hole - they are not sequential any more (say we have columns 0, 1 and 2 with respective order tags, remove column 1 and now we have 2 columns with order tags 0 and 2). Apparently the native control does not like this.

Again modifying the VCL, we can remove any possible hole when we are removing a column. The below seems to take care of the missing caption and the AV when you resize/move the column with the missing caption mentioned in a comment to the question.

destructor TListColumn.Destroy;
var
  Columns: TListColumns;
  i: Integer; //+
begin
  Columns := TListColumns(Collection);
  if TListColumns(Collection).Owner.HandleAllocated then
    ListView_DeleteColumn(TListColumns(Collection).Owner.Handle, Index);
//{+
  for i := 0 to Columns.Count - 1 do
    if Columns[i].FOrderTag > FOrderTag then
      Dec(Columns[i].FOrderTag);
//}
  inherited Destroy;
  Columns.UpdateCols;
end;


Now if we come back to what's not reported in the question, if you had been inserted some subitems, you'd have noticed that they are preserving their positions, IOW the mapping between columns and subitems are lost. There's the probability that your view is different then mine on this, but I think the subitems of the deleted column should get lost. Unfortunately I couldn't figure out a way to achieve this.


edit: I cannot think of anything to easily integrate/fix in the VCL. There's nothing stopping you from deleting the first inserted column. This one corresponds to the items, if we delete the items, all subitems will also be taken out. The current implementation in VCL is that in fact no item data is deleted when you remove a column. You can verify this by adding a column after you remove one, subitems will magically appear under the new column.

Anyway, what I can suggest you to is to delete the subitems of a removed column manually. Below is an example of a utility procedure to delete a column and its corresponding subitems:

procedure ListViewDeleteColumn(ListView: TListView; Col: Integer);
var
  i: Integer;
  ColumnOrder: array of Integer;
begin
  SetLength(ColumnOrder, ListView.Columns.Count);
  ListView_GetColumnOrderArray(
        ListView.Handle, ListView.Columns.Count, PInteger(ColumnOrder));
  Assert(ColumnOrder[Col] <> 0, 'column with items cannot be removed');

  for i := 0 to ListView.Items.Count - 1 do
    if Assigned(ListView.Items[i].SubItems) and
        (ListView.Items[i].SubItems.Count >= Col) then
    ListView.Items[i].SubItems.Delete(ColumnOrder[Col] - 1);

  ListView.Columns.Delete(Col);
end;

If you decide to delete the first column, decide what you'll do with items/subitems and rebuild them.

OTHER TIPS

If you delete the last column using code below it works ok:

uses
 CommCtrl;

procedure TForm1.Button3Click(Sender: TObject);
begin
  ListView_DeleteColumn(ListView1.Handle, 2);
end;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top