Это мой первый пост на StackOverflow, поэтому прошу прощения за неправильное форматирование кода ниже.
Чтобы предотвратить блокировку формы при обновлении ListView, вы можете использовать нижеприведенный метод, который я написал для решения этой проблемы.
Примечание: Этот метод не следует использовать, если вы планируете заполнить ListView более чем 20 000 элементов. Если вам нужно добавить в список List более 20 тыс. Элементов, попробуйте запустить 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; }
}
Затем создайте метод, который возвращает элементы данных. Этот метод может запрашивать базу данных, вызывать 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. Скорее всего, вы захотите сделать это в другом месте в форме.
Я включил функцию, которая генерирует ListViewItem из экземпляра моего класса TestListViewItemClass. В сценарии производства вы, скорее всего, захотите определить функцию в другом месте.
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 много раз в проектах, где мы были заселять вверх до 12 000 элементов в ListView, и это очень быстро. Главное, что вам нужно, чтобы ваш объект был полностью построен из базы данных, прежде чем вы даже коснетесь 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));
}
Freeze означает что-то еще: это означает, что объект (в данном случае набор элементов) не изменится, пока он заморожен. В этом случае вы тут же меняете его! –
Freeze был просто термином, который я использовал для объяснения моего требования. –