Esta es la primera vez que publico en StackOverflow, perdone el formato de código desordenado a continuación.
Para evitar el bloqueo del formulario al actualizar el ListView, puede utilizar el siguiente método que he escrito para resolver este problema.
Nota: Este método no debe usarse si espera llenar el ListView con más de 20,000 elementos. Si necesita agregar más de 20k elementos a ListView, considere ejecutar el ListView en modo virtual.
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);
}
Usted no tiene que proporcionar un objeto IProgress, sólo tiene que utilizar nulo y el método funcionará igual de bien.
A continuación se muestra un ejemplo de uso del método.
Primero, defina una clase que contenga los datos para 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; }
}
A continuación, cree un método que devuelva sus elementos de datos. Este método podría consultar una base de datos, llamar a una API de servicio web o lo que sea, siempre que devuelva un tipo de IEnumerable de su clase.
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,
};
}
}
Finalmente, en el formulario donde reside su ListView, puede llenar el ListView. Para fines de demostración, estoy usando el evento Load del formulario para llenar ListView. Lo más probable es que quieras hacer esto en otro lugar del formulario.
He incluido la función que genera un ListViewItem de una instancia de mi clase, TestListViewItemClass. En un escenario de producción, es probable que desee definir la función en otro lugar.
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);
}
En el ejemplo anterior, he creado un objeto IProgress en el constructor del formulario como el siguiente:
progress = new Progress<int>(value =>
{
toolStripProgressBar1.Visible = true;
if (value >= 100)
{
toolStripProgressBar1.Visible = false;
toolStripProgressBar1.Value = 0;
}
else if (value > 0)
{
toolStripProgressBar1.Value = value;
}
});
He utilizado este método para llenar un ListView muchas veces en proyectos en los que poblaban hasta a 12,000 artículos en el ListView, y es extremadamente rápido. Lo principal es que necesita tener su objeto completamente construido desde la base de datos incluso antes de tocar el ListView para las actualizaciones.
Espero que esto sea útil.
He incluido debajo una versión asincrónica del método, que llama al método principal que se muestra en la parte superior de esta publicación.
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));
}
Congelar significa algo más: significa que el objeto (en este caso una colección de elementos) no cambiará mientras está congelado. ¡En este caso, lo estás modificando de inmediato! –
Congelar fue solo un término que utilicé para explicar mi requerimiento –