2015-06-20 48 views
3

更新1:我写既是一个MFC-C++实现和一个老派的Win32应用程序和录制的视频演示该问题,真的是多么糟糕的是:为什么ListView对某些字符渲染速度很慢?

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

由于老派Win32应用程序没有展示这个问题,这导致我相信C#和MFC都使用必须导致此问题的相同渲染API(基本上解除了我的怀疑,问题可能出在OS /图形驱动程序级别)。


原帖:

虽然不必显示一个ListView里面休息一下数据,我遇到了一个非常奇特的问题:

对于某些输入,ListView的渲染会到字面上慢在水平滚动的同时爬行。

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

对于常规非子类的ListView与10个项目,我可以从字面上看每个项目被单独绘制而滚动(重绘约需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); 
     } 
    } 
} 

有人能说明什么问题是,以及如何解决它?

+0

看一看点击:http:/ /www.virtualdub.org/blog/pivot/entry.php?id=273 – SteveFerg

+0

@SteveFerg你有没有去上面提供的ListView的子类版本提供的示例? – MrCC

回答

0

我相信我已经下降到视觉样式收窄的问题。在滚动过程中注释掉static void Main导致巨大的性能提升Application.EnableVisualStyles();,虽然远不及的Win32应用程序的性能,如图,我在更新1

的这门课程的缺点提到的视频是所有控件你的应用程序看起来会“老”。因此,我已经试验了有选择地禁用/通过

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

使得能够视觉样式和使用Win32.SetWindowTheme(lv.Handle, " ", " ");如MSDN文档中描述。最合乎逻辑的事情是让大多数控件的视觉样式保持活动状态,如果关闭性能关键控件则关闭。但是,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='*'\"") 
0

尝试了几个不同的东西后,下面是我找到的最快解决方案。仍然有一点犹豫,但没有接近你原来的解决方案。在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();您可以看到更改。

+0

你只会看到一个区别,因为你正在比较你的ListViewWithLessSuck与我假设的BufferedListView。 BufferedListView是迄今为止最慢的三种,主要是因为OptimizedDoubleBuffer风格。将其样式设置为'ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint'应该可以缓解这一点。在比较控制和标准控制时,我发现在渲染速度方面没有任何改进。此外,在阅读完您的博客文章之后,您是否可以提供有关何时内部“hdr.code == NM_CUSTOMDRAW'部分正在发射的信息?因为在我的系统上,这是从来没有执行过...... – MrCC

+0

..什么*做*帮助,但是如果我快捷WndProc'if(m.Msg == 0x204E){m.Result =(IntPtr)0;返回; } base.WndProc(ref m);'即跳过内部if。但不是太多,我可以把它称为任何接近可接受的地方。基本上,滚动吃一个核心上的所有CPU cyles。如果消息0x204E是WM_REFLECT_NOTIFY,那么内部代码何时执行?另外,你能否提供我粗略的估计,渲染在标准ListView上的渲染需要多长时间? ... – MrCC

+0

我创建了一个展示相同问题的MFC C++项目。除非MFC使用与Winforms/GDI +相同的绘图例程,否则comctl32渲染代码,comctl32使用的OS绘图函数或我的图形驱动程序中可能存在根本性缺陷。如果有人可以创建一个C项目直接进入本机列表视图控件,并证明问题仍然存在,那至少可以帮助消除MFC是罪魁祸首的情况,只留下comctl32,操作系统和驱动程序。 – MrCC