如何在WPF中绑定反向布尔属性?

441
我手头有一个对象,其中包含一个IsReadOnly属性。如果这个属性为true,我想将一个Button的IsEnabled属性(例如)设置为false。我本来以为可以像这样轻松地实现:IsEnabled="{Binding Path=!IsReadOnly}",但在WPF中行不通。难道我必须遍历所有的样式设置吗?这对于只需将一个布尔值设置为另一个布尔值的反向值来说,似乎过于冗长。
<Button.Style>
    <Style TargetType="{x:Type Button}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=IsReadOnly}" Value="True">
                <Setter Property="IsEnabled" Value="False" />
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=IsReadOnly}" Value="False">
                <Setter Property="IsEnabled" Value="True" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Button.Style>

相关:https://dev59.com/y3RB5IYBdhLWcg3wuZbo - StayOnTarget
1
Eh MS 做了好事情,但是没有完整完成。 - user1005462
16个回答

571
你可以使用一个值转换器来反转布尔属性。
XAML:
IsEnabled="{Binding Path=IsReadOnly, Converter={StaticResource InverseBooleanConverter}}"

转换器:

[ValueConversion(typeof(bool), typeof(bool))]
    public class InverseBooleanConverter: IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            if (targetType != typeof(bool))
                throw new InvalidOperationException("The target must be a boolean");

            return !(bool)value;
        }

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

        #endregion
    }

9
这里有几个问题我必须考虑,这可能会让我选择 @Paul的答案而不是这个。我独自编码(暂时),所以我需要选择“我”会记得并且会一再使用的解决方案。我也觉得说得越简洁越好,创建一个反向属性非常明确,这让我易于记忆,也希望未来的开发人员能够迅速了解我的意图,并让他们更容易地把我推到类似的情境下。 - Russ
18
根据你所提出的论点,在我看来,转换器解决方案从长远角度来看更好:你只需要编写一次转换器,之后就可以反复重复使用。如果选择新的属性方式,你将不得不在每个需要它的类中重新编写它... - Thomas Levesque
52
我正在使用相同的方法...但这使得熊猫很难过... =( - Massimiliano
37
! 相比,这段代码有些冗长…… 人们会花费疯狂的精力将他们认为是“代码”的部分与那些可怜的设计师分开。当我既是编码者又是设计师时,这就显得特别麻烦了。 - Roman Starkov
11
许多人包括我自己认为这是过度设计的典型例子。我建议像保罗·亚历山大在下面的帖子中所示那样使用反向属性。 - Christian Westman
显示剩余20条评论

110

你是否考虑过添加一个 IsNotReadOnly 属性?如果绑定的对象是MVVM领域中的ViewModel,则增加该属性是非常合理的。如果它是直接的Entity模型,你可以考虑使用组合并将实体专门呈现为一个ViewModel来展示在表单中。


5
我刚用了这种方法解决了同样的问题,我认为这不仅更加优雅,而且比使用转换器更易于维护。 - alimbada
28
我不认为这种方法比值转换器更好。如果需要多个 NotProperty 实例,它还会产生更多的代码。 - Thiru
32
MVVM不是关于不写代码,而是通过声明式地解决问题。为此,转换器正确的解决方案。 - Jeff
16
这种解决方案的问题在于,如果你有100个对象,你需要为这100个对象都添加一个IsNotReadOnly属性。该属性必须是一个DependencyProperty。这将给这100个对象中的每一个都增加大约10行代码,总计1000行代码。而转换器只需20行代码。1000行或20行,你会选择哪一个? - Rhyous
13
有一个常见的说法:做一次,做两次,然后自动化。如果不确定,在项目中第一次需要时我会使用这个答案,然后如果事情变得更复杂,我会使用被接受的答案。但是预先制作转换器代码片段可能会使使用过程更容易。 - heltonbiker
显示剩余5条评论

92

使用标准绑定时,您需要使用看起来有点复杂的转换器。因此,我建议您查看我的项目CalcBinding,该项目专门开发用于解决此问题和其他一些问题。使用高级绑定,您可以直接在xaml中编写具有多个源属性的表达式。例如,您可以编写以下内容:

<Button IsEnabled="{c:Binding Path=!IsReadOnly}" />
或者
<Button Content="{c:Binding ElementName=grid, Path=ActualWidth+Height}"/>
或者
<Label Content="{c:Binding A+B+C }" />
或者
<Button Visibility="{c:Binding IsChecked, FalseToVisibility=Hidden}" />

其中A、B、C、IsChecked是viewModel的属性,它将正常工作。


6
尽管 QuickConverter 更加强大,但我发现 CalcBinding 模式更易读且更易用。 - xmedeko
5
这是个很棒的工具,我希望它在5年前就已经存在了! - jugg1es
1
这是一个很棒的工具,但在样式方面存在问题。<Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value> 会出现“'Binding' is not valid for Setter.Value”编译时错误。 - mcalex

22

32
给别人一个负一评分时,最好解释一下原因。 - Noxxys

18

我希望我的XAML保持尽可能优雅,因此我创建了一个类来包装位于其中一个共享库中的bool值,隐式运算符允许该类在后台代码中无缝地作为bool使用。

public class InvertableBool
{
    private bool value = false;

    public bool Value { get { return value; } }
    public bool Invert { get { return !value; } }

    public InvertableBool(bool b)
    {
        value = b;
    }

    public static implicit operator InvertableBool(bool b)
    {
        return new InvertableBool(b);
    }

    public static implicit operator bool(InvertableBool b)
    {
        return b.value;
    }

}

你的项目只需要进行以下修改:将你想要取反的属性返回“this”而不是bool。

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

并且在XAML中,使用Value或Invert后缀来绑定。

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"

2
缺点是您必须更改所有将其与其他 bool 类型表达式 / 变量进行比较或分配的代码,即使不引用其反值。相反,我将添加一个“Not”扩展方法到 Boolean 结构体中。 - Tom
1
糟糕!没关系。忘记了Binding的属性必须是Property而不是Method。我的“Downside”声明仍然适用。顺便说一下,'Boolean`“Not”扩展方法仍然有用,可以避免使用“!”运算符,因为当它(通常情况下)嵌入到看起来像它的字符旁边时,很容易被忽略(即一个或多个“(”和“l”和“I”)。 - Tom

12

在你的视图模型中添加一个属性,它将返回反转的值,并将其绑定到按钮。

例如:

在视图模型中:

public bool IsNotReadOnly{get{return !IsReadOnly;}}

在 XAML 中:

IsEnabled="{Binding IsNotReadOnly"}

