C# WPF 如何使用多个绑定来控制 IsEnabled 属性?

57

我有一个描述GUI部分的WPF xaml文件,我希望一个特定控件的启用/禁用取决于另外两个控件。目前代码大致如下:

<ComboBox Name="MyComboBox"
          IsEnabled="{Binding ElementName=SomeCheckBox, Path=IsChecked}"/>

但我想它也应该依赖于另一个复选框,就像这样:

<ComboBox Name="MyComboBox"
          IsEnabled="{Binding ElementName=SomeCheckBox&AnotherCheckbox, Path=IsChecked}"/>

最佳方法是什么?我感觉自己可能在错过一些显而易见的东西或者走了错误的方向?

5个回答

99
您可以使用实现了IMultiValueConverter的转换器来使用MultiBinding

以下内容只需复制&粘贴即可:

需要静态资源:

<converterNamespace:BooleanAndConverter x:Key="booleanAndConverter" />

组合框:

<ComboBox Name="MyComboBox">
  <ComboBox.IsEnabled>
    <MultiBinding Converter="{StaticResource booleanAndConverter}">
      <Binding ElementName="SomeCheckBox" Path="IsChecked" />
      <Binding ElementName="AnotherCheckbox" Path="IsChecked"  />
    </MultiBinding>
  </ComboBox.IsEnabled>
</ComboBox>

转换器的代码:

namespace ConverterNamespace
{
    public class BooleanAndConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            foreach (object value in values)
            {
                if ((value is bool) && (bool)value == false)
                {
                    return false;
                }
            }
            return true;
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException("BooleanAndConverter is a OneWay converter.");
        }
    }
}

我有一个问题,如果values为空怎么办? 在开头加上这样的检查不是更好吗: if (values == null) { return false; } - Devid
1
根据MSDN的说明,ConvertBack方法应该返回null而不是抛出NotSupportedException - Knowledge Cube
4
在我看来,使用 LinQ 的 Convert 方法更容易理解: return values.All(value => value is bool && (bool) value); 或者 return values.All(value => value is bool b && b); - Daniel Eisenreich
我知道这个方法有点老了,但是我使用了这个 LinQ 语法:return values.OfType<bool>().All(x => x); - AidanH
如果我需要不同的转换器呢? - user5260143

20

您也可以尝试相同的较短版本:

public class BooleanAndConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return values.OfType<IConvertible>().All(System.Convert.ToBoolean);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

public class BooleanOrConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return values.OfType<IConvertible>().Any(System.Convert.ToBoolean);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

当然,您可能也需要可见性转换器:

public class BooleanOrToVisibilityConverter : IMultiValueConverter
{
    public Visibility HiddenVisibility { get; set; }

    public bool IsInverted { get; set; }

    public BooleanOrToVisibilityConverter()
    {
        HiddenVisibility = Visibility.Collapsed;
        IsInverted = false;
    }

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        bool flag = values.OfType<IConvertible>().Any(System.Convert.ToBoolean);
        if (IsInverted) flag = !flag;
        return flag ? Visibility.Visible : HiddenVisibility;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class BooleanAndToVisibilityConverter : IMultiValueConverter
{
    public Visibility HiddenVisibility { get; set; }

    public bool IsInverted { get; set; }

    public BooleanAndToVisibilityConverter()
    {
        HiddenVisibility = Visibility.Collapsed;
        IsInverted = false;
    }

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        bool flag = values.OfType<IConvertible>().All(System.Convert.ToBoolean);
        if (IsInverted) flag = !flag;
        return flag ? Visibility.Visible : HiddenVisibility;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

1
这些不能链式调用吗? - Joshua Frank

14

2
作为对qqbenq答案的补充:
添加了处理集合中Count的函数,例如如果您想检查ListView中是否选择了某个项目。
转换器:
public class IsEnabledConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        foreach (var value in values)
        {
            switch (value)
            {
                case bool b when !b:
                case int i when i == 0:
                    return false;
            }
        }

        return true;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return null;
    }
}

命名空间 <theNamespace:IsEnabledConverter x:Key="IsEnabledConverter"/>

按钮

<Button x:Name="MyButton">
    <Button.IsEnabled>
        <MultiBinding Converter="{StaticResource IsEnabledConverter}">
            <Binding ElementName="MyListView" Path="SelectedItems.Count"/>
            <Binding ElementName="MyCheckBox" Path="IsChecked"/>
        </MultiBinding>
    </Button.IsEnabled>
</Button>

0

当您不想使用MultiBinding时

public class AndEnabledTextBox : TextBox
{
    public static readonly DependencyProperty AndEnabled1SubProperty =
        DependencyProperty.Register(nameof(AndEnabled1), typeof(bool), typeof(AndEnabledTextBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnAndEnabledPropertyChanged)));
    public static readonly DependencyProperty AndEnabled2SubProperty =
        DependencyProperty.Register(nameof(AndEnabled2), typeof(bool), typeof(AndEnabledTextBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnAndEnabledPropertyChanged)));
    public bool AndEnabled1 { get { return (bool)GetValue(AndEnabled1SubProperty); } set { SetValue(AndEnabled1SubProperty, value); } }
    public bool AndEnabled2 { get { return (bool)GetValue(AndEnabled2SubProperty); } set { SetValue(AndEnabled2SubProperty, value); } }
    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);
        IsEnabled = AndEnabled1 && AndEnabled2;
    }
    protected static void OnAndEnabledPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        AndEnabledTextBox textBox = (AndEnabledTextBox)obj;
        textBox.IsEnabled = textBox.AndEnabled1 && textBox.AndEnabled2;
    }
}

使用继承控件时,XMAL更加简化。

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp1">
    <StackPanel>
        <ToggleButton x:Name="switch1" Content="{Binding ElementName=switch1, Path=IsChecked}"/>
        <ToggleButton x:Name="switch2" Content="{Binding ElementName=switch2, Path=IsChecked}"/>
        <local:AndEnabledTextBox Text="TEXT"
                                 AndEnabled1="{Binding ElementName=switch1, Path=IsChecked, Mode=OneWay}"
                                 AndEnabled2="{Binding ElementName=switch2, Path=IsChecked, Mode=OneWay}"/>
    </StackPanel>
</Window>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接