Как избавиться от ограничения вертикальной прокрутки TListBox?

Я реализовал просмотрщик журнала, используя TListBox в виртуальном режиме.

Он отлично работает (для всего кода, который я написал), отображает содержимое, как и ожидалось (я даже легко добавил горизонтальную полосу прокрутки), но я предполагаю, что достиг некоторого предела вертикальной полосы прокрутки.

То есть, когда я прокручиваю вертикальную полосу сверху вниз, она не прокручивает содержимое до конца списка, а только до некоторого предела.

Знаете ли вы возможность избавиться от этого ограничения? Я пробовал с SetScrollInfo, но это не сработало, поскольку лимит звучит не на полосе прокрутки, а в TListBox сам.

Я знаю решение создания выделенного TCustomControl: в этом случае SetScrollInfo будет работать как положено. Но кто-нибудь знает о решении / уловке, чтобы все еще использовать TListBox?

Редактировать: чтобы было понятно - я не прошу (стороннее) компонентное решение, но хочу знать, есть ли какое-то низкоуровневое сообщение GDI для отправки в стандарт TListBox переопределить этот предел. Если его нет, я пойду на выделенный TCustomControl решение.

Вот код, использующий TSCROLLINFO:

procedure ScrollVertHuge(Handle: HWND; count: integer);
var Scroll: TSCROLLINFO;
begin
  Scroll.cbSize:= sizeof(Scroll);
  Scroll.fMask := SIF_DISABLENOSCROLL or SIF_RANGE;
  Scroll.nMin := 0;
  Scroll.nMax := count;
  SetScrollInfo(Handle,SB_VERT,Scroll,false);
end;

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

2 ответа

Решение

Приведенное ниже, вероятно, следует рассматривать в качестве обходного пути для некорректного поведения ОС, поскольку, если темы не включены, стандартная оконная процедура элемента управления списком довольно хорошо справляется с отслеживанием большого пальца. По какой-то причине, когда темы включены (тест здесь показан с Vista и более поздними версиями), элемент управления, похоже, полагается на данные положения прокрутки размером в Word WM_VSCROLL,

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

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    procedure ListBox1Data(Control: TWinControl; Index: Integer;
      var Data: string);
    procedure ListBox1DrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure FormCreate(Sender: TObject);
  end;

[...]

procedure TForm1.FormCreate(Sender: TObject);
begin
  ListBox1.Count := 600000;
end;

procedure TForm1.ListBox1Data(Control: TWinControl; Index: Integer;
  var Data: string);
begin
  Data := IntToStr(Index) + ' listbox item number';
end;

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  // just simple drawing to be able to clearly see the items
  if odSelected in State then begin
    ListBox1.Canvas.Brush.Color := clHighlight;
    ListBox1.Canvas.Font.Color := clHighlightText;
  end;
  ListBox1.Canvas.FillRect(Rect);
  ListBox1.Canvas.TextOut(Rect.Left + 2, Rect.Top + 2, ListBox1.Items[Index]);
end;


Чтобы увидеть проблему, просто прокрутите полосу прокрутки большим пальцем, и вы заметите, как элементы обертываются, чтобы начинаться с начала для каждого 65536, как описано Арно в комментариях к вопросу. И когда вы отпустите большой палец, он будет привязан к элементу в верхней части High(Word),


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

type
  TListBox = class(stdctrls.TListBox)
  private
    procedure WMVScroll(var Msg: TWMVScroll); message WM_VSCROLL;
  end;

[...]

procedure TListBox.WMVScroll(var Msg: TWMVScroll);
var
  Info: TScrollInfo;
begin
  // do not intervene when themes are disabled
  if ThemeServices.ThemesEnabled then begin
    Msg.Result := 0;

    case Msg.ScrollCode of
      SB_THUMBPOSITION: Exit; // Nothing to do, thumb is already tracked
      SB_THUMBTRACK:
        begin
          ZeroMemory(@Info, SizeOf(Info));
          Info.cbSize := SizeOf(Info);
          Info.fMask := SIF_POS or SIF_TRACKPOS;
          if GetScrollInfo(Handle, SB_VERT, Info) and
              (Info.nTrackPos <> Info.nPos) then
            TopIndex := TopIndex + Info.nTrackPos - Info.nPos;
        end;
      else
        inherited;
    end;
  end else
    inherited;
end;

Для пользовательского средства просмотра журнала, которое я написал, я использую TListView в виртуальном режиме, а не TListBox, Прекрасно работает, без ограничений 32K, не нужно возиться с SetScrollInfo() совсем. Просто установите Item.Count а остальное обрабатывается автоматически. Это даже имеет OnDataHint событие, которое можно использовать для оптимизации доступа к данным, позволяя загружать только те данные, которые TListView на самом деле нужно. Вы не получите это с TListBox,

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