TListView: VCL loses the order of columns if you add a column

后端 未结 1 430
离开以前
离开以前 2021-01-12 21:51

I\'m trying to add a column between existing columns in a TListView. Therefor I add the new column at the end and move it by setting it`s index to the designated value. This

相关标签:
1条回答
  • 2021-01-12 22:31

    Call the UpdateItems method after you've arranged the columns. E.g.:

    ..
    col.Index := number;
    listview.UpdateItems(0, MAXINT);
    ..
    



    Update:

    In my tests, I still seem to need the above call in some occasion. But the real problem is that "there is a bug in the Delphi list view control".

    Duplicating the problem with a simple project:

    • Place a TListView control on a VCL form, set its ViewStyle to 'vsReport' and set FullDrag to 'true'.
    • Put the below code to the OnCreate handler of the form:
      ListView1.Columns.Add.Caption := 'col 1';
      ListView1.Columns.Add.Caption := 'col 2';
      ListView1.Columns.Add.Caption := 'col 3';
      ListView1.AddItem('cell 1', nil);
      ListView1.Items[0].SubItems.Add('cell 2');
      ListView1.Items[0].SubItems.Add('cell 3');
      
    • Place a TButton on the form, and put the below code to its OnClick handler:
      ListView1.Columns.Add.Caption := 'col 4';
    • Run the project and drag the column header of 'col 3' to in-between 'col 1' and 'col 2'. The below picture is what you'll see at this moment (everything is fine):

      list view after column drag

    • Click the button to add a new column, now the list view becomes:

      list view after adding column

      Notice that 'cell 2' has reclaimed its original position.

    Bug:

    The columns of a TListView (TListColumn) holds its ordering information in its FOrderTag field. Whenever you change the order of a column (either by setting the Index property or by dragging the header), this FOrderTag gets updated accordingly.

    Now, when you add a column to the TListColumns collection, the collection first adds the new TListColumn and then calls the UpdateCols method. The below is the code of the UpdateCols method of TListColumns in D2007 VCL:

    procedure TListColumns.UpdateCols;
    var
      I: Integer;
      LVColumn: TLVColumn;
    begin
      if not Owner.HandleAllocated then Exit;
      BeginUpdate;
      try
        for I := Count - 1 downto 0 do
          ListView_DeleteColumn(Owner.Handle, I);
    
        for I := 0 to Count - 1 do
        begin
          with LVColumn do
          begin
            mask := LVCF_FMT or LVCF_WIDTH;
            fmt := LVCFMT_LEFT;
            cx := Items[I].FWidth;
          end;
          ListView_InsertColumn(Owner.Handle, I, LVColumn);
          Items[I].FOrderTag := I;
        end;
        Owner.UpdateColumns;
      finally
        EndUpdate;
      end;
    end;
    


    The above code removes all columns from the underlying API list-view control and then inserts them anew. Notice how the code assigns each inserted column's FOrderTag the index counter:

          Items[I].FOrderTag := I;
    

    This is the order of the columns from left to right at that point in time. If the method is called whenever the columns are ordered any different than at creation time, then that ordering is lost. And since items do not change their positions accordingly, it all gets mixed up.

    Fix:

    The below modification on the method seemed to work for as little as I tested, you need to carry out more tests (evidently this fix does not cover all possible cases, see 'torno's comments below for details):

    procedure TListColumns.UpdateCols;
    var
      I: Integer;
      LVColumn: TLVColumn;
      ColumnOrder: array of Integer;
    begin
      if not Owner.HandleAllocated then Exit;
      BeginUpdate;
      try
        SetLength(ColumnOrder, Count);
        for I := Count - 1 downto 0 do begin
          ColumnOrder[I] := Items[I].FOrderTag;
          ListView_DeleteColumn(Owner.Handle, I);
        end;
    
        for I := 0 to Count - 1 do
        begin
          with LVColumn do
          begin
            mask := LVCF_FMT or LVCF_WIDTH;
            fmt := LVCFMT_LEFT;
            cx := Items[I].FWidth;
          end;
          ListView_InsertColumn(Owner.Handle, I, LVColumn);
        end;
        ListView_SetColumnOrderArray(Owner.Handle, Count, PInteger(ColumnOrder));
    
        Owner.UpdateColumns;
      finally
        EndUpdate;
      end;
    end;
    

    If you are not using packages you can put a modified copy of 'comctrls.pas' to your project folder. Otherwise you might pursue run-time code patching, or file a bug report and wait for a fix.

    0 讨论(0)
提交回复
热议问题