【问题标题】:how auto size the columns width of a list view in virtual mode?如何在虚拟模式下自动调整列表视图的列宽?
【发布时间】:2012-03-04 13:27:42
【问题描述】:

当我使用 TListView (ViewStyle = vsReport) 时,我可以在每列的 Width 属性中设置 LVSCW_AUTOSIZELVSCW_AUTOSIZE_USEHEADER 值来自动调整列的宽度,现在我开始在虚拟模式下使用 Listview,但不会根据这些值修改列的宽度。所以问题是:当 lisvtiew 处于虚拟模式时,如何调整列的宽度以适应内容或标题?

【问题讨论】:

  • 在虚拟模式下,大小应该基于可见项目,因为这是所有控件都必须使用的。
  • @DavidHeffernan 已经怀疑过类似的事情,但是有一些功能可以自动调整列吗?还是我必须编写自己的函数?
  • 您必须编写自己的函数,因为公共控件永远不会询问您控件中的所有数据,只会询问可见的行。
  • 我同意。这样做的一种方法是遍历列中的所有字符串,并对每个项目执行 Canvas.TextWidth。
  • @Elling,这是唯一的方法。在虚拟模式下,您仅查询可见项目的数据,因此列表视图永远不会知道所有项目的标题(这就是 LVSCW_AUTOSIZE 这样做的原因)。关于额外的像素,该值是推测性的(甚至 MS 也没有说明如何计算它们,请参阅下面的答案)。

标签: delphi delphi-xe tlistview


【解决方案1】:

考虑一下RRUZ写的helper function unit

辅助函数摘录:

procedure AutoResizeColumn(const Column:TListColumn;const Mode:Integer=LVSCW_AUTOSIZE_BESTFIT);
procedure AutoResizeColumns(const Columns : Array of TListColumn;const Mode:Integer=LVSCW_AUTOSIZE_BESTFIT);
procedure AutoResizeListView(const ListView : TListView;const Mode:Integer=LVSCW_AUTOSIZE_BESTFIT);

模式(参数)可以是:

  • LVSCW_AUTOSIZE_BESTFIT
  • LVSCW_AUTOSIZE
  • LVSCW_AUTOSIZE_USEHEADER

我希望它可以作为满足您要求的良好起点。

【讨论】:

  • 这正是我设置列宽的方式,但仅在列表视图未处于虚拟模式时有效。
【解决方案2】:

由于虚拟模式下的列表视图事先不知道项目标题(因为它只要求可见区域的数据),它也无法知道最宽的宽度,所以这就是 autosize 标志的原因的LVM_SETCOLUMNWIDTH 的行为是这样的。

因此,唯一的方法是编写一个自定义函数,该函数将查询您的所有数据、测量所有未来字幕的文本宽度并将列宽设置为最宽的值。

以下示例显示了如何执行此操作。它使用ListView_GetStringWidth 宏来计算文本宽度(这似乎是最自然的方法)。然而问题是文本填充的值。正如文档中所述:

ListView_GetStringWidth 宏返回精确的宽度,以像素为单位, 的指定字符串。如果您使用返回的字符串宽度作为 调用 ListView_SetColumnWidth 宏时的列宽, 字符串将被截断。检索列宽可以 包含字符串而不截断它,您必须在 返回字符串宽度。

