【问题标题】:How do I correctly implement INotifyPropertyChanged for my bool property and bind to CheckBox.IsChecked?如何为我的 bool 属性正确实现 INotifyPropertyChanged 并绑定到 CheckBox.IsChecked?
【发布时间】:2016-08-26 12:54:19
【问题描述】:

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

所以我在视图中创建了一个复选框,并将其绑定到上述状态类中的布尔属性,并附带一个绕过复选框并直接切换布尔属性的按钮(恰当地标记为“忍者!”)。重点是测试复选框的数据绑定在属性更改时是否会做出反应。但是,我无法弄清楚在属性更改时应该如何调用 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)时,那自然是指布尔值,而不是字符串。我没有得到什么?还有什么我做错了,因为当我通过按钮更改复选框时,复选框没有注册属性更改?

【问题讨论】:

    标签: c# wpf data-binding


    【解决方案1】:

    建议您传递字符串文字"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() 方法,使其成为线程安全的,也就是说,如果在事件字段与null 和实际引发事件的时间。通常,这不是问题,因为这些属性几乎总是只能从单个线程访问,但它很容易防范并且是一个养成的好习惯。

    请注意,在 C# 6 中,您可以选择只为方法主体编写 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));。并不是每个人都 100% 地使用新的编译器,所以我只是将其作为您的可选选择。

    当然,你还需要正确设置DataContext,如one of the other answers所示:

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


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

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


    附录

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

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

    【讨论】:

    • OnPropertyChanged 方法现在可以在 C#6 中使用空条件运算符缩短为一行代码:PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    【解决方案2】:

    你真的很亲密。您需要进行 2 处小改动,然后您的测试才有效:

    1. 将 Window 的 DataContext 分配给 _state 变量。
    2. 将字符串“Checked”放入OnPropertyChanged 并在OnPropertyChanged 方法中将propertyName 传递给PropertyChangedEventArgs

    所以你的 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 的一种常见模式,有助于鼓励关注点分离(将您的业务逻辑排除在用户界面逻辑之外)

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        猜你喜欢
        • 2011-06-08
        • 2011-05-30
        • 1970-01-01
        • 2011-06-21
        • 2013-04-15
        • 2011-06-04
        • 2012-08-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多