Đây là lần đầu tiên tôi đăng trên StackOverflow, vì vậy hãy tha thứ cho định dạng mã lộn xộn bên dưới.
Để ngăn chặn khóa biểu mẫu trong khi cập nhật ListView, bạn có thể sử dụng phương pháp bên dưới mà tôi đã viết để giải quyết vấn đề này.
Lưu ý: Phương pháp này không nên được sử dụng nếu bạn dự kiến đưa vào ListView với hơn 20.000 mục. Nếu bạn cần thêm nhiều hơn 20k mục vào ListView, hãy xem xét chạy ListView ở chế độ ảo.
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);
}
Bạn không phải cung cấp đối tượng IProgress, chỉ cần sử dụng null và phương thức cũng sẽ hoạt động.
Dưới đây là ví dụ về cách sử dụng phương pháp.
Đầu tiên, hãy xác định lớp có chứa dữ liệu cho 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; }
}
Sau đó, tạo phương thức trả về các mục dữ liệu của bạn. Phương thức này có thể truy vấn một cơ sở dữ liệu, gọi một API dịch vụ web, hoặc bất cứ điều gì, miễn là nó trả về một IEnumerable loại lớp của bạn.
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,
};
}
}
Cuối cùng, trên biểu mẫu mà ListView của bạn cư trú, bạn có thể điền ListView. Đối với mục đích trình diễn, tôi đang sử dụng sự kiện Load của biểu mẫu để điền vào ListView. Nhiều khả năng, bạn sẽ muốn làm điều này ở nơi khác trên biểu mẫu.
Tôi đã bao gồm hàm tạo ra một ListViewItem từ một thể hiện của lớp của tôi, TestListViewItemClass. Trong một kịch bản sản xuất, bạn có thể sẽ muốn xác định hàm ở nơi khác.
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);
}
Trong ví dụ trên, tôi tạo ra một đối tượng IProgress trong constructor của form như thế này:
progress = new Progress<int>(value =>
{
toolStripProgressBar1.Visible = true;
if (value >= 100)
{
toolStripProgressBar1.Visible = false;
toolStripProgressBar1.Value = 0;
}
else if (value > 0)
{
toolStripProgressBar1.Value = value;
}
});
Tôi đã sử dụng phương pháp này Populating một ListView nhiều lần trong các dự án mà chúng tôi đã Populating lên đến 12.000 mục trong ListView, và nó cực kỳ nhanh. Điều chính là bạn cần phải có đối tượng của bạn được xây dựng hoàn toàn từ cơ sở dữ liệu trước khi bạn thậm chí chạm vào ListView để cập nhật.
Hy vọng điều này hữu ích.
Tôi đã bao gồm bên dưới phiên bản không đồng bộ của phương thức, gọi phương thức chính được hiển thị ở đầu bài đăng này.
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));
}
Cố định có nghĩa là đối tượng khác (trong trường hợp này là tập hợp các phần tử) sẽ không thay đổi khi bị đóng băng. Trong trường hợp này, bạn ngay lập tức sửa đổi nó! –
Freeze chỉ là một thuật ngữ mà tôi đã sử dụng cho mục đích giải thích yêu cầu của tôi –