但他们没有提到如何获得填充值(似乎they won't 这样做)。有人说(例如here)将 6 px 用于 item 的 padding 和 12 px 用于 subitem 的 padding 就足够了,但事实并非如此(至少对于 Windows 7 上的此示例而言)。

///////////////////////////////////////////////////////////////////////////////
/////   List View Column Autosize (Virtual Mode)   ////////////////////////////
///////////////////////////////////////////////////////////////////////////////

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, StdCtrls,
  Forms, Dialogs, StrUtils, ComCtrls, CommCtrl;

type
  TSampleRecord = record
    Column1: string;
    Column2: string;
    Column3: string;
  end;
  TSampleArray = array [0..49] of TSampleRecord;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    SampleArray: TSampleArray;
    procedure AutoResizeColumn(const AListView: TListView;
      const AColumn: Integer);
    procedure OnListViewData(Sender: TObject; Item: TListItem);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

///////////////////////////////////////////////////////////////////////////////
/////   TForm1.AutoResizeColumn - auto-size column   //////////////////////////
///////////////////////////////////////////////////////////////////////////////

// AListView - list view object instance
// AColumn - index of the column to be auto-sized

procedure TForm1.AutoResizeColumn(const AListView: TListView;
  const AColumn: Integer);
var
  S: string;
  I: Integer;
  MaxWidth: Integer;
  ItemWidth: Integer;
begin
  // set the destination column width to the column's caption width
  // later on we'll check if we have a wider item
  MaxWidth := ListView_GetStringWidth(AListView.Handle,
    PChar(AListView.Columns.Items[AColumn].Caption));
  // iterate through all data items and check if their captions are
  // wider than the currently widest item if so then store that value
  for I := 0 to High(SampleArray) do
  begin
    case AColumn of
      0: S := SampleArray[I].Column1;
      1: S := SampleArray[I].Column2;
      2: S := SampleArray[I].Column3;
    end;
    ItemWidth := ListView_GetStringWidth(AListView.Handle, PChar(S));
    if MaxWidth < ItemWidth then
      MaxWidth := ItemWidth;
  end;
  // here is hard to say what value to use for padding to prevent the
  // string to be truncated; I've found the suggestions to use 6 px
  // for item caption padding and 12 px for subitem caption padding,
  // but a few quick tests confirmed me to use at least 7 px for items
  // and 14 px for subitems
  if AColumn = 0 then
    MaxWidth := MaxWidth + 7
  else
    MaxWidth := MaxWidth + 14;
  // and here we set the column width with caption padding included
  AListView.Columns.Items[AColumn].Width := MaxWidth;
end;

///////////////////////////////////////////////////////////////////////////////
/////   TForm1.FormCreate - setup the list view and fill custom data   ////////
///////////////////////////////////////////////////////////////////////////////

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  ListView1.ViewStyle := vsReport;
  ListView1.Columns.Add.Caption := 'Column 1';
  ListView1.Columns.Add.Caption := 'Column 2';
  ListView1.Columns.Add.Caption := 'Column 3';
  ListView1.OwnerData := True;
  ListView1.OnData := OnListViewData;
  ListView1.Items.Count := High(SampleArray);

  for I := 0 to High(SampleArray) do
  begin
    SampleArray[I].Column1 := 'Cell [0, ' + IntToStr(I) + '] ' +
      DupeString('x', I);
    SampleArray[I].Column2 := 'Cell [1, ' + IntToStr(I) + '] ' +
      DupeString('x', High(SampleArray) - I);
    SampleArray[I].Column3 := '';
  end;
end;

///////////////////////////////////////////////////////////////////////////////
/////   TForm1.FormCreate - custom handler for OnData event   /////////////////
///////////////////////////////////////////////////////////////////////////////

procedure TForm1.OnListViewData(Sender: TObject; Item: TListItem);
begin
  Item.Caption := SampleArray[Item.Index].Column1;
  Item.SubItems.Add(SampleArray[Item.Index].Column2);
  Item.SubItems.Add(SampleArray[Item.Index].Column3);
end;

///////////////////////////////////////////////////////////////////////////////
/////   TForm1.Button1Click - auto-resize all 3 columns   /////////////////////
///////////////////////////////////////////////////////////////////////////////

procedure TForm1.Button1Click(Sender: TObject);
begin
  AutoResizeColumn(ListView1, 0);
  AutoResizeColumn(ListView1, 1);
  AutoResizeColumn(ListView1, 2);
end;

end.

【讨论】:

  • GetThemeData 是什么?找到填充值可能会很方便(当然只有在您启用主题时),但仍然如此。
  • 我认为这里真正需要的是基于可见虚拟集的实时自动加宽和/或自动缩短。
【解决方案3】:

这是避免列太窄的另一种可能的解决方案。但是,它需要一些关于您需要显示的数据的知识,所以它不是一个通用的解决方案..

使用最长/最宽的数据项构建 ListViewItem。切换到非虚拟模式并仅添加此最大 ListViewItem。根据最大项自动调整列宽,然后删除最大项,并切换回虚拟模式。例如:

// build a ListViewItem with longest data items
string[] items = new string[2];
items[0] = "999999"; // number
items[1] = "99:59:59.999"; // time hh:mm:ss.ttt
ListViewItem lviMax = new ListViewItem (items);
lv.VirtualMode = false; // switch to non-virtual mode
lv.Items.Clear (); // empty the row/line collection
lv.Visible = false; // so user doesnt see the fake values
lv.Items.Add (lviMax); // add line(s) with longest possible data items
lv.AutoResizeColumns (ColumnHeaderAutoResizeStyle.ColumnContent); // adjust column width
lv.AutoResizeColumns (ColumnHeaderAutoResizeStyle.HeaderSize); // adjust column width
lv.Items.Clear (); // empty row/line collection
lv.Visible = true;
lv.VirtualMode = true; // switch back to virtual mode

根据您的示例格式值,现在某些列可能有点太宽,但至少没有列会太窄..

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-28
    • 2011-08-08
    • 1970-01-01
    • 2013-04-22
    • 1970-01-01
    • 1970-01-01
    • 2015-06-10
    • 1970-01-01
    相关资源
    最近更新 更多