mvvm中的3状态复选框绑定,无法设置空状态

时间:2015-10-15 00:18:12

标签: xaml checkbox mvvm data-binding uwp

我正在使用MVVM创建一个UWP应用程序。在我的xaml视图中,我有一个3状态复选框。我的复选框位于ListView中,此列表视图还有一个子列表视图,带有更多复选框 - 所以基本上是嵌套的复选框列表。如果选中内部列表视图中的任何复选框,我试图让外部列表视图中的复选框处于空状态。如果选择了所有内部复选框,我希望外部复选框处于true状态,​​如果没有选中,我希望它处于false状态。我正在使用委托命令来实现这些事件处理程序。

我成功地能够获得真假案例,但是我无法获得在UI上显示状态null的复选框,因为它在我的ViewModel中的值成功更改为null。

<ListView ItemsSource="{Binding ObxList, Mode=TwoWay}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{x:Bind IsSelected, Mode=TwoWay}" IsThreeState="True" Command="{Binding DataContext.ClickCommand, ElementName=Y}">
                <TextBlock Text="{x:Bind Value}"  />
            </CheckBox>
            <ListView ItemsSource="{Binding Children}" Visibility="{Binding visibility, Mode=TwoWay}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}"
                                  IsThreeState="False" Command="{Binding DataContext.ChildCommand, ElementName=Y}">
                            <TextBlock Text="{Binding Value}"/>
                        </CheckBox>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView

以下是在我的委托命令中调用的ViewModel方法的代码:

public void ChildCheckboxHandler()
    {
        // gets all of the lowest level children
        foreach (Object item in AllObjectsList.Where(t =>
        {
            return t.Children.Count() == 0;
        }))
        {
            if (item.IsSelected == true)
            {
                var parent = item.GetParent(AllObjectsList);
                if (parent.IsSelected == null || parent.IsSelected == false)
                {
                    // checks if all children are selected
                    // and changes the parent to true if so
                    // null if only some are selected
                    bool allChildrenSelected = true;
                    foreach (var child in parent.GetChildren(AllObjectsList))
                    {
                        if (child.IsSelected == false)
                        {
                            allChildrenSelected = false;
                            break;
                        }
                    }
                    if (allChildrenSelected)
                    {
                        parent.IsSelected = true;
                    }
                    else
                    {
                        parent.IsSelected = new Nullable<Boolean>();

                    }
                }
            }
            else if (item.IsSelected == false)
            {
                var parent = item.GetParent(AllObjectsList);
                if (parent.IsSelected == null)
                {
                    // checks if all the children are unchecked
                    // if they are, then sets the parent to false
                    // if they are not, then sets the parent to null
                    bool noChildrenSelected = true;
                    foreach (var child in parent.GetChildren(AllObjectsList))
                    {
                        if (child.IsSelected == true)
                        {
                            noChildrenSelected = false;
                            break;
                        }
                    }
                    if (noChildrenSelected)
                    {
                        parent.IsSelected = false;
                    }
                }
                else if (parent.IsSelected == true)
                {
                    parent.IsSelected = null;
                }
            }
        }
    }

2 个答案:

答案 0 :(得分:3)

这是因为WinRT不支持绑定到可空类型。

@Jerry Nixon - MSFT有blog以3种方式解决此问题,并在他的博客中提供了示例。

答案 1 :(得分:0)

最终的问题是 DependencyPropertyCheckBox.IsChecked 实现。 WinRT 不隐式支持可空基本类型绑定,在 WinRT 中,底层类型是 IReference1<Boolean>,这是一个内部定义,因此我们不能使用简单的类型转换器来解决此问题。

<块引用>

previous answer 在技术上是正确的,@Jerry Nixon 在 linked article 中提供了一些有用的背景信息,但它仅表明存在 解决方法,而不是具体_how实施它们。

这篇博文中的建议在近 10 年的 UWP 中仍然适用:Windows 8 Dev Tip: Nullable Dependency Properties and Binding。最简单的解决方法是创建一个类型为 Object 的新依赖属性。

<块引用>

这是许多 3rd 方控件库中的常见解决方案,通常会找到一个 object 类型化的 Value 属性和一个单独的 类型化 属性。

可以通过附加属性实现,但我更喜欢扩展 CheckBox 类:

/// <summary>
/// Check Box Control with support for binding with an indeterminate state
/// </summary>
/// <remarks>https://www.danrigby.com/2012/07/24/windows-8-dev-tip-nullable-dependency-properties-and-binding/</remarks>
public class BindableCheckBox : CheckBox
{
    public BindableCheckBox()
    {
        this.IsThreeState = true;
        // start in the nullable state!
        this.IsChecked = null;

        // hook up UI events to manually set the value appropriately
        this.Checked += BindableCheckBox_Checked;
        this.Unchecked += BindableCheckBox_Unchecked;
        this.Indeterminate += BindableCheckBox_Indeterminate;

        // Hookup property changed event, in case some moron sets the IsChecked some other way
        this.RegisterPropertyChangedCallback(CheckBox.IsCheckedProperty, (s, e) =>
        {
            if (s is BindableCheckBox cb)
            {
                if (!cb._ValueIsChanging)
                {
                    var newValue = (bool?)s.GetValue(e);
                    if (cb.State != newValue)
                        cb.State = newValue;
                }
            }
        });
    }

    private bool _ValueIsChanging = false;
    private void BindableCheckBox_Indeterminate(object sender, Windows.UI.Xaml.RoutedEventArgs e)
    {
        if(!_ValueIsChanging)
            Value = null;
    }

    private void BindableCheckBox_Unchecked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
    {
        if (!_ValueIsChanging)
            Value = false;
    }

    private void BindableCheckBox_Checked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
    {
        if (!_ValueIsChanging)
            Value = true;
    }

    /// <summary>
    /// DP For the Three States of this checkbox, Null implies that it is in the Indeterminate state
    /// </summary>
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register(
           nameof(Value),
           typeof(object),
           typeof(BindableCheckBox), new PropertyMetadata(null, (s, e) =>
           {
               if (s is BindableCheckBox cb)
               {
                   try
                   {
                       cb._ValueIsChanging = true;
                       var newValue = (bool?)e.NewValue;
                       if (cb.IsChecked != newValue)
                           cb.IsChecked = newValue;
                   }
                   finally
                   {
                       cb._ValueIsChanging = false;
                   }
               }
           }));

    /// <summary>
    /// Value property, a bindable implementation of IsChecked
    /// </summary>
    /// <remarks>You should bind this property INSTEAD of IsChecked, if you want to bind to the indeterminate state</remarks>
    public bool? Value
    {
        get { return (bool?)GetValue(ValueProperty); }
        private set { SetValue(ValueProperty, value); }
    }
}

现在您可以使用 MVVM 风格的 TwoWay 绑定绑定到 Value 属性