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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36970912/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:47:06  来源:igfitidea点击:

How do I correctly implement INotifyPropertyChanged for my bool property and bind to CheckBox.IsChecked?

c#wpfdata-binding

提问by Espen Moe

Novice here. I've been trying to wrap my head around databinding, and wanted to do try out two-way binding of a checkbox in the view to a boolean in a separate class that I've called "State". The point is to ensure that they are always in sync.

新手来了。我一直在尝试围绕数据绑定进行思考,并想尝试将视图中的复选框双向绑定到我称为“状态”的单独类中的布尔值。关键是要确保它们始终同步。

So I've made a checkbox in the view and bound it to the aforementioned boolean property in the State-class, accompanied by a button that bypasses the checkbox and toggles the boolean property directly (aptly labeled 'Ninja!'). The point was to test that the checkbox' databinding reacts when the property changes. However, I can't for the best of me figure out how the OnPropertyChanged-method is supposed to be invoked when the property changes.

因此,我在视图中创建了一个复选框,并将其绑定到上述 State 类中的布尔属性,并附有一个绕过复选框并直接切换布尔属性的按钮(贴切地标记为“忍者!”)。重点是测试复选框的数据绑定是否在属性更改时做出反应。但是,我无法弄清楚当属性更改时应该如何调用 OnPropertyChanged 方法。

Here's what I have so far:

这是我到目前为止所拥有的:

<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"/>

And the code for the "State"-class I've made:

以及我制作的“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));
            }
        }
    }
}

And the code-behind on the view for initialization and handling the events:

以及视图中用于初始化和处理事件的代码隐藏:

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();
        }
    }
}

From what I gather, OnPropertyChanged expects a String propertyName, but I don't know what that would entail here. When I put in the name of the property (Checked), then that naturally refers to a boolean, not a string. What am I not getting? And what else am I doing wrong, as the checkbox doesn't register the property change when I change it through the button?

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

回答by Peter Duniho

The two answers which suggest you pass the string literal "Checked"will work, but IMHO aren't the best way to do it. Instead, I prefer using [CallerMemberName]when implementing the OnPropertyChanged()method. (I have no idea what that third answer is all about…it doesn't appear to have anything to do with this question, and I'd guess it was just copy/pasted from somewhere else).

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

Here's an example of how I'd write your Stateclass:

这是我如何编写您的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));
        }
    }
}

The key here is that the parameter marked with [CallerMemberName]will automatically be filled in with the correct value from the caller, simply by not passing any value. The default value of nullis there just so the compiler will allow the caller to not pass a value.

这里的关键是标记为 的参数[CallerMemberName]将自动填充来自调用者的正确值,只需不传递任何值即可。的默认值null就是这样编译器将允许调用者不传递值。

Note that I also simplified the Toggle()method. There's no need to use an ifstatement to transform one boolvalue into another; that's what the Boolean operators are there for.

请注意,我还简化了Toggle()方法。无需使用if语句将一个bool值转换为另一个值;这就是布尔运算符的用途。

I also changed the OnPropertyChanged()method so that it's thread-safe, i.e. won't crash if some code unsubscribes the last handler from the PropertyChangedevent between the time the event field is compared to nulland the time the event is actually raised. Typically, this is a non-issue as these properties are nearly always accessed only from a single thread, but it's easy enough to protect against and is a good habit to get into.

我还更改了OnPropertyChanged()方法,以便它是线程安全的,即如果某些代码在PropertyChanged比较事件字段null的时间和实际引发事件的时间之间取消订阅事件的最后一个处理程序,则不会崩溃。通常,这不是问题,因为这些属性几乎总是只能从单个线程访问,但它很容易防止并且是一个很好的习惯。

Note that in C# 6, you have the option of just writing PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));for the method body. Not everyone is using the new compiler 100% of the time yet, so I just mention that as an optional choice for you.

请注意,在 C# 6 中,您可以选择只PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));为方法主体编写代码。并不是每个人都 100% 的时间都在使用新的编译器,所以我只是提到它是你的一个可选选择。

Naturally, you also need to set the DataContextcorrectly, as shown in one of the other answers:

当然,您还需要DataContext正确设置,如其他答案之一所示:

public MainWindow()
{
    InitializeComponent();
    _state = new State((bool)checkBox.IsChecked);
    this.DataContext = _state;
}

Though, personally, I'm not sure I'd bother with the constructor. You appear to have no other code that would set checkBox.IsChecked, so it seems to me that you're always going to get the default value anyway. Besides, you can't create your view model class in XAML if it doesn't have a parameterized constructor; in the future, you may prefer to configure your DataContextlike that. E.g.:

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

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

And in the window's constructor:

在窗口的构造函数中:

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


See also the related Q&A Automatically INotifyPropertyChanged. The question there is really about something different — they want to implement the interface without having to explicitly write anything in the property setter — but for better or worse, the answers they got are really more about your scenario, where it's just a question of simplifyingthe property setter implementation rather than making it completely automatic.


另请参阅相关的问答自动 INotifyPropertyChanged。那里的问题真的是关于一些不同的东西——他们想要实现接口而不必在属性设置器中显式地写任何东西——但无论好坏,他们得到的答案实际上更多的是关于你的场景,这只是一个简化的问题属性设置器实现而不是使其完全自动化。

I have to admit, I would've thought there would have been another question already with which to mark yours as a duplicate. And I did find lots of relatedquestions. But nothing that focuses directly on just "how do I implement and use a view model that implements INotifyPropertyChanged?", which is really what your question seems to be about.

我不得不承认,我原以为已经有另一个问题可以将您的问题标记为重复。我确实找到了很多相关的问题。但是没有什么直接关注“我如何实现和使用实现的视图模型INotifyPropertyChanged?”,这确实是您的问题。


Addendum:


附录

I did some more searching, and while none of these seem like they would be considered exact duplicates per se, they all have good information that help address the question about implementing INotifyPropertyChanged:

我做了更多的搜索,虽然这些似乎都不会被认为是完全重复的,但它们都有很好的信息,可以帮助解决有关实施的问题INotifyPropertyChanged

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

属性的使用... INotifyPropertyChanged
INotifyPropertyChanged 用于模型和视图模型
BindableBase 与 INotifyChanged
如何在 MVVM (WPF) 中编写“ViewModelBase”

回答by Taterhead

You are real close. You need to make 2 small changes and your test works:

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

  1. Assign the DataContextof your Window to the _state variable.
  2. Put the string "Checked" into the OnPropertyChangedand pass propertyNameto the PropertyChangedEventArgsin the OnPropertyChangedmethod.
  1. DataContextWindow分配给 _state 变量。
  2. 把“检查”串入OnPropertyChanged并传递propertyNamePropertyChangedEventArgsOnPropertyChanged方法。

So your MainWindow ctor becomes:

所以你的 MainWindow ctor 变成了:

    public MainWindow()
    {
        InitializeComponent();
        _state = new State((bool)checkBox.IsChecked);
        this.DataContext = _state;
    }

and the State class file looks like:

并且 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));
        }
    }
}

As a novice, I recommend you learn more about Model-View-ViewModel MVVMDesign Pattern. It is a common pattern with WPF and helps encourage separation of concerns (keeping your business logic out of your user interface logic)

作为新手,我建议您了解更多有关 Model-View-ViewModel MVVM设计模式的知识。它是 WPF 的常见模式,有助于鼓励关注点分离(使您的业务逻辑脱离用户界面逻辑)

回答by andreask

The OnPropertyChangedmethod expects the Checkedproperty's name as argument - at the moment, you're passing its value!

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

This means, change the Checkedproperty declaration to:

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

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