2010-07-21 33 views
5

Tengo el siguiente código, que básicamente toma valores de una base de datos y rellena una vista de lista..NET Listview Refresh

using (IDataReader reader = cmd.ExecuteReader()) 
{      
    lvwMyList.Items.Clear(); 
    while (reader.Read()) 
    { 
     ListViewItem lvi = lvwMyList.Items.Add(reader["Value1"].ToString()); 
     lvi.SubItems.Add(reader["Value2"].ToString());      
    } 
} 

El problema que tengo es que este se ejecuta repetidamente a intervalos cortos (cada segundo) y los resultados en los elementos de la vista de lista desapareciendo continuamente y volver a aparecer. ¿Hay alguna forma de evitar que la vista de lista se actualice hasta que se complete con las actualizaciones? Algo parecido a continuación:

using (IDataReader reader = cmd.ExecuteReader()) 
{      
    lvwMyList.Items.Freeze(); // Stop the listview updating 
    lvwMyList.Items.Clear(); 
    while (reader.Read()) 
    { 
     ListViewItem lvi = lvwMyList.Items.Add(reader["Value1"].ToString()); 
     lvi.SubItems.Add(reader["Value2"].ToString());      
    } 
    lvwMyList.Items.UnFreeze(); // Refresh the listview 
} 
+0

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! –

+1

Congelar fue solo un término que utilicé para explicar mi requerimiento –

Respuesta

9

De esta manera:

try 
{ 
    lvwMyList.BeginUpdate(); 
    //bla bla bla 

} 
finally 
{ 
    lvwMyList.EndUpdate(); 
} 

Asegúrese de que se invoca lvwMyList.Items.Clear()despuésBeginUpdate si desea borrar la lista antes de llenarlo.

+1

Aún va a 'parpadear' cuando borre artículos. Lo mismo sucede en TreeView. – leppie

+1

Esto definitivamente hace lo que solicité. El único problema es que virtualmente también bloquea el formulario :-) –

+1

Tener claro inside beginupdate debe evitar que destelle. El formulario no está bloqueado por beginupdate, sino por su código que agrega los nuevos elementos. Intente recuperar todos los elementos del Db antes de realizar la actualización. – jgauffin

0

También puede intentar configurar las propiedades visibles o habilitadas en falso durante la actualización y ver si le gustan mejor esos resultados. Por supuesto, restablezca los valores a verdadero cuando la actualización haya finalizado.

Otro enfoque es crear un panel para superponer el listbox. Establezca sus propiedades de izquierda, derecha, altura y ancho de la misma manera que su cuadro de lista y establezca su propiedad visible en verdadero durante la actualización, falso después de que haya terminado.

+1

Desactivar y habilitar el control parece empeorar el problema –

1

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)); 
}