c# - 为什么我的 WPF CheckBox 绑定(bind)不起作用?

标签 c# wpf data-binding mvvm

我正在使用 MVVM、VS 2008 和 .NET 3.5 SP1。我有一个项目列表,每个项目都有一个 IsSelected 属性。我添加了一个 CheckBox 来管理列表中所有项目的选择/取消选择(更新每个项目的 IsSelected 属性)。当 CheckBox 的绑定(bind)控件触发 PropertyChanged 事件时,除了 IsChecked 属性未在 View 中更新外,一切正常。

<CheckBox
  Command="{Binding SelectAllCommand}"
  IsChecked="{Binding Path=AreAllSelected, Mode=OneWay}"
  Content="Select/deselect all identified duplicates"
  IsThreeState="True" />

我的虚拟机:

public class MainViewModel : BaseViewModel
{
  public MainViewModel(ListViewModel listVM)
  {
    ListVM = listVM;
    ListVM.PropertyChanged += OnListVmChanged;
  }

  public ListViewModel ListVM { get; private set; }
  public ICommand SelectAllCommand { get { return ListVM.SelectAllCommand; } }

  public bool? AreAllSelected
  {
    get
    {
      if (ListVM == null)
        return false;

      return ListVM.AreAllSelected;
    }
  }

  private void OnListVmChanged(object sender, PropertyChangedEventArgs e)
  {
    if (e.PropertyName == "AreAllSelected")
      OnPropertyChanged("AreAllSelected");
  }
}

我没有在这里展示 SelectAllCommand 的实现或单个项目选择,但它似乎不相关。当用户选择列表中的单个项目时(或单击问题 CheckBox 以选择/取消选择所有项目),我已验证 OnPropertyChanged("AreAllSelected") 代码行已执行,并在调试器中进行跟踪,可以看到PropertyChanged 事件已订阅并按预期触发。但是 AreAllSelected 属性的 get 只执行一次 - 当 View 实际呈现时。 Visual Studio 的输出窗口不报告任何数据绑定(bind)错误,因此据我所知,CheckBox 的 IsSelected 属性已正确绑定(bind)。

如果我将 CheckBox 替换为 Button:

<Button Content="{Binding SelectAllText}" Command="{Binding SelectAllCommand}"/>

并更新虚拟机:

...

public string SelectAllText
{
  get
  {
    var msg = "Select All";
    if (ListVM != null && ListVM.AreAllSelected != null && ListVM.AreAllSelected.Value)
      msg = "Deselect All";

    return msg;
  }
}

...

private void OnListVmChanged(object sender, PropertyChangedEventArgs e)
{
  if (e.PropertyName == "AreAllSelected")
    OnPropertyChanged("SelectAllText");
}

一切都按预期工作 - 按钮的文本随着所有项目被选择/取消而更新。关于 CheckBox 的 IsSelected 属性上的绑定(bind),我是否遗漏了什么?

感谢您的帮助!

最佳答案

我发现了问题。在 IsChecked 上使用 OneWay 绑定(bind)的 WPF 3.0 中似乎存在一个错误,导致绑定(bind)被删除。感谢this post寻求帮助,听起来该错误已在 WPF 4.0 中修复

要重现,请创建一个新的 WPF 项目。

添加一个 FooViewModel.cs:

using System;
using System.ComponentModel;
using System.Windows.Input;

namespace Foo
{
  public class FooViewModel : INotifyPropertyChanged
  {
    private bool? _isCheckedState = true;

    public FooViewModel()
    {
      ChangeStateCommand = new MyCmd(ChangeState);
    }

    public bool? IsCheckedState
    {
      get { return _isCheckedState; }
    }

    public ICommand ChangeStateCommand { get; private set; }

    private void ChangeState()
    {
      switch (_isCheckedState)
      {
        case null:
          _isCheckedState = true;
          break;
        default:
          _isCheckedState = null;
          break;
      }

      OnPropertyChanged("IsCheckedState");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
      var changed = PropertyChanged;
      if (changed != null)
        changed(this, new PropertyChangedEventArgs(propertyName));
    }
  }

  public class MyCmd : ICommand
  {
    private readonly Action _execute;
    public event EventHandler CanExecuteChanged;

    public MyCmd(Action execute)
    {
      _execute = execute;
    }

    public void Execute(object parameter)
    {
      _execute();
    }

    public bool CanExecute(object parameter)
    {
      return true;
    }
  }
}

修改Window1.xaml.cs:

using System.Windows;
using System.Windows.Controls.Primitives;

namespace Foo
{
  public partial class Window1
  {
    public Window1()
    {
      InitializeComponent();
    }

    private void OnClick(object sender, RoutedEventArgs e)
    {
      var bindingExpression = MyCheckBox.GetBindingExpression(ToggleButton.IsCheckedProperty);
      if (bindingExpression == null)
        MessageBox.Show("IsChecked property is not bound!");
    }
  }
}

修改Window1.xaml:

<Window
  x:Class="Foo.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:vm="clr-namespace:Foo"
  Title="Window1"
  Height="200"
  Width="200"
  >

  <Window.DataContext>
    <vm:FooViewModel />
  </Window.DataContext>

  <StackPanel>
    <CheckBox
      x:Name="MyCheckBox"
      Command="{Binding ChangeStateCommand}"
      IsChecked="{Binding Path=IsCheckedState, Mode=OneWay}"
      Content="Foo"
      IsThreeState="True"
      Click="OnClick"/>
    <Button Command="{Binding ChangeStateCommand}" Click="OnClick" Content="Change State"/>
  </StackPanel>
</Window>

点击按钮几次,然后看到 CheckBox 的状态在 true 和 null(不是 false)之间切换。但是单击 CheckBox,您会看到 Binding 已从 IsChecked 属性中删除。

解决方法:

将 IsChecked 绑定(bind)更新为 TwoWay 并将其 UpdateSourceTrigger 设置为显式:

IsChecked="{Binding Path=IsCheckedState, Mode=TwoWay, UpdateSourceTrigger=Explicit}"

并更新绑定(bind)属性,使其不再是只读的:

public bool? IsCheckedState
{
  get { return _isCheckedState; }
  set { }
}

关于c# - 为什么我的 WPF CheckBox 绑定(bind)不起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6602737/

相关文章:

c# - 在 XAML 中应用自定义画笔

wpf - 选中切换按钮时更改切换按钮的背景颜色

c# - 从 WPF 保存到 SQL Server 数据库

asp.net - 绑定(bind)到 ListView fieldName 来自资源

c# - 根据值从字典中选择随机条目

c# - 分组为元素字典

c# - 如何使用 C# 提取 MFCC

c# - 什么是 php 中的 tinytext 以及 tinytext 和 string 之间的区别是什么?

c# - 我如何从选定的 ListView 项目中获取名称?

asp.net - 带有 Eval 的中继器的 SeparatorTemplate