TListView: VCL теряет порядок столбцов, если вы добавляете столбец

Я пытаюсь добавить столбец между существующими столбцами в TListView. Для этого я добавляю новый столбец в конце и перемещаю его, устанавливая индекс в указанное значение. Это работает, пока не добавится еще один новый столбец.

Что я сделал: добавьте столбец в последней позиции (Columns.Add) и добавьте подэлемент в последней позиции (Subitems.Add). После этого я перемещаю столбец, устанавливая его индекс в правильное положение. Это прекрасно работает, если только один столбец добавляется. При добавлении второго нового столбца подпункты облажаются. Новый подпункт первого столбца перемещается в последнюю позицию, например, так:

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

Я был бы очень рад, если бы кто-то мог помочь!

Например, есть ли команда или сообщение, которое я могу отправить в ListView, чтобы он обновлял или сохранял свое сопоставление Столбец -> Подэлемент, которое я мог бы использовать после добавления первого нового столбца и его подэлементов, чтобы я мог обработать второй новый столбец так же, как и первый.

Или это просто ошибка столбца TListViews -> обработка подэлемента или TListColumns...?

пример кода для приложения vcl-форм (назначьте событие 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.

Спасибо!


Редактировать: предлагаемое исправление от Sertac Akyuz работает нормально, хотя я не могу его использовать, потому что изменение исходного кода Delphi не является решением для моего проекта. Об ошибке сообщается.

Изменить: удалил второй вопрос, который был непреднамеренно включен в первый пост и открыл новый вопрос (см. Связанный вопрос и пересмотр вопроса).

Обновление: сообщенная ошибка теперь закрыта, как исправлено в Delphi XE2 Update 4.

1 ответ

Решение

Позвоните UpdateItemsметод после того, как вы расположили столбцы. Например:

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



Обновить:

В моих тестах мне все еще, кажется, нужен вышеупомянутый вызов в некотором случае. Но настоящая проблема заключается в том, что "в элементе управления списком Delphi есть ошибка".

Дублирование проблемы с простым проектом:

  • Поместите TListView управление в форме VCL, установите его ViewStyle "vsReport" и установить FullDrag на "правда".
  • Поместите приведенный ниже код в OnCreate обработчик формы:
    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');
    
  • Поместите TButton в форме, и поместите приведенный ниже код в его OnClick обработчик:
    ListView1.Columns.Add.Caption := 'col 4';
  • Запустите проект и перетащите заголовок столбца "col 3" между "col 1" и "col 2". На картинке ниже вы увидите (все в порядке):

    представление списка после перетаскивания столбца

  • Нажмите кнопку, чтобы добавить новый столбец, теперь представление списка становится:

    представление списка после добавления столбца

    Обратите внимание, что "ячейка 2" вернула свое первоначальное положение.

ошибка:

Столбцы TListView (TListColumn) содержит информацию о своем заказе в FOrderTag поле. Всякий раз, когда вы меняете порядок столбца (либо путем установки Index свойство или перетаскивая заголовок), это FOrderTag обновляется соответственно.

Теперь, когда вы добавляете столбец в TListColumns коллекция, коллекция сначала добавляет новый TListColumn а затем вызывает UpdateCols метод. Ниже приведен код UpdateCols метод TListColumns в 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;


Приведенный выше код удаляет все столбцы из базового элемента управления представления списка API, а затем вставляет их заново. Обратите внимание, как код назначает каждому вставленному столбцу FOrderTag счетчик индекса:

      Items[I].FOrderTag := I;

Это порядок столбцов слева направо в данный момент времени. Если метод вызывается всякий раз, когда столбцы упорядочены иначе, чем во время создания, то это упорядочение теряется. И поскольку предметы не меняют своих позиций соответственно, все это путается.

Fix:

Приведенная ниже модификация метода, казалось, работала так же мало, как я тестировал, вам нужно провести больше тестов (очевидно, это исправление не охватывает все возможные случаи, подробности смотрите в комментариях 'torno ниже):

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;

Если вы не используете пакеты, вы можете поместить измененную копию 'comctrls.pas' в папку вашего проекта. В противном случае вы можете продолжить исправление кода во время выполнения или подать отчет об ошибке и дождаться исправления.

Другие вопросы по тегам