这是我第一次在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>();
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();
}
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);
}
});
}
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));
}