为什么ListView渲染对于某些字符来说这么慢?

时间:2015-06-20 16:39:15

标签: c# .net winforms listview rendering

更新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-2秒)。

这是示例代码(是的,我知道这些看似熊和蝴蝶的表情;这个问题毕竟是从用户提供的数据中找到的):

seekToTime()

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

2 个答案:

答案 0 :(得分:1)

尝试了一些不同的东西后,这是我找到的最快的解决方案。仍有一点犹豫,但不是原来的解决方案。在Microsoft决定使用比GDI +更好的东西之前,除非你使用.NET 4及更高版本的WPF,否则它不会变得更好。哦,好吧。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SlowLVRendering
{
    [System.Runtime.InteropServices.DllImport("user32")]
    private static extern bool SendMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);

    private uint LVM_SETTEXTBKCOLOR = 0x1026;

    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 BufferedListView();
            // new ListView();
            //new ListViewWithLessSuck();

            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);
        //SendMessage(lv.Handle, LVM_SETTEXTBKCOLOR, IntPtr.Zero, unchecked((IntPtr)(int)0xFFFFFF));

        }

    }
    public class BufferedListView : ListView
    {
        public BufferedListView()
            : base()
        {
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        }
    }
class ListViewWithLessSuck : ListView
{
    [StructLayout(LayoutKind.Sequential)]
    private struct NMHDR
    {
        public IntPtr hwndFrom;
        public uint idFrom;
        public uint code;
    }

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

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x204E)
        {
            NMHDR hdr = (NMHDR)m.GetLParam(typeof(NMHDR));
            if (hdr.code == NM_CUSTOMDRAW)
            {
                m.Result = (IntPtr)0;
                return;
            }
        }

        base.WndProc(ref m);
    }

}

你可以亲眼看看差异。如果您取消注释SendMessage并将new BufferedListView();更改为new ListViewWithLessSuck();您可以看到更改。

答案 1 :(得分:1)

我相信我已将问题缩小到视觉样式。在Application.EnableVisualStyles();中评论static void Main可以在滚动期间带来巨大的性能提升,但远不及Win32应用的性能,如我在更新1中提到的视频所示。

这当然的缺点是应用程序中的所有控件都看起来“旧”。因此,我尝试通过

选择性地禁用/启用视觉样式
    [DllImport("uxtheme", ExactSpelling = true, CharSet = CharSet.Unicode)]
    public extern static Int32 SetWindowTheme(IntPtr hWnd, String textSubAppName, String textSubIdList);

并使用MSDN文档中描述的Win32.SetWindowTheme(lv.Handle, " ", " ");。最合乎逻辑的是将视觉样式保持为大多数控件的活动状态,如果关闭性能关键控制则关闭。但是,ListView的一部分似乎故意忽略视觉样式是否被禁用或启用,即报表模式下列表视图的列标题:

Column Header ignores turning off visual style

(注意列标题​​与滚动条的对比情况)

因此,除非有人知道如何在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);
    }
}

禁用视觉样式和子类化允许渲染速度几乎与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='*'\"")