如何正确实现INotifyPropertyChanged以及绑定到CheckBox.IsChecked属性?该问题涉及到IT技术。

4

初学者在此。我一直在努力理解数据绑定,并想尝试将视图中的复选框双向绑定到一个名为“State”的单独类中的布尔值,以确保它们始终同步。

因此,我在视图中制作了一个复选框,并将其绑定到State类中上述的布尔属性,同时伴随着一个绕过复选框并直接切换布尔属性的按钮(恰当地标记为'Ninja!')。 这是为了测试当属性更改时复选框的数据绑定是否会产生反应。 然而,我不能找出当属性更改时OnPropertyChanged方法应该如何被调用。

这是我目前的代码:

<CheckBox x:Name="checkBox" Content="CheckBox" HorizontalAlignment="Left" Margin="232,109,0,0" VerticalAlignment="Top" IsChecked="{Binding Checked, Mode=TwoWay}"/>
<Button x:Name="button" Content="Ninja!" HorizontalAlignment="Left" Margin="228,182,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>

以下是我编写的“State”类的代码:

namespace TestTwoWayBinding
{
    class State : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private bool _checked;
        public bool Checked {
            get
            {
                return _checked;
            }
            set
            {
                _checked = value;
                OnPropertyChanged(Checked);
            }
        }       

        public void Toggle()
        {
            if (!Checked)
            {
                Checked = true;
            }
            else
            {  
                Checked = false;

            }
        }

        public State(bool c)
        {
            this.Checked = c;
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if(PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(Checked));
            }
        }
    }
}

视图上的代码后台用于初始化和处理事件:

namespace TestTwoWayBinding
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private State _state;
        public MainWindow()
        {            
            InitializeComponent();
            _state = new State((bool)checkBox.IsChecked);
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            _state.Toggle();
        }
    }
}

据我所了解,OnPropertyChanged期望一个字符串propertyName,但我不知道这里需要什么。当我输入属性的名称(Checked)时,那自然是指一个布尔值,而不是一个字符串。我哪里没理解?还有,当我通过按钮更改复选框时,为什么它没有注册属性更改?
3个回答

6
两个答案建议您传递字符串文字“Checked”将起作用,但在我看来并不是最好的方法。相反,我更喜欢在实现OnPropertyChanged()方法时使用[CallerMemberName]。(我不知道第三个答案是什么...它似乎与这个问题没有任何关系,我猜它只是从其他地方复制/粘贴过来的)。
以下是我如何编写您的State类的示例:
class State : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private bool _checked;
    public bool Checked
    {
        get { return _checked; }
        set { _checked = value; OnPropertyChanged(); }
    }       

    public void Toggle()
    {
        Checked = !Checked;
    }

    public State(bool c)
    {
        this.Checked = c;
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

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

关键在于带有[CallerMemberName]标记的参数将自动填充为来自调用者的正确值,只需不传递任何值即可。 null的默认值存在只是为了让编译器允许调用者不传递值。
请注意,我还简化了Toggle()方法。没有必要使用if语句将一个bool值转换为另一个值;那就是布尔运算符的作用。
我还更改了OnPropertyChanged()方法,使其线程安全,即如果某些代码取消订阅了PropertyChanged事件的最后一个处理程序,则不会崩溃。通常,这不是什么问题,因为这些属性几乎总是仅从单个线程访问,但很容易保护并且是一个很好的习惯。
请注意,在C# 6中,您可以选择只写PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(propertyName));作为方法体。并非所有人都已经100%使用新编译器,所以我只是将其作为可选项提供给您提及。
当然,您还需要正确设置DataContext,如其他答案之一中所示:
public MainWindow()
{
    InitializeComponent();
    _state = new State((bool)checkBox.IsChecked);
    this.DataContext = _state;
}

个人而言,我不确定是否需要构造函数。你似乎没有其他代码来设置checkBox.IsChecked,所以我认为你总是会得到默认值。此外,如果您的视图模型类没有参数化构造函数,则无法在XAML中创建它;将来,您可能更喜欢像这样配置DataContext。例如:

<Window.DataContext>
  <l:State Checked="True"/>
</Window.DataContext>

在窗口的构造函数中:

public MainWindow()
{
    InitializeComponent();
    _state = (State)this.DataContext;
}

另请参阅相关Q&A 自动INotifyPropertyChanged。那里的问题实际上是关于不必在属性setter中显式编写任何内容就可以实现接口,但无论是否更好,他们得到的答案都更多地涉及您的情况,即简化属性setter实现而不是使其完全自动化。
我必须承认,我本以为已经有另一个问题能标记为重复了。而我确实找到了很多相关的问题。但没有直接关注“如何实现和使用实现INotifyPropertyChanged的视图模型?”这个问题,这似乎是你的问题所涉及的内容。
补充:
我做了更多搜索,虽然这些问题似乎都不被认为是确切的重复,但它们都有很好的信息来回答关于实现INotifyPropertyChanged的问题:
使用Attributes… INotifyPropertyChanged INotifyPropertyChanged for model and viewmodel BindableBase vs INotifyChanged 如何编写MVVM(WPF)中的ViewModelBase

在C#6中,使用空值条件运算符,OnPropertyChanged方法现在可以缩短为一行代码:PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - Toby

3
你离成功非常接近。你需要做两个小修改,才能使你的测试工作:
  1. 将你窗口的 DataContext 分配给 _state 变量。
  2. OnPropertyChanged 中放入字符串 "Checked",并将 propertyName 传递给 PropertyChangedEventArgsOnPropertyChanged 方法中。
所以你的 MainWindow 构造函数变为:
    public MainWindow()
    {
        InitializeComponent();
        _state = new State((bool)checkBox.IsChecked);
        this.DataContext = _state;
    }

而 State 类文件如下:

class State : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private bool _checked;
    public bool Checked
    {
        get
        {
            return _checked;
        }
        set
        {
            _checked = value;
            OnPropertyChanged("Checked");
        }
    }

    public void Toggle()
    {
        if (!Checked)
        {
            Checked = true;
        }
        else
        {
            Checked = false;

        }
    }

    public State(bool c)
    {
        this.Checked = c;
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

作为一名新手,我建议您学习更多关于Model-View-ViewModel MVVM设计模式的知识。这是一种常见的WPF模式,有助于促进关注点分离(将业务逻辑与用户界面逻辑分开)。

0

OnPropertyChanged 方法期望传入 Checked 属性的名称作为参数 - 目前您正在传递其值!

这意味着,将 Checked 属性声明更改为:

public bool Checked {
    get
    {
        return _checked;
    }
    set
    {
        _checked = value;
        OnPropertyChanged("Checked");
    }
 }    

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