如何在WPF ViewModel中实现可观察的整数?

25

在我的 MVVM ViewModel 中,我有这样的字段

public int Delta { get; private set; }

但是当我像这样更新它:

Delta = newValue;

UI没有刷新。

我曾认为数据绑定会自动刷新UI。例如,我可以将集合声明为ObservableCollection,然后数据绑定就会生效。

但是,没有ObservableInt这样的东西,那么该如何告诉View需要进行刷新呢?

也许我应该触发某个事件“通知属性已更改”之类的东西吗?


是的,你走对了路。你需要触发一个属性变更事件。参考google INotifyPropertyChanged。 - Ritch Melton
但是,视图如何知道当某个属性被引发时需要更新某个控件? - Oleg Vazhnev
视图将通过您设置的绑定来了解它。 - BigL
6个回答

43

你有两个选择:

  1. 在你的类上实现INotifyPropertyChanged接口。
  2. 继承自DependencyObject并将Delta实现为DependencyProperty。

最简单的选项是#1。你可以很容易地在你的类上实现INotifyPropertyChanged接口:

public class YourClass : INotifyPropertyChanged
{

  private int _delta;
  public int Delta
  {
      get { return _delta; }
      set { _delta = value; PropertyChanged?.Invoke(nameof(Delta)); }
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

你可以在MSDN上阅读更多关于使用和实现依赖属性的内容。


3
+1 绝对正确,但对于任何新手来说,我鼓励你也查看其他答案,因为采用现有的工具包将需要更少的工作量,并且能够更轻松地学习该框架。 - jeremyalan
我能否以某种方式省略创建“private int _delta;”? 我希望这由C#自动生成,就像普通属性一样。 - Oleg Vazhnev
1
@javapowered:很遗憾,不行。你不能拥有一个“部分实现”的自动属性。如果你需要在setter中调用方法(就像这种情况),你必须提供支持字段和属性实现。 - LBushkin
如果您只想绑定一次而不观察更改,那么是否必须使用NotifyPropertyChanged(“Delta”)? - Emil

7

顺便提一下,除了改进答案外,C# 6.0和7.0的其他新功能也有助于使其更加紧凑:

public class Prop<T> : INotifyPropertyChanged
{
    private T _value;

    public T Value
    {
        get => _value; 
        set { _value = value; NotifyPropertyChanged(nameof(Value)); }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    internal void NotifyPropertyChanged(string propertyName) => 
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

这样做可以避免使用任何“嵌入式值”(即属性名称),从而使代码重构更加安全。由于c# 6.0和7.0的新表达式体特性,也不需要冗余的代码块。

5
这句话的意思是"这里应该用nameof(value)而不是nameof(_value)吗?而且可以通过在NotifyPropertyChanged()方法声明上使用CallerMemberName属性来避免使用参数。" - UweBaemayr

6
使用 @LBushKin 的答案,我进行了修改。
public class Prop<T> : INotifyPropertyChanged
{
    private T _value;
    public T Value
    {
        get { return _value; }
        set { _value = value; NotifyPropertyChanged("Value"); }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    internal void NotifyPropertyChanged(String propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

并进行设置:

class MainWindow ...
    // a bool with initial value of true
    public static Prop<bool> optionBool { get; set; } = new Prop<bool>{ Value = true };

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // connect UI to be able to use the Prop
        DataContext = this;
    }

并且要使用它:

<Grid ...
    <CheckBox Content="Da Check" ... IsChecked="{Binding optionBool.Value}"/>

这里还有一个集合和两个属性版本: Utils.ObservableProperties.cs(此存储库包含几个相关类)。

3
只需在您的类中实现INotifyPropertyChanged接口并使用它来引发属性更改事件,然后UI将会更新。如果您正在使用MVVM项目模板,那么您很可能已经实现了一个帮助方法,您只需要使用它即可。 MSDN INotifyPropertyChanged GalaSoft MVVM Light Toolkit

2

1
请注意,即使在ObservableCollection上,如果集合本身的引用发生了更改,您仍需要引发属性更改。 - Joel Cochran

0
添加到https://dev59.com/fmsy5IYBdhLWcg3wyxCi#8316100,现在有一种新的简单方法可以实现此操作,而无需记住在每个位置中跟踪PropertyChanged?.Invoke(nameof(Delta))
public class YourClass : INotifyPropertyChanged
{
   private int _delta;
   public int Delta
   {
      get { return _delta; }
      set { 
            _delta = value;               
            // Call OnPropertyChanged whenever the property is updated
            OnPropertyChanged();
          }
   }
   // Declare the event
   public event PropertyChangedEventHandler PropertyChanged;

   public YourClass()
   {
   }

   // Create the OnPropertyChanged method to raise the event
   // The calling member's name will be used as the parameter.
   protected void OnPropertyChanged([CallerMemberName] string name = null)
   {
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
   }
}

它利用CallerMemberName来跳过手动输入属性名称的步骤。更多详细信息请参阅MSDN文档


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接