【问题标题】:c# flickering Listview on update时间:2019-05-10 标签:c#flickering Listview on update
【发布时间】:2010-10-01 08:46:15
【问题描述】:

我有一个定期更新(每 60 秒)的列表视图。每次更新时我都会闪烁,这让我很恼火。使用的方法是清除所有项目,然后重新创建它们。我决定不清除项目,而是直接将新文本写入单元格。这是更好的方法还是有人有更好的解决方案。

【问题讨论】:

  • MSDN 建议在 BeginUpdate/EndUpdate 中封装大量更新。我测试过,它有帮助(链接在 ListBox 上,但似乎同样适用于 ListView - 如here)

标签: c# listview flicker


【解决方案1】:

这是我对不需要子类化列表视图等的 C# 实现的快速修复。

在表单构造函数中使用反射将 DoubleBuffered 属性设置为 true。

    lvMessages
        .GetType()
        .GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
        .SetValue(lvMessages, true, null);

2021 年更新: 我在这篇旧帖子上收到了评论,现在我会以不同的方式编写这段代码。下面是一个扩展方法,它将向 ListView 添加一个新方法,以便能够根据需要将双缓冲属性设置为 true/false。这将扩展所有列表视图并使其更容易按要求调用。

/// <summary>
/// Extension methods for List Views
/// </summary>
public static class ListViewExtensions
{
    /// <summary>
    /// Sets the double buffered property of a list view to the specified value
    /// </summary>
    /// <param name="listView">The List view</param>
    /// <param name="doubleBuffered">Double Buffered or not</param>
    public static void SetDoubleBuffered(this System.Windows.Forms.ListView listView, bool doubleBuffered = true)
    {
        listView
            .GetType()
            .GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
            .SetValue(listView, doubleBuffered, null);
    }
}

【讨论】:

  • 我认为这是迄今为止实现的最简单的解决方案。不需要子类,并允许使用 Visual Studio 工具箱中未修改的列表视图。可耻的 DoubleBuffered 不只是公开!
  • 迄今为止最好的解决方案。 Tks
  • @RegisSt-Gelais 谢谢,我刚刚添加了一个替代扩展方法版本,它可以更轻松地扩展具有相同功能的所有列表视图。几年前我写了原始代码,现在我会用扩展方法来做。
  • @WraithNath 它看起来很像我在我的项目中编写的代码 :)
【解决方案2】:

如果有人仍然会为此寻找答案,我使用了一个计时器稍微延迟,它很好地解决了这个问题。我想在鼠标移动事件上突出显示(更改颜色)整行,但我认为它适用于项目替换等。对我来说 listView.BeginUpdate() 和 listView.EndUpdate() 不起作用,DoubleBuffered 属性也没有不工作,我用谷歌搜索了很多,但没有任何工作。

private int currentViewItemIndex;
private int lastViewItemIndex;

    private void listView_MouseMove(object sender, MouseEventArgs e)
    {            
        ListViewItem lvi = listView.GetItemAt(e.X, e.Y);

        if (lvi != null && lastViewItemIndex == -1)
        {
            listView.Items[lvi.Index].BackColor = Color.Green;
            lastViewItemIndex = lvi.Index;
        }

        if (lvi != null && lastViewItemIndex != -1)
        {
            currentViewItemIndex = lvi.Index;
            listViewTimer.Start(); 
        } 
    }

    private void listViewTimer_Tick(object sender, EventArgs e)
    {
        listView.BeginUpdate();
        listView.Items[lastViewItemIndex].BackColor = Colour.Transparent;
        listView.Items[currentViewItemIndex].BackColor = Colour.Green;
        listView.EndUpdate();
        lastViewItemIndex = currentViewItemIndex;
        listViewTimer.Stop();
    }

