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

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

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

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

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

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

Думая, что проблема может быть вызвана механизмом оконных тем, я переключился на использование 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;

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

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 пикселей. Я также принимаю во внимание возможность того, что представление использует значки (в этом случае я добавляю ширину значка плюс поле снова к первому столбцу).

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;

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

Другие вопросы по тегам