【问题标题】:Virtual ListView with artifacts in short list for Windows 8 and higher适用于 Windows 8 及更高版本的虚拟 ListView 与短列表中的工件
【发布时间】:2025-12-10 01:15:01
【问题描述】:

我在 Winform 用户控件中有一个 ListView。 VirtualMode 为真,VirtualListSize 为 200。当 ListView 中的项目少于可见行时,我在(真实)项目下方得到奇怪的字符。当软件在 Windows 8、10 或 Windows Server 2012 上运行时会出现这些“工件”,但在 Windows 7 上不会出现。

有谁知道是什么导致了这些“人工制品”?我在所有创建 ListViewItems 的地方的 Title 中添加了一个字符“A”“B”等。所以我知道这个用户控件中的任何代码都没有创建它们。我添加了一个示例解决方案,显示了以下问题。 有时它们显示为汉字,有时只是随机的字母和字符组合。通常不超过 4 个字符。

[更新] 在最新版本的 Windows 10 上不会出现。

[Update2] 我能够在一个小样本解决方案上重现该问题。找到压缩文件here

【问题讨论】:

  • 您的 DefaultVirtualListSize 不是一个好主意。避免在 ListView 事件的事件处理程序中分配 VirtualListSize。
  • 确实,不分配 VirtualListSize 会删除这些工件。但是有几页空的 ListViewItems 似乎也有点奇怪的用户体验,你不觉得吗?
  • 您肯定能想出更好的方法来编写该代码吗?等待事件开始查找文件是没有意义的,无论如何它都会在毫秒后触发。
  • 需要记住的几件事:1)这不是我的代码。它是遗留应用程序的一部分。 2)我正在尝试修复这个错误。 3) 文件的显示只是一个例子。实物显示不同的对象,可能有数千个,这就是选择虚拟模式的原因。
  • 如果未初始化 VirtualListSize,则不显示任何内容。 Microsoft 在其example¨ 中为 VirtualListSize 分配了更高的值。他们说VirtualListSize property must be set to the size of the virtual list.@Hans Passant:那你为什么说 DefaultVirtualListSize 不是一个好主意?

标签: c# windows winforms listview virtual


【解决方案1】:

每当 ListView 中的项目数发生变化时,我都会将 ListView 的可见行数分配给 VirtualListSize 属性。

我使用以下类来确定 ListView 中可见行的数量:

public static class ListViewSizer
{
    const UInt32 LVM_FIRST = 0x1000;
    const UInt32 LVM_GETHEADER = (LVM_FIRST + 31);

    private static int GetHeaderHeight(ListView listView)
    {
        Rect rect = new Rect();
        IntPtr hwnd = SendMessage((IntPtr)listView.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
        if (hwnd != null)
        {
            if (GetWindowRect(new System.Runtime.InteropServices.HandleRef(null, hwnd), out rect))
            {
                return rect.Bottom - rect.Top;
            }
        }

        return -1;
    }

    public static int GetLastVisibleLine(ListView listView)
    {
        int firstVisibleIndex = listView.TopItem.Index;

        int heightOfFirstItem = listView.GetItemRect(firstVisibleIndex, ItemBoundsPortion.Entire).Height;

        // assuming each item has the same height (I think this is true for list view and details view)
        const int DefaultVisibleLines = 11;
        int visibleLines = heightOfFirstItem != 0 ? (int)Math.Ceiling((decimal)((listView.Height - GetHeaderHeight(listView)) / heightOfFirstItem)) : DefaultVisibleLines;

        int lastVisibleIndexInDetailsMode = firstVisibleIndex + visibleLines;
        return lastVisibleIndexInDetailsMode;
    }

    [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern bool GetWindowRect(System.Runtime.InteropServices.HandleRef hwnd, out Rect lpRect);

    [Serializable, System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct Rect
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
}

我知道这有点小题大做。工件仍然存在,但是您再也看不到它们了,因为 ListView 总是有尽可能多的行可以显示。这是我在不改变控件的情况下能想到的最好的方法。

【讨论】:

    最近更新 更多