2
很好的回答。有一件事要补充,使用此方法,最好在属性IsReadOnly的setter中为IsNotReadOnly引发PropertyChanged事件。这样可以确保UI得到正确更新。 - Muhannad
这应该是被接受的答案,因为它是最简单的。 - gabnaim

11

这个也适用于可空布尔值。

 [ValueConversion(typeof(bool?), typeof(bool))]
public class InverseBooleanConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (targetType != typeof(bool?))
        {
            throw new InvalidOperationException("The target must be a nullable boolean");
        }
        bool? b = (bool?)value;
        return b.HasValue && !b.Value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return !(value as bool?);
    }

    #endregion
}

11

.Net Core解决方案

处理空情况并且不会抛出异常,但如果没有值则返回true;否则获取输入的布尔值并反转它。

public class BooleanToReverseConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
     => !(bool?) value ?? true;

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
     => !(value as bool?);
}

Xaml

IsEnabled="{Binding IsSuccess Converter={StaticResource BooleanToReverseConverter}}"

App.Xaml 我喜欢将所有的转换器静态资源放在 app.xaml 文件中,这样我就不必在项目的窗口/页面/控件中重新声明它们。

<Application.Resources>
    <converters:BooleanToReverseConverter x:Key="BooleanToReverseConverter"/>
    <local:FauxVM x:Key="VM" />
</Application.Resources>

明确一下,converters:是指实际类实现的命名空间(xmlns:converters="clr-namespace:ProvingGround.Converters")。


1
简单、整洁且完全解释清楚,感谢OmegaMan! - Geertie

3
我有一个倒置问题,但是有个巧妙的解决方案。
动力是当没有数据上下文或没有MyValues(项源)时,XAML设计师将显示一个空控件。
初始代码: 当MyValues为空时隐藏控件。 改进后的代码:当MyValues不为空或不为null时显示控件。
当然,问题在于如何表达“1个或多个项”,这是0项的相反。
<ListBox ItemsSource={Binding MyValues}">
  <ListBox.Style x:Uid="F404D7B2-B7D3-11E7-A5A7-97680265A416">
    <Style TargetType="{x:Type ListBox}">
      <Style.Triggers>
        <DataTrigger Binding="{Binding MyValues.Count}">
          <Setter Property="Visibility" Value="Collapsed"/>
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </ListBox.Style>
</ListBox>

我通过添加以下内容解决了这个问题:
<DataTrigger Binding="{Binding MyValues.Count, FallbackValue=0, TargetNullValue=0}">

因此,设置绑定的默认值。当然,这并不适用于所有类型的反问题,但对于编写干净代码非常有帮助。


2

我不确定这是否与XAML相关,但在我的简单Windows应用程序中,我手动创建了绑定并添加了一个格式事件处理程序。

public FormMain() {
  InitializeComponent();

  Binding argBinding = new Binding("Enabled", uxCheckBoxArgsNull, "Checked", false, DataSourceUpdateMode.OnPropertyChanged);
  argBinding.Format += new ConvertEventHandler(Binding_Format_BooleanInverse);
  uxTextBoxArgs.DataBindings.Add(argBinding);
}

void Binding_Format_BooleanInverse(object sender, ConvertEventArgs e) {
  bool boolValue = (bool)e.Value;
  e.Value = !boolValue;
}

1
似乎与转换器方法基本相同。在WinForms绑定中,“格式”和“解析”事件大致相当于WPF转换器。 - Alejandro

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