MVVMCross changing ViewModel within a MvxBindableListView

Your analysis is definitely correct about where the click event is trying to bind.

There are two approaches I generally take:

  1. Use ItemClick on the List
  2. Continuing using Click but do some redirection on the ViewModel side.

So…1

The Main Menu in the tutorial has a ViewModel a bit like:

public class MainMenuViewModel
    : MvxViewModel
{
    public List<T> Items { get; set; }

    public IMvxCommand ShowItemCommand
    {
        get
        {
            return new MvxRelayCommand<T>((item) => /* do action with item */ );
        }
    }
}

This is used in axml as:

<Mvx.MvxBindableListView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res/Tutorial.UI.Droid"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    local:MvxBind="{'ItemsSource':{'Path':'Items'},'ItemClick':{'Path':'ShowItemCommand'}}"
    local:MvxItemTemplate="@layout/listitem_viewmodel"
  />

This approach can only be done for ItemClick on the whole list item – not on individual subviews within the list items.


Or…2

Since we don’t have any RelativeSource binding instructions in mvx, this type of redirection can be done in the ViewModel/Model code.

This can be done by presenting a behaviour-enabled wrapper of the Model object rather than the Model object itself – e.g. using a List<ActiveArticle>:

public ActiveArticle
{
   Article _article;
   ArticleViewModel _parent;

   public WrappedArticle(Article article, ArticleViewModel parent)
   {
       /* assignment */
   }

   public IMvxCommand TheCommand { get { return MvxRelayCommand(() -> _parent.DoStuff(_article)); } }

   public Article TheArticle { get { return _article; } } 
}

Your axml would then have to use bindings like:

    <TextView            ...
        local:MvxBind="{'Text':{'Path':'TheArticle.Label'}}" />

and

    <ImageButton
        ...
        local:MvxBind="{'Click':{'Path':'TheCommand.MyTest'}}" />

One example of this approach is the Conference sample which uses WithCommand

However… please note that when using WithCommand<T> we discovered a memory leak – basically the GarbageCollection refused to collect the embedded MvxRelayCommand – which is why WithCommand<T> is IDisposable and why BaseSessionListViewModel clears the list and disposes the WithCommand elements when views are detached.


Update after comment:

If your data list is large – and your data is fixed (your articles are models without PropertyChanged) and you don’t want to incur the overhead of creating a large List<WrappedArticle> then one way around this might be to use a WrappingList<T> class.

This is very similar to the approach taken in Microsoft code – e.g. in virtualizing lists in WP7/Silverlight – http://shawnoster.com/blog/post/Improving-ListBox-Performance-in-Silverlight-for-Windows-Phone-7-Data-Virtualization.aspx

For your articles this might be:

public class ArticleViewModel: MvxViewModel
{
    public WrappingList<Article> Articles;

    // normal members...
}

public class Article
{
    public string Label { get; set; }
    public string Remark { get; set; }
}

public class WrappingList<T> : IList<WrappingList<T>.Wrapped>
{
    public class Wrapped
    {
        public IMvxCommand Command1 { get; set; }
        public IMvxCommand Command2 { get; set; }
        public IMvxCommand Command3 { get; set; }
        public IMvxCommand Command4 { get; set; }
        public T TheItem { get; set; }
    }

    private readonly List<T> _realList;
    private readonly Action<T>[] _realAction1;
    private readonly Action<T>[] _realAction2;
    private readonly Action<T>[] _realAction3;
    private readonly Action<T>[] _realAction4;

    public WrappingList(List<T> realList, Action<T> realAction)
    {
        _realList = realList;
        _realAction = realAction;
    }

    private Wrapped Wrap(T item)
    {
        return new Wrapped()
            {
                Command1 = new MvxRelayCommand(() => _realAction1(item)),
                Command2 = new MvxRelayCommand(() => _realAction2(item)),
                Command3 = new MvxRelayCommand(() => _realAction3(item)),
                Command4 = new MvxRelayCommand(() => _realAction4(item)),
                TheItem = item
            };
    }

    #region Implementation of Key required methods

    public int Count { get { return _realList.Count; } }

    public Wrapped this[int index]
    {
        get { return Wrap(_realList[index]); }
        set { throw new NotImplementedException(); }
    }

    #endregion

    #region NonImplementation of other methods

    public IEnumerator<Wrapped> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void Add(Wrapped item)
    {
        throw new NotImplementedException();
    }

    public void Clear()
    {
        throw new NotImplementedException();
    }

    public bool Contains(Wrapped item)
    {
        throw new NotImplementedException();
    }

    public void CopyTo(Wrapped[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public bool Remove(Wrapped item)
    {
        throw new NotImplementedException();
    }

    public bool IsReadOnly { get; private set; }

    #endregion

    #region Implementation of IList<DateFilter>

    public int IndexOf(Wrapped item)
    {
        throw new NotImplementedException();
    }

    public void Insert(int index, Wrapped item)
    {
        throw new NotImplementedException();
    }

    public void RemoveAt(int index)
    {
        throw new NotImplementedException();
    }

    #endregion
}   

Leave a Comment