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

时间:2016-05-01 19:23:50

标签: c# wpf data-binding

新手在这里。我一直在尝试围绕数据绑定,并希望尝试将视图中的复选框双向绑定到我称为“状态”的单独类中的布尔值。重点是确保它们始终保持同步。

所以我在视图中创建了一个复选框,并将其绑定到State-class中前面提到的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
{
    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需要一个String propertyName,但我不知道这会带来什么。当我输入属性的名称(Checked)时,那自然是指一个布尔值,而不是一个字符串。我得不到什么?还有什么我做错了,因为当我通过按钮更改它时,复选框没有注册属性更改?

3 个答案:

答案 0 :(得分:5)

建议您传递字符串文字"Checked"的两个答案将有效,但恕我直言并不是最好的方法。相反,我更倾向于在实施[CallerMemberName]方法时使用OnPropertyChanged()。 (我不知道第三个答案是什么......它似乎与这个问题没什么关系,而且我猜它只是从其他地方复制/粘贴了。)

以下是我如何撰写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方法,以便它是线程安全的,即如果某些代码取消订阅null事件之间的PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));事件的最后一个处理程序,则不会崩溃将事件字段与DataContext以及实际引发事件的时间进行比较。通常,这不是问题,因为这些属性几乎总是只从一个线程访问,但它很容易防止并且是一个很好的习惯。

请注意,在C#6中,您可以选择只为方法体写public MainWindow() { InitializeComponent(); _state = new State((bool)checkBox.IsChecked); this.DataContext = _state; } 。并非所有人都在100%的时间内使用新的编译器,所以我只是提到它作为您的可选选择。

当然,您还需要正确设置checkBox.IsChecked,如one of the other answers所示:

DataContext

虽然,就个人而言,我不确定我是否会对构造函数感到困扰。您似乎没有其他代码可以设置<Window.DataContext> <l:State Checked="True"/> </Window.DataContext> ,所以在我看来,无论如何您总是会获得默认值。此外,如果没有参数化构造函数,则无法在XAML中创建视图模型类。在将来,您可能更愿意像这样配置public MainWindow() { InitializeComponent(); _state = (State)this.DataContext; } 。 E.g:

INotifyPropertyChanged

在窗口的构造函数中:

INotifyPropertyChanged


另见相关的Q&amp; A Automatically INotifyPropertyChanged。这个问题确实存在一些不同之处 - 他们想要实现界面而不必在属性设置器中明确地写任何东西 - 但无论好坏,他们得到的答案更多地是关于你的场景,它只是在哪里简化属性设置器实现的问题,而不是使其完全自动化。

我不得不承认,我认为还有另外一个问题可以将你的标记重复。我确实找到了很多相关的问题。但是没有任何内容直接关注&#34;我如何实现和使用实现spyderlib/userconfig.py的视图模型?&#34;,这正是你的问题所针对的。


附录

我做了一些搜索,虽然这些看起来都不像是完全重复,但它们都有很好的信息来帮助解决有关实施self.read(self.filename(), encoding='utf-8')的问题:

Use of Attributes… INotifyPropertyChanged
INotifyPropertyChanged for model and viewmodel
BindableBase vs INotifyChanged
How to write “ViewModelBase” in MVVM (WPF)

答案 1 :(得分:4)

你真的很亲近。您需要做两个小的更改并且您的测试有效:

  1. 将窗口的DataContext分配给_state变量。
  2. 把字符串&#34; Checked&#34;进入OnPropertyChanged并将propertyName传递给PropertyChangedEventArgs方法中的OnPropertyChanged
  3. 所以你的MainWindow ctor变成:

        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的常见模式,有助于鼓励关注点分离(保持业务逻辑不受用户界面逻辑影响)

答案 2 :(得分:0)

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

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

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