Как автоматический размер столбцов ширины представления списка в виртуальном режиме?

Когда я использую TListView (ViewStyle = vsReport), я могу авторизовать ширину столбцов, устанавливая значения LVSCW_AUTOSIZE или LVSCW_AUTOSIZE_USEHEADER в свойстве Width каждый столбец, теперь я начинаю использовать Listview в виртуальном режиме, но ширина столбцов не изменяется в соответствии с этими значениями. Поэтому возникает вопрос: как я могу настроить ширину столбцов для соответствия содержимому или заголовку, когда lisvtiew находится в виртуальном режиме?

Ответы

Ответ 1

Так как представление списка в виртуальном режиме не знает заголовки элемента заранее (потому что он запрашивает только данные видимой области), он также не может знать ширину самого широкого, так что причина, по которой флаг autosize LVM_SETCOLUMNWIDTH ведет себя таким образом.

Таким образом, единственный способ - написать пользовательскую функцию, которая будет запрашивать все ваши данные, измерять ширину текста всех будущих заголовков и устанавливать ширину столбца на значение самого широкого.

В следующем примере показано, как это сделать. Он использует макрос ListView_GetStringWidth для расчета ширины текста (это, по-видимому, самый естественный способ сделать это). Однако проблема заключается в значении заполнения текста. Как указано в документации:

Макрос ListView_GetStringWidth возвращает точную ширину в пикселях, указанной строки. Если вы используете возвращаемую ширину строки как ширины столбца в вызове макроса ListView_SetColumnWidth, строка будет усечена. Чтобы получить ширину столбца, которая может содержать строку без ее усечения, вы должны добавить Возвращаемая ширина строки.

Но они не упомянули, как получить значение заполнения (и, похоже, they won't). Некоторые люди говорят (например, here), что этого достаточно, чтобы использовать 6 пикселей для заполнения элемента и 12 пикселей для заполнения подэлемента, но это не (по крайней мере для этого примера в 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 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.

Ответ 2

Рассмотрим этот вспомогательный функциональный блок, написанный RRUZ.

Вывод вспомогательных функций:

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

Я надеюсь, что это послужит хорошей отправной точкой для вашего требования.

Ответ 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

В зависимости от ваших значений формата образца некоторые столбцы теперь могут быть слишком широкими, но по крайней мере ни один столбец не будет слишком узким.