2015-06-20 87 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