如何摆脱TListBox垂直滚动限制?

发布于 2024-11-30 12:25:58 字数 1079 浏览 3 评论 0原文

我已经使用 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 文章,这些文章令人困惑。

I've implement a log viewer using a TListBox in virtual mode.

It works fine (for all the code I wrote), displays the content as expected (I even added an horizontal scrollbar easily), but I guess I've reached the some kind of limit of the vertical scrollbar.

That is, when I scroll the vertical bar from the top to the bottom, it will not scroll the content to the end of the list, but only to some limit.

Do you know any possibility to get rid of this limit? I tried with SetScrollInfo, but it didn't work since the limit sounds to be not in the scrollbar, but in the TListBox itself.

I know the solution of creating a dedicated TCustomControl: in this case, the SetScrollInfo will work as expected. But does anyone know about a solution/trick to still use TListBox?

Edit: to make it clear - I don't ask for a (third-party) component solution, but to know if there is some low-level GDI message to send to the standard TListBox to override this limit. If there is none, I'll go to the dedicated TCustomControl solution.

Here is the code using 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;

To precise the issue: Adding and drawing both work, of course (my tool works as exepected), but what does not work is the vertical scrollbar dragging. I renamed the title of the question, and got rid of the deprecated MSDN articles, which are confusing.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

如梦亦如幻 2024-12-07 12:25:58

下面的内容可能应该被视为有缺陷的操作系统行为的解决方法,因为除非启用主题,否则列表框控件的默认窗口过程可以很好地处理拇指跟踪。由于某种原因,当启用主题时(此处测试显示的是 Vista 及更高版本),该控件似乎依赖于 WM_VSCROLL 的 Word 大小的滚动位置数据。

首先,一个复制问题的简单项目,下面是一个所有者绘制虚拟 (lbVirtualOwnerDraw) 列表框,其中包含约 600,000 个项目(由于项目数据未缓存,因此不需要花一点时间即可填充该框) )。高列表框将有助于轻松跟踪行为:

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 个项目如 Arnaud 在对问题的评论。当您松开拇指时,它将捕捉到顶部 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;

The below probably should be considered as a work-around for defective OS behavior, since, unless themes are enabled, the default window procedure of a listbox control handles thumb-tracking quite well. For some reason, when themes are enabled (test here shows with Vista and later), the control seems to rely upon the Word sized scroll position data of WM_VSCROLL.

First, a simple project to duplicate the problem, below is an owner draw virtual (lbVirtualOwnerDraw) list box with some 600,000 items (since item data is not cached it doesn't take a moment to populate the box). A tall listbox will be good for easy following the behavior:

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;

To see the problem just thumb-track the scroll bar, you'll notice how the items are wrapped to begin from the start for every 65536 one as described by Arnaud in the comments to the question. And when you release the thumb, it will snap to an item in the top High(Word).

Below workaround intercepts WM_VSCROLL on the control and performs thumb and item positioning manually. The sample uses an interposer class for simplicity, but any other sub-classing method would do:

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;
你的背包 2024-12-07 12:25:58

对于我编写的自定义日志查看器,我在虚拟模式下使用 TListView,而不是 TListBox。效果很好,没有 32K 限制,根本不需要摆弄 SetScrollInfo()。只需设置 Item.Count ,其余的就会自动处理。它甚至还有一个 OnDataHint 事件,可让您仅加载 TListView 实际需要的数据,从而优化数据访问。使用 TListBox 无法实现这一点。

For a custom log viewer I wrote, I use a TListView in virtual mode, not a TListBox. Works great, no 32K limits, no need to fiddle with SetScrollInfo() at all. Just set the Item.Count and the rest is handled automatically. It even has an OnDataHint event that can be used to optimize data access by letting you load only the data that the TListView actually needs. You don't get that with a TListBox.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文