TListView:如果添加列,则VCL将丢失列的顺序 [英] TListView: VCL loses the order of columns if you add a column

查看:262
本文介绍了TListView:如果添加列,则VCL将丢失列的顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在TListView中的现有列之间添加一列。因此,我在最后添加新列,并将其设置为指定值的索引。直到添加另一个新列。



我做了什么:
在最后一个位置添加列(Columns.Add),并在最后添加子项位置(Subitems.Add)也是。之后,通过将列的索引设置到正确的位置来移动列。
只要只有一列被添加就可以正常工作。当添加第二个新列时,子项将被解除。第一列的新子项移动到最后一个位置,例如像这样:

  0 | 1 |新A |新B | 3 
标题|老子1 |老子3 |新Sub B |新的子A

如果有人可以帮助我会很高兴



例如,可能有一个命令或消息可以发送到ListView,以便它刷新或保存它的Column - > Subitem映射,我可以使用添加第一个新的列和它的subitems后使用,所以我可以使用与第一个相同的方式处理第二个新列。



或者这只是一个TListViews列 - >子项处理或TListColumns ...的错误? p>

vcl表单应用程序的示例代码(分配Form1.OnCreate事件):

  unit Unit1; 

接口

使用
Windows,消息,SysUtils,变体,类,图形,控件,表单,
对话框,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);
程序OnAddClick(Sender:TObject);
public
{公开声明}
end;

var
Form1:TForm1;

实现

{$ R * .dfm}

程序TForm1.FormCreate(发件人:TObject);
begin
listview:= TListView.Create(self);
with listview do
begin
左:= 8;
顶部:= 8;
宽度:= self.Width - 30;
身高:= self.Height - 100;
Anchors:= [akLeft,akTop,akRight,akBottom];
TabOrder = 0;
ViewStyle:= vsReport;
父母:=自我;
结束

initButton:= TButton.Create(self);
with initButton do
begin
left:= 8;
top:= listview.Top + listview.Height + 20;
宽度:= 75;
身高:= 25;
TabOrder = 1;
Caption:='init';
OnClick:= OnInitClick;
父母:=自我;
结束

editColumn:= TEdit.Create(self);
with editColumn do
begin
left:= initButton.Left + initButton.Width + 30;
top:= listview.Top + listview.Height + 20;
宽度:= 120;
身高:= 25;
TabOrder = = 2;
父母:=自我;
Caption:='';
结束

addColumn:= TButton.Create(self);
with addColumn do
begin
left:= editColumn.Left + editColumn.Width + 10;
top:= listview.Top + listview.Height + 20;
宽度:= 75;
身高:= 25;
TabOrder = 1;
启用:= true;
标题:='add';
OnClick:= OnAddClick;
父母:=自我;
结束

end;

procedure TForm1.OnInitClick(Sender:TObject);
var col:TListColumn;
i,j:integer;
item:TListItem;
begin
listview.Items.Clear;
listview.Columns.Clear;

//添加项
为I:= 0到2 do
begin
col:= ListView.Columns.Add;
col.Caption:='column'+ IntToStr(i);
col.Width:= 80;
结束

//添加列
为I:= 0到3 do
begin
item:= ListView.Items.Add;
item.Caption:='ItemCaption';

//为每列添加子项
for j:= 0 to 1 do
begin
item.SubItems.Add('subitem'+ IntToStr(j + 1));
结束
结束

subItemCount:= 5;
结束

procedure TForm1.OnAddClick(Sender:TObject);
var number:integer;
col:TListColumn;
i:整数;
ascii:char;
begin
listview.Columns.BeginUpdate;

number:= StrToInt(editColumn.Text);
ascii:= Chr(65 + number);

//创建新列
col:= TListColumn(ListView.Columns.add());
col.Width:= 80;
col.Caption:= ascii;

//将新的子项
添加到I:= 0到ListView.Items.Count-1 do
begin
ListView.Items [i] .SubItems。添加('subitem'+ ascii);
结束

//将其移动到指定的位置
col.Index:= number;

listview.Columns.EndUpdate;

Inc(subItemCount);
结束

结束。

谢谢!






