【问题标题】:.NET Listview Refresh.NET 列表视图刷新
【发布时间】:2011-03-18 23:03:35
【问题描述】:

我有以下代码,它基本上从数据库中获取值并填充列表视图。

using (IDataReader reader = cmd.ExecuteReader())
{                    
    lvwMyList.Items.Clear();
    while (reader.Read())
    {
        ListViewItem lvi = lvwMyList.Items.Add(reader["Value1"].ToString());
        lvi.SubItems.Add(reader["Value2"].ToString());                    
    }
}

我遇到的问题是,这会以短时间间隔(每秒)重复执行,并导致列表视图中的项目不断消失并重新出现。有什么方法可以阻止列表视图刷新,直到完成更新?如下所示:

using (IDataReader reader = cmd.ExecuteReader())
{                    
    lvwMyList.Items.Freeze(); // Stop the listview updating
    lvwMyList.Items.Clear();
    while (reader.Read())
    {
        ListViewItem lvi = lvwMyList.Items.Add(reader["Value1"].ToString());
        lvi.SubItems.Add(reader["Value2"].ToString());                    
    }
    lvwMyList.Items.UnFreeze(); // Refresh the listview
}

【问题讨论】:

  • 冻结意味着别的东西:它意味着对象(在这种情况下是元素的集合)在冻结时不会改变。在这种情况下,您将立即对其进行修改!
  • 冻结只是我用来解释我的要求的一个术语

标签: c# .net winforms listview


【解决方案1】:

这是我第一次在 StackOverflow 上发帖,请原谅下面凌乱的代码格式。

为了防止在更新 ListView 时锁定表单,您可以使用我编写的以下方法来解决此问题。

注意:如果您希望使用超过 20,000 个项目填充 ListView,则不应使用此方法。如果您需要向 ListView 添加超过 20k 的项目,请考虑在虚拟模式下运行 ListView。

 public static async void PopulateListView<T>(ListView listView, Func<T, ListViewItem> func, 
        IEnumerable<T> objects, IProgress<int> progress) where T : class, new()
    {
        if (listView != null && listView.IsHandleCreated)
        {
            var conQue = new ConcurrentQueue<ListViewItem>();

            // Clear the list view and refresh it
            if (listView.InvokeRequired)
            {
                listView.BeginInvoke(new MethodInvoker(() =>
                    {
                        listView.BeginUpdate();
                        listView.Items.Clear();
                        listView.Refresh();
                        listView.EndUpdate();
                    }));
            }
            else
            {
                listView.BeginUpdate();
                listView.Items.Clear();
                listView.Refresh();
                listView.EndUpdate();
            }

            // Loop over the objects and call the function to generate the list view items
            if (objects != null)
            {
                int objTotalCount = objects.Count();

                foreach (T obj in objects)
                {
                    await Task.Run(() =>
                        {
                            ListViewItem item = func.Invoke(obj);

                            if (item != null)
                                conQue.Enqueue(item);

                            if (progress != null)
                            {
                                double dProgress = ((double)conQue.Count / objTotalCount) * 100.0;

                                if(dProgress > 0)
                                    progress.Report(dProgress > int.MaxValue ? int.MaxValue : (int)dProgress);
                            }
                        });
                }

                // Perform a mass-add of all the list view items we created
                if (listView.InvokeRequired)
                {
                    listView.BeginInvoke(new MethodInvoker(() =>
                        {
                            listView.BeginUpdate();
                            listView.Items.AddRange(conQue.ToArray());
                            listView.Sort();
                            listView.EndUpdate();
                        }));
                }
                else
                {
                    listView.BeginUpdate();
                    listView.Items.AddRange(conQue.ToArray());
                    listView.Sort();
                    listView.EndUpdate();
                }
            }
        }

        if (progress != null)
            progress.Report(100);
    }

您不必提供 IProgress 对象,只需使用 null,该方法也可以正常工作。

以下是该方法的示例用法。

首先,定义一个包含 ListViewItem 数据的类。

public class TestListViewItemClass
{
    public int TestInt { get; set; }

    public string TestString { get; set; }

    public DateTime TestDateTime { get; set; }

    public TimeSpan TestTimeSpan { get; set; }

    public decimal TestDecimal { get; set; }
}

然后,创建一个返回数据项的方法。此方法可以查询数据库、调用 Web 服务 API 或其他任何方式,只要它返回您的类类型的 IEnumerable。

