【问题标题】:MVVM Model class with Thread updating properties具有线程更新属性的 MVVM 模型类
【发布时间】:2017-11-25 14:08:44
【问题描述】:

我刚开始使用 MVVM 设计模式,并根据此 youtube 视频 https://www.youtube.com/watch?v=EpGvqVtSYjs 中显示的示例构建了自己的小项目,源代码可在此处找到 http://danderson.io/posts/mvvm-session-01-02-demo-source-code-downloads/。除了示例之外,我还在模型中创建了一个新线程,它每秒更新一些模型属性。它运作良好;我的视图中的文本框像预期的那样每秒更新一次。但是,示例在模型上实现了 INotifyPropertyChanged,并且从视图绑定到模型的属性。如果我正确理解了 MVVM 模式,后一种描述的方法是针对 MVVM 模式的,INotifyPropertyChanged 应该在 ViewModel 上实现,而 View 应该绑定到 ViewModel 中的属性。这正是我在我的小项目上尝试过的,但我不知道 ViewModel 应该如何知道模型的属性是否已更新以将 PropertyChanged 事件抛出给 View。模型、视图模型和视图绑定代码的代码粘贴在下面。非常欢迎任何关于我的奋斗的想法,提前谢谢。

模型类:

public class Customer
{
    private Thread _thread;

    /// <summary>
    /// Initializes a new instance of the Customer class.
    /// </summary>
    public Customer() {
        _thread = new Thread(Temporal);
        _thread.IsBackground = true;
        _thread.Start();
    }

    private string _Name;
    /// <summary>
    /// Gets or sets the Customer's name.
    /// </summary>
    public String Name {
        get {
            return _Name;
        }
        set {
            _Name = value;
        }
    }

    public void Temporal()
    {
        int i = 0;

        while (true)
        {
            Name = "Customer #" + i.ToString();
            i++;
            System.Threading.Thread.Sleep(1000);                
        }
    }
}

ViewModel 类:

internal class CustomerViewModel : INotifyPropertyChanged
{
    /// <summary>
    /// Initializes a new instance of the CustomerViewModel class.
    /// </summary>
    public CustomerViewModel() {
        _Customer = new Customer();
    }        

    private Customer _Customer;

    public string Name
    {
        get { return _Customer.Name }
        set
        {
            _Customer.Name = value;
            OnPropertyChanged("Name");
        }
    }           

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName) {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion
}

对于我的视图中的绑定,我使用以下内容:

Text="{Binding CustomerViewModel.Name, UpdateSourceTrigger=PropertyChanged}"

【问题讨论】:

  • 在哪里实现 INotifyPropertyChanged 并不重要。它在模型和视图模型中都有效。也就是说,最好使用DispatcherTimer 进行重复操作,而不是生成线程。最后,设置UpdateSourceTrigger=PropertyChanged 对您的绑定没有影响。它只控制 TwoWay 或 OneWayToSource 绑定如何更新其源属性。一个常见的误解是它与 INotifyPropertyChanged 接口的 PropertyChanged 事件有关。
  • 我闻到你在装订的地方有问题。 @Clemens 所说的同上,但你的绑定不应该是:Text="{Binding Name}",而不是在它前面加上CustomerViewModel。您要绑定的属性名称与您正在使用的上下文相关,因此您无需输入视图模型的名称。
  • 我会考虑更新您的问题,以便更清楚地了解您实际想要实现的目标。据我所知,您希望每秒更新客户的姓名。如果是这种情况,我会看两种方法中的一种:从 ViewModel 对客户进行更新(并通知更改);或使用某种消息/事件模式从客户那里进行更新,并通知 ViewModel 以这种方式更新。另一个有用的约定是将private 成员的名称设为camelCase(个人偏好以_ 开头。希望这会有所帮助。
  • 感谢大家的反馈。这真的帮助了我前进。

标签: c# wpf mvvm


【解决方案1】:

您的代码中有很多问题。

问题 1

您的代码将无法编译,因为您的 CustomerViewModel 没有使用 Customer 的实例,而是使用了 Customer 类。看看下面的代码,内联阅读我的 cmets:

public string Name
{
    get { return Customer.Name } // <-- Should be _Customer.Name
    set
    {
        Customer.Name = value; // <-- Should be _Customer.Name
        OnPropertyChanged("Name");
    }
}      

问题 2

当您的Customer 实例被您的线程更改时,您的CustomerViewModel 完全不知道此更改。您的 Model 不能拥有PropertyChanged 并没有一成不变的规则。因此,在您的Customer 类中使用自定义事件或PropertyChanged。像这样:

public class Customer : INotifyPropertyChanged
{
    // other code...
    private string _Name;
    public String Name
    {
        get
        {
            return _Name;
        }
        set
        {
            _Name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    //... other code
}

所以现在您的Customer 类能够在其属性发生变化时通知订阅者。

问题 3

虽然你的CustomerViewModel 有关于PropertyChanged 的代码,但它没有实现INotifyPropertyChanged 所以无论谁使用它,例如你的视图,都不知道它是可观察的。因此,如果您的CustomerViewModel 更改,视图将不知道。因此它不会更新。

因此,将CustomerViewModel中的代码更改为实现INotifyPropertyChanged,同时订阅Customer.PropertyChanged,然后触发视图自行更新。

internal class CustomerViewModel : INotifyPropertyChanged
{
    public CustomerViewModel()
    {
        _Customer = new Customer();
        // Subscribe to the PropertyChanged event
        _Customer.PropertyChanged += _Customer_PropertyChanged;
    }

    private void _Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // Check if Name changed, if yes, trigger the event so subscribers (view) can update.
        if (e.PropertyName == nameof(Customer.Name))
        {
            OnPropertyChanged(nameof(Name));
        }
    }

    private Customer _Customer;

    public string Name
    {
        get { return _Customer.Name; }
        set
        {
            _Customer.Name = value;
            OnPropertyChanged("Name");
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion
} 

最后是 XAML:

<TextBox Text="{Binding Path=Name}">

【讨论】:

  • 感谢 CodingYoshi 的反馈。关于问题 1 和问题 3;我完全同意,在简化示例的同时,我犯了一些错误。在这里学到的教训:在 Stackoverflow 上发布任何内容之前还要编译和运行我的代码,很抱歉浪费了您的宝贵时间。关于问题2;你可能是对的,我(也)必须在模型级别实现事件。我试图避免这种情况,但我意识到这是我唯一的选择,因为我不希望 ViewModel 轮询 Model 以检查某些属性是否已更改。
猜你喜欢
  • 1970-01-01
  • 2022-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多