【问题标题】:Updating UI when a model property changes in an ObservableCollection?当 ObservableCollection 中的模型属性更改时更新 UI?
【发布时间】:2018-05-18 01:46:17
【问题描述】:

我有一个视图,其中包含一组从 Web 服务获取的图像 我在此类的列表中收到它们:

 public class ImageModel 
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string imageUrl { get; set; }
    }

在每张图片下我显示了一个投票按钮,因此我在上面的模型中添加了另一个 bool 属性:

 public bool UpVoted { get; set; }

显示这些图像的 ListView 绑定到 ObservableCollection<ImageModel > ,当用户单击投票图标时,我想通过将 UpVoted 的值转换为相应图标的转换器更改投票图标:一个命令执行这个方法:

    private void OnVoting(ImageModel image)
    {
        Images.Single(x => x.id == image.id).UpVoted = !image.UpVoted;
    }

问题是 UI 没有更新,为了确保我理解问题,我将模型转换为 View 模型并对 UpVoted 属性进行了必要的更改(我正在使用 MVVM 灯光库)

bool upVoted;
        public bool UpVoted
        {
            get { return upVoted; }
            set
            {
                Set(ref upVoted, value);
            }
        }

它现在可以工作了, 所以我需要将UpVoted 绑定到 UI,所以它会在它改变时更新

【问题讨论】:

  • 这里有什么问题?据我所知,您刚刚解释了 MVVM 的工作原理。
  • 拥有可观察的 ImageModels 列表只会在添加或删除该列表的条目时通知。如果您希望模型通知其属性之一的更改,您必须在那里执行或像您一样使用 ViewModel。如果你想存储它或类似的东西,我只会让 ViewModel 引用模型的属性。
  • 为了补充@Fildor 所说的,其中一种解决方案(至少在 UWP 中)是在您通过设置将 PropertyChanged 事件处理程序添加到列表中时订阅通知您的 ObservableCollection 的方法一个项目已被修改,而当您的项目已从集合中删除时则相反。
  • @Fildor 你能否详细说明一下:“如果你想存储它或类似的东西,我只会让 ViewModel 引用模型的属性”.. 如何实现这一点?
  • 我只是提到“我把模型变成了视图模型”——我两者都有。所以你可以分离关注点。模型保存数据,ViewModel 为视图准备它们。因此,您可以保持模型原样,并且视图模型关心提出更改通知。

标签: c# xamarin mvvm xamarin.forms mvvm-light


【解决方案1】:

首先 你的模型类必须继承自 MvxNotifyPropertyChanged

public class ImageModel : MvxNotifyPropertyChanged
    {
        public int Id { get; set; }
        public string Name { get; set; }
        private bool upVoted ;
        public bool UpVoted 
        {
            get { return upVoted ; }
            set { upVoted = value; RaisePropertyChanged(() => UpVoted ); }
        }
    }

然后MvxValueConverter你准备好了

【讨论】:

  • 是的,抽象地说:当一个元素的属性发生变化时,可观察集合不会更新视图。元素必须自己完成。 (或者该集合订阅其元素上的 propertychanged 事件,但我不推荐上述实现)
  • 我选择了 INPC,但很想知道MvxNotifyPropertyChanged 在哪里?
  • @mshwf - 注意:MvxNotifyPropertyChanged 特定于 MvvmCross 库。 INotifyPropertyChanged 是所有BindableObjects 支持的接口,所以如果使用不同的MVVM 库,请寻找实现INotifyPropertyChanged 的基类。例如,在TinyMvvm 中,要继承的类是TinyMvvm.ViewModelBase。如果您直接继承INotifyPropertyChanged,那么您需要自己实现所需的成员。我会添加一个答案来说明这一点。
【解决方案2】:

Mustafa 的回答提到了一个特定于 MvvmCross 库的类。
另一种选择是TinyMvvm

如果您想编写自己的 MVVM(或了解 MVVM 的工作原理), 一般的模式是实现INotifyPropertyChanged:Implement Property Change Notification,其中I discuss here

实现 INotifyPropertyChanged 的​​一种便捷方法是创建一个执行该实现的基类,然后从该基类继承。您可以将该示例中的代码用作您的基类。或者使用稍微不同的实现,避免手动将属性名称作为字符串传递:

using System.ComponentModel;
using System.Runtime.CompilerServices;

// Use this as base class for all your "view model" classes.
// And possibly for your (domain) model classes.
// E.g.:  "public class MyLoginViewModel : HasNotifyPropertyChanged".
// OR     "public class MyLoginModel : HasNotifyPropertyChanged".
// Give it whatever name you want, for ViewModels I suggest "ViewModelBase".
public class HasNotifyPropertyChanged : INotifyPropertyChanged
{
    // --- This is pattern to use to implement each property. ---
    //     This works for any property type: int, Color, etc.
    //     What's different from a standard c# property, is the "SetProperty" call.
    //     You will often write an IValueConverter (elsewhere) to use in XAML to convert from string to your property type,
    //     or from your property type to a type needed in your UI.
    //     Comment out this example property if you don't need it.
    /// <summary>
    /// Set to "true" at end of your initialization.
    /// Then can use Property Trigger on Ready value=true in XAML to do something when your instance is ready for use.
    /// For example, load something from web, then trigger to update UI.
    /// </summary>
    private bool _ready;
    public bool Ready
    {
        get => _ready;
        set => SetProperty(ref _ready, value);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void SetProperty<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
    {
        if (property == null || !property.Equals(value))
        {
            property = value;
            RaisePropertyChanged(propertyName);
        }
    }

    protected void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

同样,上述代码的替代方法是使用现有的 MVVM 库。

对于另一种选择,不需要在所有属性设置器中编写“SetProperty(..)”或“OnPropertyChanged(..)”,请通过 Google 了解有关使用 Fody/PropertyChanged 的信息.那么你就不需要上面的任何代码了;您的课程将简单地从INotifyPropertyChanged 继承。 (在应用启动时,您调用一个方法,将所需的逻辑“注入”到所有 INotifyPropertyChanged 类的所有属性中。)

致谢:上面示例中的代码模式基于其中一个开源库。它可能来自 TinyMvvm。

【讨论】:

    【解决方案3】:

    您没有说明您使用的是哪种容器,但并非所有控件都默认设置为支持双向通知。所以你可能需要添加一个

    Mode=TwoWay
    

    从后端获取数据已更改的通知。或者正如 Mustafa 先前的回答所表明的,您可能需要验证您的类是否正在使用 mvvm light 实现 InotifyPropertyChanged 事件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-15
      • 1970-01-01
      • 1970-01-01
      • 2020-07-22
      • 1970-01-01
      • 2014-01-01
      • 1970-01-01
      • 2013-03-09
      相关资源
      最近更新 更多