To jest mój pierwszy wpis na StackOverflow, więc przepraszam za niechlujny format kodu poniżej.
Aby zapobiec blokowaniu formularza podczas aktualizacji ListView, możesz użyć poniższej metody, którą napisałem, aby rozwiązać ten problem.
Uwaga: Ta metoda nie powinna być używana, jeśli użytkownik chce wypełnić ListView ponad 20 000 pozycji. Jeśli chcesz dodać więcej niż 20k elementów do ListView, rozważ uruchomienie ListView w trybie wirtualnym.
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);
}
Nie trzeba dostarczyć przedmiot IProgress, wystarczy użyć nieważną i metoda będzie działać tak samo dobrze.
Poniżej znajduje się przykład zastosowania tej metody.
Najpierw zdefiniuj klasę zawierającą dane dla obiektu 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; }
}
Następnie utwórz metodę, która zwraca elementy danych. Ta metoda może wywoływać zapytania do bazy danych, wywoływać interfejs API usług sieci Web lub coś podobnego, o ile zwraca wartość IEnumerable danego typu klasy.
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,
};
}
}
Wreszcie na formularzu, gdzie znajduje się Twój ListView, możesz wypełnić ListView. Do celów demonstracyjnych używam zdarzenia Load formularza, aby zapełnić listę ListView. Najprawdopodobniej będziesz chciał to zrobić gdzie indziej w formularzu.
Dołączyłem funkcję, która generuje obiekt ListViewItem z instancji mojej klasy, TestListViewItemClass. W scenariuszu produkcyjnym prawdopodobnie będziesz chciał zdefiniować funkcję w innym miejscu.
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);
}
W powyższym przykładzie, stworzyłem obiekt IProgress w konstruktorze formularza jest tak:
progress = new Progress<int>(value =>
{
toolStripProgressBar1.Visible = true;
if (value >= 100)
{
toolStripProgressBar1.Visible = false;
toolStripProgressBar1.Value = 0;
}
else if (value > 0)
{
toolStripProgressBar1.Value = value;
}
});
Użyłem tej metody wypełniania ListView wielokrotnie w projektach, w których byliśmy podczas wypełniania górę do 12 000 pozycji w ListView i jest niezwykle szybki. Najważniejsze jest, aby obiekt był w pełni zbudowany z bazy danych, zanim jeszcze dotkniesz ListView w poszukiwaniu aktualizacji.
Mam nadzieję, że jest to pomocne.
Dołączyłem poniżej wersję asynchroniczną metody, która wywołuje główną metodę pokazaną u góry tego wpisu.
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));
}
Zamrożenie oznacza coś innego: oznacza, że obiekt (w tym przypadku zbiór elementów) nie ulegnie zmianie podczas jego zamrożenia. W takim przypadku natychmiast go modyfikujesz! –
Zamrażanie było tylko terminem, którego używałem w celu wyjaśnienia mojego wymogu. –