为什么ListView呈现某些字符的速度这么慢?

MrC*_*rCC 5 .net c# listview rendering winforms

更新1:我同时编写了MFC-C ++实现和老式的Win32应用程序,并录制了一段视频,演示了问题的严重程度:

https://www.youtube.com/watch?v=f0CQhQ3GgAM

由于老式的Win32应用程序没有出现此问题,因此使我相信C#和MFC都使用必须导致此问题的同一呈现API(基本上是怀疑我的问题可能出在OS /图形驱动程序级别)。


原始帖子:

当不得不在ListView中显示一些REST数据时,我遇到了一个非常特殊的问题:

对于某些输入,在水平滚动时,ListView渲染在字面上会缓慢地进行爬网。

在我的系统上,使用典型的带有“ OptimizedDoubleBuffer”的子类ListView,在ListView中只有6个项目时,在滚动到我可以看到标题“游动”的点时,渲染速度会变慢,即在编译过程中项目和标题的渲染滚动不匹配。

对于具有10个项目的常规非子类ListView,我可以从字面上看到滚动时分别绘制每个项目(重新绘制大约需要1-2s)。

这是示例代码(是的,我知道它们看起来像熊和蝴蝶的表情;毕竟,从用户提供的数据中发现了这个问题):

using System;
using System.Windows.Forms;

namespace SlowLVRendering
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.Load += new System.EventHandler(this.Form1_Load);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            const string slow = "?(  ´??°)? ?????~ ? ( ?(  ´??°)?  ? ?´º?º ?? ) (????)??(  ´??°)? ?????~ ? ( ?(  ´??°)?  ? ?´º?º ?? ) (????)?";
            ListView lv = new ListView();
            lv.Dock = DockStyle.Fill;
            lv.View= View.Details;
            for (int i = 0; i < 2; i++) lv.Columns.Add("Title "+i, 500);
            for (int i = 0; i < 10; i++)
            {
                var lvi = lv.Items.Add(slow);
                lvi.SubItems.Add(slow);
            }
            Controls.Add(lv);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有人可以解释这个问题是什么以及如何解决?

MrC*_*rCC 1

我相信我已经将问题缩小到Visual Styles。注释掉Application.EnableVisualStyles();static void Main在滚动过程中带来巨大的性能提升,尽管与我在更新 1 中提到的视频中所示的 Win32 应用程序的性能相差甚远。

当然,这样做的缺点是应用程序中的所有控件都会看起来“旧”。因此,我尝试通过以下方式选择性地禁用/启用视觉样式

    [DllImport("uxtheme", ExactSpelling = true, CharSet = CharSet.Unicode)]
    public extern static Int32 SetWindowTheme(IntPtr hWnd, String textSubAppName, String textSubIdList);
Run Code Online (Sandbox Code Playgroud)

Win32.SetWindowTheme(lv.Handle, " ", " ");按照 MSDN 文档中的描述使用。最合乎逻辑的事情是让大多数控件的视觉样式保持活动状态,并在性能关键的控件中关闭它。然而,ListView的一部分似乎故意忽略视觉样式是禁用还是启用,即报表模式下列表视图的列标题:

列标题忽略关闭视觉样式

(注意列标题​​与滚动条相比的外观)

因此,除非有人知道如何在列表视图列标题上强制关闭视觉样式,否则这是一种“选择你的毒药”的情况:要么注释掉 Application.EnableVisualStyles(); 并且有一个丑陋的用户界面,或者保留它并冒着不可预测的渲染器速度减慢的风险。

如果你选择第一个选择,你可以通过子类化 ListView 并短路 WM_REFLECT_NOTIFY 消息来获得另一个巨大的性能提升(感谢 SteveFerg 的原创):

public class ListViewWithoutReflectNotify : ListView
{
    [StructLayout(LayoutKind.Sequential)]
    private struct NMHDR
    {
        public IntPtr hwndFrom;
        public uint idFrom;
        public uint code;
    }

    private const uint NM_CUSTOMDRAW = unchecked((uint) -12);


    public ListViewWithoutReflectNotify()
    {

    }
    protected override void WndProc(ref Message m)
    {
        // WM_REFLECT_NOTIFY
        if (m.Msg == 0x204E)
        {
            m.Result = (IntPtr)0;
            return;

            //the if below never was true on my system so i 'shorted' it
            //delete the 2 lines above if you want to test this yourself
            NMHDR hdr = (NMHDR) m.GetLParam(typeof (NMHDR));
            if (hdr.code == NM_CUSTOMDRAW)
            {
                Debug.WriteLine("Hit");
                m.Result = (IntPtr) 0;
                return;
            }
        }

        base.WndProc(ref m);
    }
}
Run Code Online (Sandbox Code Playgroud)

禁用视觉样式和子类化可以使渲染速度几乎与 Win32 C 应用程序相当。但是,我不完全理解缩短 WM_REFLECT_NOTIFY 的潜在后果,因此请谨慎使用。

我还检查了 Win32 应用程序,并确认您可以通过添加清单来真正降低应用程序的渲染性能,例如如下所示:

// enable Visual Styles
#pragma comment( linker, "/manifestdependency:\"type='win32' \
                         name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
                         processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
                         language='*'\"")
Run Code Online (Sandbox Code Playgroud)