Little problem with my Android application and I don't know how to solve it with MVVM Cross.
Here is my Model
public class Article
{
string Label{ get; set; }
string Remark { get; set; }
}
My ViewModel
public class ArticleViewModel: MvxViewModel
{
public List<Article> Articles;
....
}
My layout.axml ...
<LinearLayout
android:layout_width="0dip"
android:layout_weight="6"
android:layout_height="fill_parent"
android:orientation="vertical"
android:id="@+id/layoutArticleList">
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/editSearch"
android:text=""
android:singleLine="True"
android:selectAllOnFocus="true"
android:capitalize="characters"
android:drawableLeft="@drawable/ic_search_24"
local:MvxBind="{'Text':{'Path':'Filter','Mode':'TwoWay'}}"
/>
<Mvx.MvxBindableListView
android:id="@+id/listviewArticle"
android:choiceMode="singleChoice"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
local:MvxItemTemplate="@layout/article_rowlayout"
local:MvxBind="{'ItemsSource':{'Path':'Articles'}}" />
</LinearLayout>
...
And here comes my problem, the "article_rowlayout"
...
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/blue">
<TextView
android:id="@+id/rowArticleLabel"
android:layout_width="0dip"
android:layout_weight="14"
android:layout_height="wrap_content"
android:textSize="28dip"
local:MvxBind="{'Text':{'Path':'Label'}}" />
<ImageButton
android:src="@drawable/ic_modify"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="wrap_content"
android:id="@+id/rowArticleButtonModify"
android:background="@null"
android:focusable="false"
android:clickable="true"
local:MvxBind="{'Click':{'Path':'MyTest'}}"
/>
...
The "Click" command called "MyTest" is linked on the item given by the MvxBindableListView. In other words, Click search for a command "MyTest" in my model "Article", instead of my ViewModel. How can I change that behaviour in order to link my ViewModel "ArticleViewModel" which is responsible of my MvxBindableListView?
Any suggestions?
Your analysis is definitely correct about where the click event is trying to bind.
There are two approaches I generally take:
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With