TListView: VCL loses column order if you add a column

I am trying to add a column between existing columns in a TListView. Therefore, I add a new column to the end and move it, setting its index to the specified value. This works until a new column is added.

What I did: Add a column to the last position (Columns.Add) and add a subitem to the last position (Subitems.Add) too. After that, I move the column by setting its index to the correct position. This works fine if only one column is added. When adding a second new column, the subitems are screwed. The new subclause of the first column moves to the last position, for example. eg:

0 | 1 | new A | new B | 3 Caption | old sub 1 | old sub 3 | new Sub B | new sub A 

I would be very happy if someone helped!

For example, maybe there is a command or message that I can send to the ListView so that it updates or saves this Column β†’ Subitem mapping, which I could use after adding the first new column, and these are sub-items so that I can process the second new column just like the first one.

Or is it just an error in the TListViews column -> sub-element control or TListColumns ...?

sample code for a vcl forms application (set the event to Form1.OnCreate):

 unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private listview: TListView; initButton: TButton; addColumn: TButton; editColumn: TEdit; subItemCount: Integer; procedure OnInitClick(Sender: TObject); procedure OnAddClick(Sender: TObject); public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin listview := TListView.Create(self); with listview do begin Left := 8; Top := 8; Width := self.Width - 30; Height := self.Height - 100; Anchors := [akLeft, akTop, akRight, akBottom]; TabOrder := 0; ViewStyle := vsReport; Parent := self; end; initButton := TButton.Create(self); with initButton do begin left := 8; top := listview.Top + listview.Height + 20; Width := 75; Height := 25; TabOrder := 1; Caption := 'init'; OnClick := OnInitClick; Parent := self; end; editColumn := TEdit.Create(self); with editColumn do begin left := initButton.Left + initButton.Width + 30; top := listview.Top + listview.Height + 20; Width := 120; Height := 25; TabOrder := 2; Parent := self; Caption := ''; end; addColumn := TButton.Create(self); with addColumn do begin left := editColumn.Left + editColumn.Width + 10; top := listview.Top + listview.Height + 20; Width := 75; Height := 25; TabOrder := 1; Enabled := true; Caption := 'add'; OnClick := OnAddClick; Parent := self; end; end; procedure TForm1.OnInitClick(Sender: TObject); var col: TListColumn; i, j: integer; item: TListItem; begin listview.Items.Clear; listview.Columns.Clear; // add items for I := 0 to 2 do begin col := ListView.Columns.Add; col.Caption := 'column ' + IntToStr(i); col.Width := 80; end; // add columns for I := 0 to 3 do begin item := ListView.Items.Add; item.Caption := 'ItemCaption'; // add subitems for each column for j := 0 to 1 do begin item.SubItems.Add('subitem ' + IntToStr(j+1)); end; end; subItemCount := 5; end; procedure TForm1.OnAddClick(Sender: TObject); var number: integer; col: TListColumn; i: Integer; ascii: char; begin listview.Columns.BeginUpdate; number := StrToInt(editColumn.Text); ascii := Chr(65 + number); // create the new column col := TListColumn(ListView.Columns.add()); col.Width := 80; col.Caption := ascii; // add the new subitems for I := 0 to ListView.Items.Count-1 do begin ListView.Items[i].SubItems.Add('subitem ' + ascii); end; // move it to the designated position col.Index := number; listview.Columns.EndUpdate; Inc(subItemCount); end; end. 

Thanks!


Change The proposed fix from Sertac Akyuz works great, although I can’t use it because changing the Delphi source code is not a solution for my project. An error is reported.

Change Deleted the second question, which was inadvertently included in the first post, and opened a new question (see related question and revision question).

Update : the reported error is now closed as fixed, as Delphi XE2 Update 4 .

+6
source share
1 answer

Call the UpdateItems method after you sort the columns. For instance:.

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



Update:

In my tests, apparently, I still need the above call. But the real problem is that "there is an error in the Delphi list view control."

Duplicate the problem with a simple project:

  • Place the TListView control in the VCL form, set its ViewStyle to 'vsReport' and set FullDrag to 'true'.
  • Put the following code in 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'); 
  • Put a TButton on the form and put the code below in your OnClick handler:
     ListView1.Columns.Add.Caption := 'col 4'; 
  • Run the project and drag the column heading β€œcol 3” to the intermediate β€œcol 1” and β€œcol 2”. The following shows what you will see at the moment (everything is in order):

    list view after column drag
  • Click the button to add a new column, the list now opens:

    list view after adding column

    Note that "cell 2" has returned to its original position.

Error:

Columns a TListView ( TListColumn ) contain order information in the FOrderTag field. Whenever you change the order of a column (either by setting the Index property or by dragging the header), this FOrderTag updated accordingly.

Now, when you add a column to the TListColumns collection, the collection first adds a new TListColumn , and then calls the UpdateCols method. The following is the code for the UpdateCols TListColumns method in the 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 base API list control and then inserts them again. Notice how the code assigns an index counter to each inserted FOrderTag column:

  Items[I].FOrderTag := I; 

This is the order of the columns from left to right at this point in time. If the method is called whenever the columns are ordered differently than during creation, then this ordering is lost. And since the elements do not change their positions accordingly, all this is confused.

Fix:

The following modification of the method seems to work as hard as I tested, you need to do more tests (obviously, this fix does not apply to all possible cases, see the comments to torno below):

 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 place the modified copy of "comctrls.pas" in the project folder. Otherwise, you can continue to correct the code at runtime or write a bug report and wait for the fix.

+7
source

Source: https://habr.com/ru/post/902309/


All Articles