编辑:Sertac Akyuz的建议修正工作正常,尽管我不能使用它,因为更改Delphi源代码对我的项目是没有解决方案。错误报告。



修改:删除了第二个问题,意外包含在第一篇文章中,并打开了新的问题(请参阅链接的问题和问题-revision)。



更新报告错误现已关闭,如Delphi XE2 更新4

解决方案

调用 UpdateItems 方法。例如:

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




更新:



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



使用简单的项目复制问题




    <在VCL表单上放置 TListView 控件,将其 ViewStyle 设置为vsReport,并设置 FullDrag to'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中保存其订购信息字段。无论何时更改列的顺序(通过设置索引属性或拖动标题),此 FOrderTag 相应地更新。



现在,当您向 TListColumns 集合添加列时,集合首先添加新的 TListColumn 然后调用 UpdateCols 方法。以下是D2007 VCL中 UpdateCols 方法的代码 TListColumns

 过程TListColumns.UpdateCols; 
var
I:整数;
LVColumn:TLVColumn;
begin
如果不是Owner.HandleAllocated然后退出;
BeginUpdate;
尝试
为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或LVCF_WIDTH;
fmt:= LVCFMT_LEFT;
cx:= Items [I] .FWidth;
结束
ListView_InsertColumn(Owner.Handle,I,LVColumn);
项目[I] .FOrderTag:= I;
结束
Owner.UpdateColumns
finally
EndUpdate;
结束
结束


上述代码从基础API列表视图控件中删除所有列,然后插入他们重新注意代码如何分配每个插入的列的 FOrderTag 索引计数器:

 物品[I] .FOrderTag:= I; 

这是这个时间点从左到右的列的顺序。如果每当列的排序与创建时间不同时调用该方法,那么排序就会丢失。而且由于项目不会相应地改变他们的位置,所有这些都会混合起来。



修复:



以下对该方法的修改似乎与我测试一样,您需要执行更多测试(显然,此修复程序不涵盖所有可能的情况,请参阅下面的torno的注释 / em>):

 程序TListColumns.UpdateCols; 
var
I:整数;
LVColumn:TLVColumn;
ColumnOrder:Integer数组;
begin
如果不是Owner.HandleAllocated然后退出;
BeginUpdate;
try
SetLength(ColumnOrder,Count);
for I:= Count - 1 downto 0 do begin
ColumnOrder [I]:= Items [I] .FOrderTag;
ListView_DeleteColumn(Owner.Handle,I);
结束

for I:= 0 to Count - 1 do
begin
with LVColumn do
begin
mask = = LVCF_FMT或LVCF_WIDTH;
fmt:= LVCFMT_LEFT;
cx:= Items [I] .FWidth;
结束
ListView_InsertColumn(Owner.Handle,I,LVColumn);
结束
ListView_SetColumnOrderArray(Owner.Handle,Count,PInteger(ColumnOrder));

Owner.UpdateColumns;
finally
EndUpdate;
结束
结束

如果您不使用包,可以将修改后的comctrls.pas副本放入您的项目夹。否则,您可以追踪运行时代码修补,或提交错误报告,并等待修复。


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 works, until adding another new column.

What I did: Add the column at last position (Columns.Add) and add the subitem at the last position (Subitems.Add) too. Afterwards I move the column by setting it's index to the correct position. This works fine as long as it's just one column that gets added. When adding a second new column, the subitems get screwed up. The new subitem of the first column is moved to the last position, e.g. like this:

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 could help!

For example, is there maybe a command or message I can send to the ListView so it refreshes or saves it's Column --> Subitem mapping that I could use after adding the first new column and it's subitems so I can handle the second new column the same way as the first.

Or is this just a bug of TListViews column-->subitem handling or TListColumns...?

example code for a vcl forms application (assign the Form1.OnCreate event):

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.

Thank you!


Edit: The suggested fix from Sertac Akyuz works fine, though I can't use it because changing the Delphi sourcecode is no solution for my project. Bug is reported.

Edit: Removed the second question that was unintended included in the first post and opened new question (See linked question and Question-revision).

Update: The reported bug is now closed as fixed as of Delphi XE2 Update 4.

解决方案

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):


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



    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.

这篇关于TListView:如果添加列,则VCL将丢失列的顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