【讨论】:

    【解决方案3】:

    这对我来说效果最好。
    由于您是直接编辑单元格,因此在您的情况下,最好的解决方案是简单地刷新/重新加载该特定单元格/行而不是整个表格。
    您可以使用 RedrawItems(...) 方法,该方法基本上只重绘列表视图的指定范围的项目/行。

    public void RedrawItems(int startIndex, int endIndex, bool invalidateOnly);
    

    Reference

    这对我来说完全摆脱了完整的列表视图闪烁。
    更新时只有相关的项目/记录闪烁。

    干杯!

    【讨论】:

      【解决方案4】:

      对于它的价值,就我而言,我只需要添加一个调用

      Application.EnableVisualStyles()

      在运行应用程序之前,像这样:

          private static void Main()
          {
              Application.EnableVisualStyles();
              Application.Run(new Form1());
          }
      

      否则,双缓冲是不够的。也许这是一个非常古老的项目,而新项目默认具有该设置...

      【讨论】:

        【解决方案5】:

        您可以使用以下扩展类将DoubleBuffered 属性设置为true:

        using System.Reflection;
        
        public static class ListViewExtensions
        {
            public static void SetDoubleBuffered(this ListView listView, bool value)
            {
                listView.GetType()
                    .GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic)
                    .SetValue(listView, value);
            }
        }
        

        【讨论】:

          【解决方案6】:

          在 Winrt Windows phone 8.1 中,您可以设置以下代码来解决此问题。

          <ListView.ItemContainerTransitions>
              <TransitionCollection/>      
          </ListView.ItemContainerTransitions>
          

          【讨论】:

            【解决方案7】:

            我知道这是一个非常古老的问题和答案。但是,这是搜索“C++/cli listview flicker”时的最高结果——尽管这甚至不是在谈论 C++。所以这里是这个的 C++ 版本:

            我把这个放在我的主窗体的头文件中,你可以选择把它放在其他地方...

            static void DoubleBuffer(Control^ control, bool enable) {
                System::Reflection::PropertyInfo^ info = control->GetType()->
                    GetProperty("DoubleBuffered", System::Reflection::BindingFlags::Instance 
                        | System::Reflection::BindingFlags::NonPublic);
                info->SetValue(control, enable, nullptr);
            }
            

            如果您碰巧在这里为托管 C++ 寻找类似的答案,那对我有用。 :)

            【讨论】:

              【解决方案8】:

              最简单的解决方案可能是使用

                     listView.Items.AddRange(listViewItems.ToArray());
              

              而不是

                     foreach (ListViewItem listViewItem in listViewItems)
                     {
                         listView.Items.Add(listViewItem);
                     }
              

              这样效果更好。

              【讨论】:

              • 非常感谢您在阻止我的完整 GUI 时遇到了真正的麻烦,只需添加项目即可解决。
              【解决方案9】:

              简单的解决方案

              yourlistview.BeginUpdate()
              
              //Do your update of adding and removing item from the list
              
              yourlistview.EndUpdate()
              

              【讨论】:

              • 当我有一个限制为 100 个项目的列表视图时,这对我有用,如果满足该阈值,我会在添加新行时删除第一行。
              • 对所提出的问题不是一个有用的答案,因为(自 2008 年编辑第 3 版以来)OP 说他已经在这样做了。
              【解决方案10】:

              如果这有帮助,以下组件解决了我在 .NET 3.5 中的 ListView 闪烁问题

              [ToolboxItem(true)]
              [ToolboxBitmap(typeof(ListView))]
              public class ListViewDoubleBuffered : ListView
              {
                  public ListViewDoubleBuffered()
                  {
                      this.DoubleBuffered = true;
                  }
              }
              

              我将它与我进行 ListView.Items 操作的 .BeginUpdate() 和 .EndUpdate() 方法结合使用。

              我不明白为什么这个属性是受保护的...即使在 .NET 4.5 中(可能是安全问题)

              【讨论】:

                【解决方案11】:

                很好的问题,Stormenent 的回答很到位。这是他的代码的 C++ 端口,供其他可能正在处理 C++/CLI 实现的人使用。

                #pragma once
                
                #include "Windows.h" // For WM_ERASEBKGND
                
                using namespace System;
                using namespace System::Windows::Forms;
                using namespace System::Data;
                using namespace System::Drawing;
                
                public ref class FlickerFreeListView : public ListView
                {
                public:
                    FlickerFreeListView()
                    {
                        //Activate double buffering
                        SetStyle(ControlStyles::OptimizedDoubleBuffer | ControlStyles::AllPaintingInWmPaint, true);
                
                        //Enable the OnNotifyMessage event so we get a chance to filter out 
                        // Windows messages before they get to the form's WndProc
                        SetStyle(ControlStyles::EnableNotifyMessage, true);
                    }
                
                protected:
                    virtual  void OnNotifyMessage(Message m) override
                    {
                        //Filter out the WM_ERASEBKGND message
                        if(m.Msg != WM_ERASEBKGND)
                        {
                            ListView::OnNotifyMessage(m);
                        }
                    }
                
                };
                

                【讨论】:

                  【解决方案12】:

                  除了其他回复之外,许多控件都有一个[Begin|End]Update() 方法,您可以使用它来减少编辑内容时的闪烁 - 例如:

                      listView.BeginUpdate();
                      try {
                          // listView.Items... (lots of editing)
                      } finally {
                          listView.EndUpdate();
                      }
                  

                  【讨论】:

                  • 非常感谢!我一直在网上上下搜索,没有一个搜索结果适用:D
                  • 我发现这有助于但不能完全消除闪烁。
                  【解决方案13】:

                  尝试将双缓冲属性设置为 true。

                  你也可以使用:

                  this.SuspendLayout();
                  
                  //update control
                  
                  this.ResumeLayout(False);
                  
                  this.PerformLayout();
                  

                  【讨论】:

                  • 布局主要适用于移动/调整大小...我怀疑您的意思是 [Begin|End]Update,根据我的回复。
                  【解决方案14】:

                  【讨论】:

                  • -1:您不能设置ListViews DoubleBuffered 属性(它是受保护的)。
                  • -1 到您的评论,因为您可以设置它,如果您从中派生一个新类,还有“SetStyle”方法。但是,在对某人投反对票之前进行研究的方法,即使您使用您的评论毫无意义的确切原因进行回答竖起大拇指
                  • 请记住,SetStyle 也受到保护......你是如何设法调用它的?
                  【解决方案15】:

                  ListView 控件存在闪烁问题。问题似乎是控件的更新重载未正确实现,以至于它的行为类似于刷新。更新应使控件仅重绘其无效区域,而刷新则重绘控件的整个客户区。因此,如果您要更改列表中一项的背景颜色,那么只有该特定项需要重新绘制。不幸的是,ListView 控件似乎有不同的看法,并且每当您弄乱单个项目时都希望重新绘制其整个表面……即使该项目当前未显示。因此,无论如何,您可以通过滚动自己的方式轻松抑制闪烁,如下所示:

                  class ListViewNF : System.Windows.Forms.ListView
                  {
                      public ListViewNF()
                      {
                          //Activate double buffering
                          this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
                  
                          //Enable the OnNotifyMessage event so we get a chance to filter out 
                          // Windows messages before they get to the form's WndProc
                          this.SetStyle(ControlStyles.EnableNotifyMessage, true);
                      }
                  
                      protected override void OnNotifyMessage(Message m)
                      {
                          //Filter out the WM_ERASEBKGND message
                          if(m.Msg != 0x14)
                          {
                              base.OnNotifyMessage(m);
                          }
                      }
                  }
                  

                  发件人:Geekswithblogs.net

                  【讨论】:

                  • 你必须知道这个解决方案的缺点(由于双缓冲)是更新控件的速度明显变慢(尝试添加 1000 个项目)。
                  • 说实话,我从不使用winforms listview。但是,当您调用 SuspendLayout 和 ResumeLayout 时,性能损失不是消失了吗?
                  • 谢谢Stormenent。我一直在寻找这个问题的解决方案,这对我来说效果很好。
                  • OptimizedDoubleBuffer 可以正常工作。但我不认为跳过对 base.OnNotifyMessage(m) 的调用有任何帮助。我刚刚检查了 ILSpy 和 Control.OnNotifyMessage 有一个空的实现。来自 MSDN:“在派生类中重写 OnNotifyMessage 时,不需要调用基类的 OnNotifyMessage 方法,因为没有初始实现。”有此方法,因此可以在处理消息时通知子类。如果您需要更改这些消息,则需要覆盖 WndProc ?
                  • 如果你不喜欢这个解决方案,因为子类化,这里非常相似,没有子类化:stackoverflow.com/questions/87795/…
                  猜你喜欢
                  • 1970-01-01
                  • 2020-06-15
                  • 2020-10-01
                  • 2015-06-14
                  • 2011-07-08
                  • 2012-02-03
                  • 2017-04-15
                  • 2021-07-31
                  • 2016-01-27
                  相关资源
                  最近更新 更多