.NET ListView 刷新

5
我有以下代码,它基本上从数据库中获取值并填充列表视图。
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
}

冻结意味着另外一件事情:它意味着对象(在这种情况下是一组元素)在被冻结时不会改变。但在这种情况下,您立即对其进行了修改! - Kieren Johnstone
1
“Freeze”只是我用来解释我的需求的一个术语。 - Paul Michaels
3个回答

9

像这样:

try
{
    lvwMyList.BeginUpdate();
    //bla bla bla

}
finally
{
    lvwMyList.EndUpdate();
}

在填充列表之前清空列表,请确保在调用 BeginUpdate 后调用 lvwMyList.Items.Clear()

注意,此处的 “after” 指的是在调用 BeginUpdate 之后,而不是之前。


1
当你清除项目时,它仍然会“闪烁”。TreeView上也会发生相同的情况。 - leppie
1
这绝对做到了我所要求的。唯一的问题是它几乎锁定了整个表单 :-) - Paul Michaels
1
拥有清晰的beginupdate内部应该可以防止它闪烁。表单不是被beginupdate锁定,而是被添加新项的代码锁定。在更新之前尝试从数据库中获取所有项。 - jgauffin
2
考虑将EndUpdate放置在finally块中。 - TrueWill
1
@jgauffin 谢谢 - 你可能想要在 try 外面放 BeginUpdate。如果它失败了,你可能不想让 EndUpdate 执行。 - TrueWill
1
我不认为“BeginUpdate”会失败,您觉得呢? - jgauffin

3

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

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

注意:如果您希望向ListView中添加超过2万个项目,则不应使用此方法。如果需要向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,最多可以填充12000个项目,并且速度非常快。主要是需要在更新ListView之前从数据库中完整地构建对象。
希望这对你有所帮助。
下面是该方法的异步版本,调用了本文开头展示的主方法。
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));
}

0

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

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


1
禁用和启用控件似乎会使问题变得更糟。 - Paul Michaels

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接