MVVMCross ändert ViewModel innerhalb einer MvxBindableListView
-
12-12-2019 - |
Frage
Kleines Problem mit meiner Android-Anwendung und ich weiß nicht, wie ich es mit MVVM Cross lösen kann.
Hier ist mein Modell
public class Article
{
string Label{ get; set; }
string Remark { get; set; }
}
Mein ViewModel
public class ArticleViewModel: MvxViewModel
{
public List<Article> Articles;
....
}
Mein 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>
...
Und hier kommt mein Problem, das „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'}}"
/>
...
Der „Click“-Befehl namens „MyTest“ ist mit dem von MvxBindableListView angegebenen Element verknüpft.Mit anderen Worten: Klicken Sie auf die Suche nach einem Befehl „MyTest“ in meinem Modell „Article“ statt in meinem ViewModel.Wie kann ich dieses Verhalten ändern, um mein ViewModel „ArticleViewModel“ zu verknüpfen, das für mein MvxBindableListView verantwortlich ist?
Irgendwelche Vorschläge?
Lösung
Ihre Analyse ist definitiv richtig, was die Stelle angeht, an der das Klickereignis eine Bindung eingehen möchte.
Im Allgemeinen verfolge ich zwei Ansätze:
- Verwenden Sie ItemClick für die Liste
- Verwenden Sie Click weiterhin, führen Sie jedoch eine Umleitung auf der ViewModel-Seite durch.
Also...1
Der Hauptmenü im Tutorial hat ein ViewModel ein bisschen wie:
public class MainMenuViewModel
: MvxViewModel
{
public List<T> Items { get; set; }
public IMvxCommand ShowItemCommand
{
get
{
return new MvxRelayCommand<T>((item) => /* do action with item */ );
}
}
}
Dies wird in Axml verwendet als:
<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"
/>
Dieser Ansatz kann nur für ItemClick für das gesamte Listenelement durchgeführt werden – nicht für einzelne Unteransichten innerhalb der Listenelemente.
Oder...2
Da wir keine haben RelativeSource
Bindungsanweisungen in mvx, diese Art der Umleitung kann im ViewModel/Model-Code erfolgen.
Dies kann erreicht werden, indem ein verhaltensaktivierter Wrapper des Model-Objekts anstelle des Model-Objekts selbst präsentiert wird – z. B.Verwendung einer 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; } }
}
Ihr Axml müsste dann Bindungen verwenden wie:
<TextView ...
local:MvxBind="{'Text':{'Path':'TheArticle.Label'}}" />
Und
<ImageButton
...
local:MvxBind="{'Click':{'Path':'TheCommand.MyTest'}}" />
Ein Beispiel für diesen Ansatz ist das Conference-Beispiel, das verwendet WithCommand
Jedoch...Bitte beachten Sie, dass bei der Verwendung WithCommand<T>
Wir haben ein Speicherleck entdeckt – im Grunde weigerte sich die GarbageCollection, die eingebetteten Dateien zu sammeln MvxRelayCommand
- weshalb WithCommand<T>
Ist IDisposable
und warum BaseSessionListViewModel löscht die Liste und verwirft die WithCommand-Elemente, wenn Ansichten getrennt werden.
Update nach Kommentar:
Wenn Ihre Datenliste groß ist und Ihre Daten fest sind (Ihre Artikel sind Modelle ohne PropertyChanged) und Sie nicht den Aufwand für die Erstellung einer großen Liste auf sich nehmen möchten List<WrappedArticle>
Dann könnte eine Möglichkeit darin bestehen, a zu verwenden WrappingList<T>
Klasse.
Dies ist dem Ansatz im Microsoft-Code sehr ähnlich – z.bei der Virtualisierung von Listen in WP7/Silverlight - http://shawnoster.com/blog/post/Improving-ListBox-Performance-in-Silverlight-for-Windows-Phone-7-Data-Virtualization.aspx
Für Ihre Artikel könnte das sein:
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
}