2

Is there a way to bind directly to a Collection in the model and manually tell WPF that the binding needs refreshing without having to create an ObservableCollection for it in the viewmodel?

<ListBox ItemsSource="{Binding Position.PossibleMoves}">
...
</ListBox>

Position is my model, part of a chess library, and PossibleMoves is a Collection within it. I do not want to implement INotifyProperty changed or put ObservableCollections in a stand alone optimized library.

I want to avoid copying PossibleMoves into an ObservableCollection every time the position is updated. The data binding works on initialization but it would be handy if I could also refresh the binding at will inside the viewmodel.

Calling OnNotifyPropertyChanged("Position.PossibleMoves") from the viewmodel doesn't work because the reference to the collection itself does not change.

FreddyFlares
  • 473
  • 6
  • 17
  • 1
    possible duplicate of [How to force a WPF binding to refresh?](http://stackoverflow.com/questions/5676202/how-to-force-a-wpf-binding-to-refresh) – Steve Jan 31 '14 at 23:12
  • Unfortunately not, that solution is for refreshing the binding from the view by calling GetBindingExpression(dp).UpdateTarget on a FrameworkElement. – FreddyFlares Jan 31 '14 at 23:50
  • Is ListBox not a FrameworkElement? – Steve Jan 31 '14 at 23:52
  • Yes but how do I get the view to make the call using mvvm? The viewmodel knows nothing about the view there is only databinding. – FreddyFlares Feb 01 '14 at 00:07
  • Well, you will have to have the view subscribe to *some* sort of event that causes this to happen. Typically, that would be `INotifyPropertyChanged`. You could create your own event in your ViewModel. With an event the VM does not have to have a reference to the view. – Steve Feb 01 '14 at 00:10
  • Are you trying to refresh the list or properties within the list items? – jamesSampica Feb 01 '14 at 02:58
  • The List gets cleared and repopulated with possible ChessMoves when a move is made on the board in the model. In the ViewModel I want to be able to signal when this happens to avoid duplicating data. – FreddyFlares Feb 08 '14 at 10:18

2 Answers2

4

You can do this by using an attached behavior to bind a handler to an event that gets triggered in the view model. You can't bind directly to events though so you have to wrap them in a class like so:

public class Refresher
{
    public delegate void RefreshDelegate();
    public event RefreshDelegate Refresh;

    public void DoRefresh()
    {
        if (this.Refresh != null)
            this.Refresh();
    }
}

Now add an instance of that to your view model:

public class MyViewModel
{
    public IList<string> Items { get; set; }

    private Refresher _Refresher = new Refresher();
    public Refresher Refresher {get {return this._Refresher;}}
}

Next create an attached behavior that registers a delegate instance with that event and forces the listbox to refresh its binding:

public static class RefreshBehavior
{
    public static readonly DependencyProperty RefresherProperty = DependencyProperty.RegisterAttached(
        "Refresher",
        typeof(Refresher),
        typeof(RefreshBehavior),
        new PropertyMetadata(null, OnRefresherChange));

    public static void SetRefresher(DependencyObject source, Refresher value)
    {
        source.SetValue(RefresherProperty, value);
    }

    public static Refresher GetRefresher(DependencyObject source)
    {
        return (Refresher)source.GetValue(RefresherProperty);
    }

    private static void OnRefresherChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Refresher.RefreshDelegate handler = () =>
        {
            var listBox = d as ListBox;
            listBox.Items.Refresh();
        };

        if (e.NewValue != null)
            (e.NewValue as Refresher).Refresh += handler;
        if (e.OldValue != null)
            (e.OldValue as Refresher).Refresh -= handler;
    }
}

And finally attach it to your listbox in the xaml:

<ListBox ItemsSource="{Binding Items}"
    local:RefreshBehavior.Refresher="{Binding Refresher}"/>

That's it. Call Refresher.DoRefresh() in your view model and it will force a listbox update.

This works but it's really hammering a square peg into a round hole. If I were you I'd do everything I could to try and do proper collection changed notification in your view model. I understand you wanting to keep ObservableCollection out of your model but there are ways to proxy change notification automatically (e.g. Castle DynamicProxy).

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
0

You need to NotifyPropertyChange for the PossibleMoves from inside the Position class or make a property that delegates to the Position.PossibleMoves and notify that one.

atomaras
  • 2,468
  • 2
  • 19
  • 28