domingo, 31 de octubre de 2010

Avoid ListView flicker and scrollbar reset when refreshing its items

If you need to refresh the data of a listview frequently (every 20 seconds, for example) we don't recommed you use the listview "clear()" method and set again all the items. This approach have two major drawbacks.

1. You will be experimenting an annoying flicket every time you call "clear()" method.
2. The scrollbars of the listview will be reseted to (0,0) position.

To avoid this behaivior we recommed you instead of clearing everything, just add new items and remove the ones that you don't need anymore.

Our implementation mantains all the time a second generic list synchronized with the listview, this is a better aproach to us 'cause all the items are obtained from the database and we minimize the number of access to database.

First our ListView will be displaying MyObjects:

public class MyObject : IEquatable
{
public int id { get; set; }
public bool Equals(MyObject other)
{
if (Object.ReferenceEquals(other, null)) return false;
if (Object.ReferenceEquals(this, other)) return true;
return id.Equals(other.id);
}
public override int GetHashCode()
{
return id;
}
}


The code that populates the list and refresh it:


void refresh(){
//findMyObjects return the new IList that we want to sync
var myObjectsNew = findMyObjects();
var myObjectsToAdd = deleteItemsListView(myObjectsOld,myObjectsNew,listView);
foreach (MyObject myObjectToAdd in myObjectsToAdd)
{
var item = new ListViewItem(myObjectToAdd.id.ToString());
item.Name = myObjectToAdd.id.ToString();
listView.Items.Add(item);
myObjectsOld.Add(myObjectToAdd);
}
}


The delete method (returns the elements to add):


public static IList deleteItemsListView(IList oldList, IList newList, ListView listView)
{
var intersection = newList.Intersect(oldList);
var itemsToAdd = newList.Except(intersection);
var itemsToRemove = oldList.Except(intersection);
for (int i = itemsToRemove.Count() - 1; i > -1; i--)
{
T objetoParaEliminar = itemsToRemove.ToList()[i];
for (int j = 0; j < listView.Items.Count; j )
{
if (listView.Items[j].Name == objetoParaEliminar.GetHashCode().ToString())
{
listView.Items.RemoveAt(j);
break;
}
}
for (int j = 0; j < oldList.Count; j )
{
if (oldList[j].Equals(objetoParaEliminar))
{
oldList.RemoveAt(j);
break;
}
}
}
return itemsToAdd.ToList();
}
}


Complex? Yes, a little. It would be terrific having a generic ListViewItem in the ListView with a "sync()" method but thats not the case, another solution could be binding the list to a datasource like this guy in "code project":

http://www.codeproject.com/KB/list/ListView_DataBinding.aspx

To be honest, his implementation is more elegant but to our purposes this is enough.

Important points in our implementation:

1. All your items must have a unique id and must implement IEquatiable interface (we are using linq).
2. We assume that if the id is already in the list view this is synchronized. This means that we only add and delete new ids but didn't verify if item changed.
3. If you reorder in some way the ListView you can't ensure that the generic list can be acceded using the indices of the listview.
4. myObjectsOld generic list should be class scoped and should be the same reference to every call to refresh method.

No hay comentarios:

Publicar un comentario