Как правильно установить размер столбца ListView в соответствии с его содержимым?

У меня есть несколько элементов управления представлением списка (TListView), которые используются для отображения данных. Все эти представления списка установлены в режим «Подробности», и всем свойствам «SmallIcons» назначен TImageList.

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

Во-первых, я попытался установить ширину столбца на «-1» и «-2» для их автоматического изменения размера: мало того, что это не сработало идеально (некоторые столбцы содержат локальные символы — я использую D6, а это означает, что строки ANSI - слишком малы), но это также сделало отображение столбца чрезвычайно медленным (до 30 секунд для отображения представления списка с 6 столбцами и 150 элементами, когда оно мгновенное с фиксированной шириной).

Я попытался использовать GetTextExtent для каждой ячейки, чтобы получить ожидаемую ширину текста, добавив некоторое поле (от 2 до 10 пикселей) и расширив ширину столбца, если она меньше расчетной ширины текста. К первому столбцу (Items.caption) применяется специальная обработка, учитывающая отображение значка (я добавляю ширину значка плюс поле к ширине текста ячейки).

Это тоже не сработало: во многих случаях (например, отображение даты в формате «гггг/мм/дд чч:нн:сс» приводит к тому, что текст слишком велик, чтобы поместиться в столбце).

Думая, что проблема может быть связана с движком темы окна, я переключился на использование GetThemeTextExtent вместо GetTextExtent, но получил тот же результат.

Единственное, что, кажется, работает, — это добавить произвольно большое поле (20 пикселей) к ширине каждого столбца, но, конечно, это приведет к тому, что столбцы будут больше, чем они должны быть.

Итак, есть ли альтернативная стратегия? Мне не нужно ничего, кроме чего-то, что вычислит правильную ширину один раз: когда список будет впервые заполнен. Код, стоящий за «нажатием разделителя столбцов», работает просто отлично, но я не могу найти, как вызвать его с помощью кода (ну, я думаю, я мог бы отправлять сообщения о двойном щелчке в заголовок напрямую как хак)

Для пояснения, вот что я пробовал в следующем коде:

(в случае вызова выполняется вызов ListView.canvas.Font.Assign(ListView.font). Этого нет в этих функциях, потому что достаточно одного присваивания, но код зацикливается на всех столбцах списка без автоматического размера).

Изменить

Моя первая попытка использования Windows Theme API:

function _GetTextWidth1(AText: widestring; IsHeader: boolean = false): Integer;
var
  ATheme: HTheme;
  rValue: TRect;
  iPartID: integer;
  AWidetext: WideString;
const
  LVP_GROUPHEADER  = 6;
begin
  // try to get text width using theme API
  ZeroMemory(@rValue, SizeOf(rValue));
  ATheme := OpenThemeData(ListView.Handle, 'LISTVIEW');
  try
    if not IsHeader then
      iPartID := LVP_LISTITEM
    else
      iPartID := LVP_GROUPHEADER;
    AWidetext := AText;
    GetThemeTextExtent( ATheme,
                        ListView.Canvas.Handle,
                        iPartID,
                        LIS_NORMAL,
                        PWideChar(AWidetext),
                        -1,
                        DT_LEFT or DT_SINGLELINE or DT_CALCRECT,
                        nil,
                        rValue
                        );
  finally // wrap up
    CloseThemeData(ATheme);
  end;    // try/finally
  result := rValue.Right;
end;

следующая попытка использования DrawText/DrawTextW:

function _GetTextWidth2(AText: widestring; IsHeader: boolean = false): Integer;
var
  rValue: TRect;
  lFlags: Integer;
begin
  // try to get text width using DrawText/DrawTextW
  rValue := Rect(0, 0, 0, 0);
  lFlags := DT_CALCRECT or DT_EXPANDTABS or DT_NOPREFIX or DT_LEFT or DT_EXTERNALLEADING;
  DrawText(ListView.canvas.Handle, PChar(AText), Length(AText), rValue, lFlags);
  //DrawTextW(ListView.canvas.Handle, PWideChar(AText), Length(AText), rValue, lFlags);
  result := rValue.Right;
end;

Третья попытка использования функции Delphi TextWidth

function _GetTextWidth3(AText: widestring; IsHeader: boolean = false): Integer;
begin
  // try to get text width using delphi wrapped around GetTextExtentPoint32
  result := ListView.canvas.TextWidth(Atext);
end;

Во всех случаях я добавляю поле к полученной ширине: я пробовал значения до 20 пикселей. Я также принимаю во внимание возможность того, что в представлении используются значки (в этом случае я снова добавляю ширину значка плюс поля к первому столбцу).


person Stephane    schedule 20.01.2014    source источник
comment
Это можно сделать с помощью DrawText с флагом DT_CALCRECT, например: как правильно использовать drawtext dt calcrect"> stackoverflow.com/questions/16343147/   -  person Jerry Dodge    schedule 20.01.2014
comment
Спасибо, но я только что попробовал, и использование DrawText дает точно тот же результат, что и использование GetTextExtent.   -  person Stephane    schedule 21.01.2014


Ответы (1)


Вы можете использовать метод canvas.TextWidth. Но обязательно используйте холст TListView (не другой, т.е. TForm) и сначала назначьте шрифт холсту из TListView. Например:

var
  s: integer;
begin
  ListView1.AddItem('test example item', nil);
  ListView1.canvas.Font.Assign(ListView1.font);
  s := ListView1.canvas.TextWidth(ListView1.Items[0].Caption) + 10; //this "+10" is a small additional margin
  if s > ListView1.Columns[0].Width then
    ListView1.Columns[0].Width := s;

Он отлично работает для меня.

person GAD ZombiE    schedule 21.01.2014
comment
Спасибо за ваш комментарий, но, к сожалению, это тоже не работает. Это неудивительно, учитывая, что внутри он использует GetTextExtentPoint32 и поэтому делает то же самое, что и моя первая попытка. Однако, спасибо. - person Stephane; 24.01.2014
comment
Я думаю, что это должно работать. Я попробовал это, и это работает нормально. Может быть, попробовать это в новом проекте. Это работает? Если да, в вашем проекте должно быть что-то, что изменяет метод getTextExtentPoint32. Может быть, вы изменили где-то свойство PixelsPerInch? О, и я забыл рассчитать ширину значка в моем примере. - person GAD ZombiE; 25.01.2014
comment
Нет, извините: я проверил это снова, и это не работает. Он что-то делает: ширина столбцов почти правильная, но он по-прежнему обрезает практически каждый столбец, содержащий не более пары слов. Кажется хуже, когда в ячейках есть локальные или специальные символы (символы с французским акцентом, ›, ‹ в моих образцах). - person Stephane; 27.01.2014
comment
Что ж, это может быть подсказкой. Если специальный символ больше нарушает ширину, это может быть проблемой при преобразовании между строками ansi и unicode. Может быть, у вас есть ansistring, но вы передаете его функции, которая принимает строку в юникоде или наоборот? Как этот метод работает, когда у вас есть простая строка с символами от A до Z и больше ничего? - person GAD ZombiE; 28.01.2014
comment
Я пробовал это несколькими способами. Во-первых, это приложение D6, поэтому все в ANSI: по умолчанию юникода нет. Несмотря на это, я попытался использовать систему тем, чтобы получить правильную ширину, и для этого мне пришлось преобразовать строку в строку юникода (в этом случае это не проблема). Результат на 100% соответствует тому, что я получаю от GetTextExtentPoint32. Я добавлю несколько примеров кода, чтобы проиллюстрировать, что я пробовал. - person Stephane; 28.01.2014