【问题标题】:TListView: VCL loses the order of columns if you add a columnTListView:如果添加列,VCL 会丢失列的顺序
【发布时间】:2011-11-24 14:10:55
【问题描述】:

我正在尝试在 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 源代码对我的项目没有解决方案。已报告错误。

编辑:删除了第一个帖子中无意包含的第二个问题并打开了新问题(请参阅链接问题和问题修订)。

更新reported bug 现在已关闭,与 Delphi XE2 Update 4 一样已修复。

【问题讨论】:

  • 我猜某处缺少刷新/更新。虽然不确定它是什么。也就是说,这听起来像是虚拟模式列表视图会大放异彩的另一种情况。
  • 但它们仅适用于 .Net,不是吗?我在等效的 C#.Net 项目中遇到了同样的问题,也许可以在那里使用它。
  • 没有。 Windows 列表视图支持虚拟模式,Delphi 很好地封装了它。如果您在运行时操作列,那绝对是要走的路。这里的其他人都会将您指向虚拟树视图,但我自己喜欢本机控件。
  • 好的,谢谢。 ill have a look at this but i don't think i can use it because the project is quite large and changing one of its 核心组件可能不是最好的主意:)
  • 查看了 VCL 代码,似乎在更新列索引的某处,VCL 的数据与涉及子项的 Windows 列表视图数据不同步。使用 Begin/EndUpdate 会产生效果,不幸的是到目前为止还不是想要的效果。您最好的选择可能确实是按照@David 的建议将 ListView 置于虚拟模式。这样,您的应用程序始终会被要求在每个单元格中显示它需要显示的数据,并且 vcl 或窗口中没有隐藏的“副本”。

标签: delphi dynamic tlistview subitem


【解决方案1】:

排列好列后调用UpdateItems 方法。例如:

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



更新:

在我的测试中,在某些情况下我似乎仍然需要上述调用。但真正的问题是“Delphi 列表视图控件中存在错误”

用一个简单的项目复制问题:

  • 在 VCL 表单上放置一个TListView 控件,将其ViewStyle 设置为“vsReport”并将FullDrag 设置为“true”。
  • 将以下代码放入表单的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 方法。下面是D2007 VCL中TListColumnsUpdateCols方法的代码:

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;

这是该时间点从左到右的列顺序。如果每当列的排序与创建时不同时调用该方法,则该排序将丢失。而且由于物品没有相应地改变它们的位置,所以一切都变得混乱了。

修复:

以下对该方法的修改似乎只适用于我测试的时间,您需要进行更多测试(显然此修复不涵盖所有可能的情况,请参阅下面的 'torno's cmets 了解详细信息):

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”的修改副本放入您的项目文件夹。否则,您可能会寻求运行时代码修补,或提交错误报告并等待修复。

【讨论】:

  • 不幸的是,这并不能解决问题:(添加第二个新列后描述的行为仍然可以在添加您的行后使用上面的代码重现。
  • 你试过第一个例子吗?在正确的位置插入列和子项?或代码sn-p?不幸的是,我现在必须得到,可以在星期一再试一次...感谢您的建议
  • @torno - 不,我刚刚测试了 sn-p 只插入一列。应该仔细阅读问题...
  • 谢谢你,sertac! thats a nice point. Ill 会尝试验证您的修复,如果成功,我会报告错误。
  • sertac,这适用于现有项目。但是如果您添加一个新项目并用“item.SubItems.Add('new ' + IntToStr(current_subitem_index))”填充子项目。你ll see, that the order of the subitems for new items somehow still is messed up. e.g. just add a column to existing 3, move it to index 1, add a new item with subitems. the subitem[0] still has the wrong index "subitems.count-1" instead of 0. i think it may be, because the updatecols is just called after adding the column, not after overwriting its 索引。我会尝试,如果插入列有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-16
  • 1970-01-01
  • 1970-01-01
  • 2019-11-25
  • 2023-02-23
  • 1970-01-01
  • 2021-01-04
相关资源
最近更新 更多