C# 如何正确实现我的bool属性的INotifyPropertyChanged并绑定到CheckBox.IsChecked?

C# 如何正确实现我的bool属性的INotifyPropertyChanged并绑定到CheckBox.IsChecked?,c#,wpf,data-binding,C#,Wpf,Data Binding,我是新手。我一直在尝试数据绑定,并想尝试将视图中的复选框双向绑定到一个单独的类(我称之为“State”)中的布尔值。关键是要确保它们始终同步 因此,我在视图中创建了一个复选框,并将其绑定到State类中前面提到的boolean属性,同时还有一个绕过复选框并直接切换boolean属性的按钮(贴有“Ninja!”)。重点是测试复选框的数据绑定在属性更改时是否有反应。但是,我无法最好地理解当属性更改时,OnPropertyChanged方法应该如何调用 以下是我目前掌握的情况: <CheckBo

我是新手。我一直在尝试数据绑定,并想尝试将视图中的复选框双向绑定到一个单独的类(我称之为“State”)中的布尔值。关键是要确保它们始终同步

因此,我在视图中创建了一个复选框,并将其绑定到State类中前面提到的boolean属性,同时还有一个绕过复选框并直接切换boolean属性的按钮(贴有“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"/>
以及视图上用于初始化和处理事件的代码:

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();
        }
    }
}
命名空间TestTwoWayBinding
{
/// 
///MainWindow.xaml的交互逻辑
/// 
公共部分类主窗口:窗口
{
私营国家;
公共主窗口()
{            
初始化组件();
_状态=新状态((bool)复选框.IsChecked);
}
私有无效按钮\u单击(对象发送者,路由目标e)
{
_state.Toggle();
}
}
}

从我收集的信息来看,OnPropertyChanged需要一个字符串propertyName,但我不知道这需要什么。当我输入属性名(选中)时,它自然是指布尔值,而不是字符串。我没有得到什么?我还做错了什么,因为当我通过按钮更改属性时,复选框没有注册属性更改?

OnPropertyChanged方法期望
Checked
属性的名称作为参数-此时,您正在传递它的值

这意味着,将
选中的
属性声明更改为:

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

你真的很接近。您需要进行2个小更改,然后您的测试工作正常:

  • 将窗口的
    DataContext
    分配给_state变量
  • 将字符串“Checked”放入
    OnPropertyChanged
    中,并在
    OnPropertyChanged
    方法中将
    propertyName
    传递给
    PropertyChangedEventArgs
  • 因此,您的主窗口将变成:

        public MainWindow()
        {
            InitializeComponent();
            _state = new State((bool)checkBox.IsChecked);
            this.DataContext = _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));
            }
        }
    }
    

    作为新手,我建议您了解更多关于模型视图模型设计模式的知识。这是WPF的一种常见模式,有助于鼓励分离关注点(将业务逻辑排除在用户界面逻辑之外)

    这两个答案建议您传递字符串literal
    “Checked”
    将起作用,但IMHO不是最好的方法。相反,我更喜欢在实现
    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()
    方法,使其具有线程安全性,即在将事件字段与
    null
    进行比较和实际引发事件之间,如果某些代码取消订阅
    PropertyChanged
    事件的最后一个处理程序,则不会崩溃。通常情况下,这不是问题,因为这些属性几乎总是只能从单个线程访问,但这很容易防范,而且是一个好习惯

    注意,在C#6中,您可以选择只编写
    PropertyChanged?.Invoke(这是新的PropertyChangedEventArgs(propertyName))。还不是每个人都在100%的时间使用新的编译器,所以我只是把它作为您的可选选择

    当然,您还需要正确设置
    DataContext
    ,如所示:

    不过,就个人而言,我不确定我是否会为构造函数而烦恼。您似乎没有其他代码可以设置
    复选框。IsChecked
    ,因此在我看来,您总是会得到默认值。此外,如果没有参数化构造函数,就不能在XAML中创建视图模型类;将来,您可能更喜欢这样配置
    DataContext
    。例如:

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

    另见相关问答。这里的问题实际上是关于一些不同的东西-他们想要实现接口,而不必在属性setter中显式地编写任何内容-但不管是好是坏,他们得到的答案实际上更多地是关于您的场景,这里的问题只是简化属性设置器实现,而不是使其完全自动化

    我必须承认,我本以为还有另外一个问题,可以把你的标记为复制品。我发现了很多相关的问题。但是没有什么直接关注于“如何实现和使用实现了
    INotifyPropertyChanged
    ”的视图模型,这才是您的问题的真正含义


    附录

    我做了更多的搜索,虽然这些看起来都不是完全相同的,但它们都有很好的信息来帮助解决关于实现的问题
    public MainWindow()
    {
        InitializeComponent();
        _state = new State((bool)checkBox.IsChecked);
        this.DataContext = _state;
    }
    
    <Window.DataContext>
      <l:State Checked="True"/>
    </Window.DataContext>
    
    public MainWindow()
    {
        InitializeComponent();
        _state = (State)this.DataContext;
    }