Why is ListView rendering too slow for certain characters?

Update 1: I wrote both an MFC-C ++ implementation and an old-school Win32 app and recorded a video demonstrating how bad the problem is:

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

Since the old school Win32 app does not exhibit this problem, it makes me think that C # and MFC are using the same rendering API, which should cause this problem (basically raising my suspicion that the problem might be on the OS / graphics driver level).


Original post:

While displaying some REST data inside a ListView, I ran into a very peculiar problem:

For certain inputs, rendering the ListView literally slows down scrolling when scrolling horizontally.

On my system, and with a typical ListView subclass with "OptimizedDoubleBuffer", having only 6 items in the ListView slows down rendering on scrolling to the point where I can see the headers "float", that is, the rendering of items and the headers during scrolling inconsistencies.

For a regular, unclassified ListView with 10 items, I can literally see each item being drawn separately when scrolling (repainting takes about 1-2 seconds).

Here is some sample code (and yes, I know they are similar to bear and butterfly emotions, this problem was found from user supplied data):

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);
        }
    }
}

      

Can someone please explain what the problem is and how to solve it?

+3


source to share


2 answers


I believe I have narrowed down the issue to Visual Styles . Commenting Application.EnableVisualStyles();

in static void Main

results in a huge increase in performance while scrolling, although it doesn't come close to the performance of a Win32 application as shown in the video I mentioned in Update 1.

The downside to this, of course, is that all the controls in your application will look "old". So I experimented with selectively disabling / enabling visual styles via

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

      

and using Win32.SetWindowTheme(lv.Handle, " ", " ");

as described in the MSDN docs. The most logical approach would be to keep the visual styles active for most controls and disable them for critical performance. However, part of the ListView seems to be deliberately ignoring, disabled or enabled visual styles, namely the list column headers in report mode:

Column Header ignores turning off visual style

(Note how the column heading looks compared to scrollbars)



So if someone doesn't know how to turn off visual styles on listview column headers, this is the situation where you pick poison. Or comment out Application.EnableVisualStyles (); and have an ugly UI or leave it and risk unpredictable slowdowns in rendering.

If you choose the first option, you can get another huge performance boost by subclassing the ListView and short-circuiting the WM_REFLECT_NOTIFY message (thanks to SteveFerg for the original):

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);
    }
}

      

Disabling visual styles and subclassing allows rendering speeds nearly equal to that of a Win32 C application. However, I do not fully understand the potential consequences of short-circuiting WM_REFLECT_NOTIFY, so use them with caution.

I also checked a Win32 application and confirmed that you can literally kill the rendering performance of your application by simply adding a manifest, like this:

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

      

0


source


After trying a few different things, here is the fastest solution I have found. Still a little hesitant, but not anywhere near your original solution. Until Microsoft decides to use something better than GDI +, it doesn't improve unless you move to WPF from .NET 4 and up. Oh good.

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);
    }

}

      



You can see the difference yourself. If you uncomment SendMessage and change new BufferedListView();

to new ListViewWithLessSuck();

, you can see the change.

0


source







All Articles