2

I get a list of objects with some properties through a web request. Now I want to display those objects with properties in a list (I use WPF ListBox with DataTemplate) and refresh it every 5 seconds. I know two approaches to that:

  1. Every 5 seconds clear the list and populate it with newly received data. This approach results in some nasty things like losing current selection and tooltip flickering
  2. Try to find matching elements and update their properties; then add new elements to collection and remove deleted. This approach keeps references to existing objects just updating their contents and results in less buggy GUI. But this one is considerably harder to implement

What approach should I use? Maybe there are existing (simple) solutions to second one?

Update Looks like I need to clarify my question. I indeed use ObservableCollection<T> with two way binding. The problem is, when I want to update I make a web request and get another collection with different instances of objects. What I'm asking is how to apply changes from new collection to old one.

Poma
  • 8,174
  • 18
  • 82
  • 144
  • What are you doing now that causes things like flickering? Hard to offer "alternatives" to unknowns. – Peter Ritchie Jul 02 '14 at 15:36
  • 1
    Isn't a list that refreshes every 5 seconds a bit difficult to use? Wouldn't a button that force refresh be a better option for the user so he knows he is going to lose selection? – Tallmaris Jul 02 '14 at 15:36
  • @PeterRitchie after list is updated, the element under mouse cursor is "new" even if it has the same contents. So tooltip is hidden and shows up again after a delay – Poma Jul 02 '14 at 15:37
  • @Tallmaris actually data in the list changes not so frequently. It's like contact list with online statuses. – Poma Jul 02 '14 at 15:38

4 Answers4

0

Making user friendly fluid and fast interface was not and never be an easy task.

So you may find some "middle ware" solution, balancing between complexity of the code and amount of time you can afford to write it.

Like a simple and immediate solutions, that may do your UX better, you can think of:

  • if using ObservableCollection<T> for binding, do not use it directly, but use it as derived class, where you control when actually update event is fired. doing that way you may update collection (add/remove/change) and fire update event once so also avoid possible UI elements frequent flickering, caused often ba frequent update calls, so binded UI referesh requests.

    For example look on: ObservableCollection Doesn't support AddRange method, so I get notified for each item added, besides what about INotifyCollectionChanging?

  • the selection is a UI artifact's property like a color of button. think of it in that way. so the selection can be stored in your model view like a value, and after update applied, if that possible (can not be possible for example in case when the record selected before is not more present) re-apply selection on UI.

Hope this helps.

Community
  • 1
  • 1
Tigran
  • 61,654
  • 8
  • 86
  • 123
  • Why not use a [BulkObservableCollection](http://msdn.microsoft.com/en-us/library/dd867973.aspx)? It's an ObservableCollection that includes the `AddRange()` operation, provided out-of-the-box by the .NET Framework. – Jens H Jul 02 '14 at 15:53
  • @JensH: That one's truly hidden in a weird place (namespace). Is it really supposed to be used for arbitrary purposes? – O. R. Mapper Jul 02 '14 at 15:58
  • @O.R.Mapper, as far as I can tell there is no reason not to so. It mainly overrides some `protected` methods of the base `ObservableCollection` class and there is no other remark in the MSDN documentation. My guess is that the MS team who wrote the Intellisense assembly simply had the same troubles as we all had and this is just their own unknown private approach to solve it. – Jens H Jul 03 '14 at 08:30
0

You should use a class that implements INotifyPropertyChanged, and keep your list in an ObservableCollection<>.

For each property of a contact that you'd like to bind to the interface, make sure the setter raises a NotifyPropertyChanged event whenever the value changes, this will take care of the UI updates.

Then really it's just down to finding the correct item in the collection to change - this should not be that difficult so long as each contact has some unique key, i.e.

var contactToUpdate = myCollection.SingleOrDefault(c => c.Key == itemKey);

paul
  • 21,653
  • 1
  • 53
  • 54
0

Number 2. may seem hard but that is the way to do it.

Rebinding is going to be "flashy" and inefficient.

In your classes if you override equals then it makes them easier to find
Implementing the Equals Method
What this does is let you compare different instances for equality.
You can then use LINQ FirstOrDefault

And you are going to think this is whacked and not sure it would work for your case but have you considered a hashset backing that you expose as a public IEnumerable and just call INPC on the public IEnumerable after you make all the updates. I do this and where I use it it works great. This even survives the current selection.

paparazzo
  • 44,497
  • 23
  • 105
  • 176
0

I decided to use second approach. Here is what I came up with.

I've declared an attribute that I use for all properties with data

[Serializable]
[AttributeUsage(AttributeTargets.Property)]
public class CopyPropertyAttribute : Attribute
{
    public static void CopyProperties(object src, object dest)
    {
        // todo: cache
        var props = src.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(CopyPropertyAttribute), true).Length > 0).ToList();
        props.ForEach(p => p.SetValue(dest, p.GetValue(src, null), null));
    }
} 

And then I merge new data into old collection with following method

public void MergeInstances(ObservableCollection<Instance> existingInstances, IEnumerable<Instance> newInstances)
{
    var comp = new InstanceComparer();
    var itemsToRemove = existingInstances.Except(newInstances, comp).ToList();
    var itemsToAdd = newInstances.Except(existingInstances, comp).ToList();
    var itemsToUpdate = existingInstances.Join(newInstances, a => a.GetId(), a => a.GetId(), (a, b) => new { Old = a, New = b }).ToList();
    itemsToAdd.ForEach(a => existingInstances.Add(a));
    itemsToRemove.ForEach(a => existingInstances.Remove(a));
    itemsToUpdate.ForEach(a => CopyPropertyAttribute.CopyProperties(a.New, a.Old));
}

Also I use InstanceCollectionView.DeferRefresh() to optimize GUI update

Poma
  • 8,174
  • 18
  • 82
  • 144