【问题标题】:MVVMCross changing ViewModel within a MvxBindableListViewMVVMCross 在 MvxBindableListView 中更改 ViewModel
【发布时间】:2012-09-22 19:43:46
【问题描述】:

我的 Android 应用程序小问题,我不知道如何使用 MVVM Cross 解决它。

这是我的模型

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

我的视图模型

public class ArticleViewModel: MvxViewModel
{
    public List<Article> Articles;
    ....

}

我的 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>
...

我的问题来了,“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'}}"
          />
...

名为“MyTest”的“Click”命令链接在 MvxBindableListView 给出的项目上。换句话说,在我的模型“Article”而不是我的 ViewModel 中单击搜索命令“MyTest”。如何更改该行为以链接负责我的 MvxBindableListView 的 ViewModel“ArticleViewModel”?

有什么建议吗?

【问题讨论】:

    标签: android listview xamarin.android viewmodel mvvmcross


    【解决方案1】:

    关于点击事件尝试绑定的位置,您的分析绝对正确。

    我通常采取两种方法:

    1. 使用物品点击列表
    2. 继续使用 Click,但在 ViewModel 端进行一些重定向。

    所以...1

    教程中的Main Menu有一个ViewModel有点像:

    public class MainMenuViewModel
        : MvxViewModel
    {
        public List<T> Items { get; set; }
    
        public IMvxCommand ShowItemCommand
        {
            get
            {
                return new MvxRelayCommand<T>((item) => /* do action with item */ );
            }
        }
    }
    

    这在 axml 中用作:

    <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"
      />
    

    这种方法只能用于整个列表项上的 ItemClick - 而不是列表项中的单个子视图。


    或者...2

    由于我们在 mvx 中没有任何RelativeSource 绑定指令,所以这种类型的重定向可以在 ViewModel/Model 代码中完成。

    这可以通过提供模型对象的行为启用包装器而不是模型对象本身来完成 - 例如使用List&lt;ActiveArticle&gt;:

    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; } } 
    }
    

    您的 axml 将不得不使用如下绑定:

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

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

    这种方法的一个例子是使用WithCommand的会议示例

    但是...请注意,在使用 WithCommand&lt;T&gt; 时,我们发现了内存泄漏 - 基本上 GarbageCollection 拒绝收集嵌入的 MvxRelayCommand - 这就是为什么 WithCommand&lt;T&gt;IDisposable 以及为什么 BaseSessionListViewModel 会清除分离视图时列出并处置 WithCommand 元素。


    评论后更新:

    如果您的数据列表很大 - 并且您的数据是固定的(您的文章是没有 PropertyChanged 的​​模型)并且您不想产生创建大型 List&lt;WrappedArticle&gt; 的开销,那么解决此问题的一种方法可能是使用WrappingList&lt;T&gt; 班级。

    这与 Microsoft 代码中采用的方法非常相似 - 例如在 WP7/Silverlight 中的虚拟化列表中 - http://shawnoster.com/blog/post/Improving-ListBox-Performance-in-Silverlight-for-Windows-Phone-7-Data-Virtualization.aspx

    对于您的文章,这可能是:

    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
    }   
    

    【讨论】:

    • 事实上,我的列表视图上有 4 个具有行为的图像按钮。所以第一个解决方案是不可能的。对于第二个,我的问题是我从另一个框架(vici coolstorage)中获取了我的列表,并且解析整个列表以使用“ActiveArticle”创建一个新的列表会产生严重的开销(我可以有几千个项目)。但无论如何,我会尝试这个解决方案并让你知道它是否有效。顺便说一句,你不觉得在你的框架上创建一个“RelativeSource”会很困难吗?谢谢!
    • 已为您添加了替代方案 - 请参阅答案中的更新。对于“顺便说一句,你不认为这会很困难”,它需要某种方式来识别所有平台上的相对源(例如,它可能必须使用某种方式在 Droid 中导航父层次结构),然后识别什么属性绑定到(Droid 原生视图没有方便的 DataContext DependencyObject)。有可能 - 有兴趣看看你想要什么 - 随意起草你理想的 axml 解决方案可能看起来像 github/slodge/mvvmcross 中的一个问题
    • 嗨斯图尔特,你的新解决方案是我的解决方案。非常感谢。我将您的解决方案放入我的代码中,并将“理想的 axml 愿景”(对我而言)作为 github 中的一个问题。再次感谢,你很棒!
    • 很好的答案!我对解决方案 2 有一个小问题:使用 WrappedArticle 而不是仅使用 ArticleICommand 属性有什么好处?是否应该使 Article 模型“更清洁”并将行为与模型本身分开,还是有其他好处?
    • 只是代码 - 并假设 Article 已经存在(例如,来自 JSON 或 SQL 代码生成器)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-07
    • 2013-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-23
    • 1970-01-01
    相关资源
    最近更新 更多