public IEnumerable<TestListViewItemClass> GetItems()
{
    for (int x = 0; x < 15000; x++)
    {
        yield return new TestListViewItemClass()
        {
            TestDateTime = DateTime.Now,
            TestTimeSpan = TimeSpan.FromDays(x),
            TestInt = new Random(DateTime.Now.Millisecond).Next(),
            TestDecimal = (decimal)x + new Random(DateTime.Now.Millisecond).Next(),
            TestString = "Test string " + x,
        };
    }
}

最后,在 ListView 所在的窗体上,您可以填充 ListView。出于演示目的,我使用表单的 Load 事件来填充 ListView。您很可能希望在表单的其他位置执行此操作。

我已经包含了从我的类的实例 TestListViewItemClass 生成 ListViewItem 的函数。在生产场景中,您可能希望在其他地方定义函数。

private async void TestListViewForm_Load(object sender, EventArgs e)
{     
    var function = new Func<TestListViewItemClass, ListViewItem>((TestListViewItemClass x) =>
    {
        var item = new ListViewItem();

        if (x != null)
        {
            item.Text = x.TestString;
            item.SubItems.Add(x.TestDecimal.ToString("F4"));
            item.SubItems.Add(x.TestDateTime.ToString("G"));
            item.SubItems.Add(x.TestTimeSpan.ToString());
            item.SubItems.Add(x.TestInt.ToString());
            item.Tag = x;

            return item;
        }

        return null;
    });

       PopulateListView<TestListViewItemClass>(this.listView1, function, GetItems(), progress);

 }

在上面的示例中,我在表单的构造函数中创建了一个 IProgress 对象,如下所示:

progress = new Progress<int>(value =>
{
    toolStripProgressBar1.Visible = true;

    if (value >= 100)
    {
        toolStripProgressBar1.Visible = false;
        toolStripProgressBar1.Value = 0;
    }
    else if (value > 0)
    {
        toolStripProgressBar1.Value = value;
    }
 });

我在项目中多次使用这种填充 ListView 的方法,我们在 ListView 中填充了多达 12,000 个项目,而且速度非常快。最重要的是,在您触摸 ListView 进行更新之前,您需要从数据库中完全构建您的对象。

希望这会有所帮助。

我在下面包含了该方法的异步版本,它调用了本文顶部显示的 main 方法。

public static Task PopulateListViewAsync<T>(ListView listView, Func<T, ListViewItem> func,
        IEnumerable<T> objects, IProgress<int> progress) where T : class, new()
{
    return Task.Run(() => PopulateListView<T>(listView, func, objects, progress));
}

【讨论】:

    【解决方案2】:

    像这样:

    try
    {
        lvwMyList.BeginUpdate();
        //bla bla bla
    
    }
    finally
    {
        lvwMyList.EndUpdate();
    }
    

    如果您想在填写之前清除列表,请确保调用lvwMyList.Items.Clear() 之后 BeginUpdate

    【讨论】:

    • 清除物品时它仍然会“闪烁”。 TreeView 也是如此。
    • 这绝对符合我的要求。唯一的问题是它实际上也锁定了表单:-)
    • 在 beginupdate 内部清除应该可以防止它闪烁。表单不是由 beginupdate 锁定,而是由添加新项目的代码锁定。在进行更新之前尝试从 Db 中获取所有项目。
    • 考虑将 EndUpdate 放在 finally 块中。
    • @jgauffin 谢谢 - 您可能希望 BeginUpdate 在尝试之外。如果失败,您可能不希望 EndUpdate 执行。
    【解决方案3】:

    您还可以尝试在更新期间将可见或启用属性设置为 false,看看您是否更喜欢这些结果。 当然,更新完成后将值重置为 true。

    另一种方法是创建一个面板来覆盖列表框。将它的左、右、高和宽属性设置为与您的列表框相同,并在更新期间将其可见属性设置为true,完成后设置为false。

    【讨论】:

    • 禁用和启用控件似乎会使问题变得更糟
    猜你喜欢
    • 1970-01-01
    • 2011-08-31
    • 2011-05-04
    • 2011-10-23
    • 1970-01-01
    • 1970-01-01
    • 2020-03-29
    • 2020-04-22
    • 2015-07-18
    相关资源
    最近更新 更多