【问题标题】:How to bind inverse boolean properties in WPF?如何在 WPF 中绑定反布尔属性?
【发布时间】:2021-10-02 18:07:18
【问题描述】:

我拥有的是一个具有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>

【问题讨论】:

标签: wpf .net-3.5 styles


【解决方案1】:

对于标准绑定,您需要使用看起来有点风的转换器。所以,我建议你看看我的项目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 的属性,它将正常工作

【讨论】:

  • 虽然 QuickConverter 更强大,但我发现 CalcBinding 模式可读-可用。
  • 这是一个很棒的工具。我希望它存在于 5 年前!
  • 出色的工具,但在样式上落伍了。 &lt;Setter.Value&gt;&lt;cb:Binding Path="!IsReadOnly" /&gt;&lt;/Setter.Value&gt; 得到一个 'Binding' 对 Setter.Value 无效' 编译时错误
【解决方案2】:

? .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")。

【讨论】:

  • 简单、整洁且解释充分,感谢 OmegaMan!
【解决方案3】:

我使用类似@Ofaim 的方法

private bool jobSaved = true;
private bool JobSaved    
{ 
    get => jobSaved; 
    set
    {
        if (value == jobSaved) return;
        jobSaved = value;

        OnPropertyChanged();
        OnPropertyChanged("EnableSaveButton");
    }
}

public bool EnableSaveButton => !jobSaved;

【讨论】:

    【解决方案4】:

    您是否考虑过IsNotReadOnly 属性?如果要绑定的对象是 MVVM 域中的 ViewModel,那么附加属性就非常有意义。如果是直接实体模型,您可以考虑组合并将实体的专用 ViewModel 呈现给表单。

    【讨论】:

    • 我刚刚使用这种方法解决了同样的问题,我同意它不仅更优雅,而且比使用转换器更易于维护。
    • 我不同意这种方法比价值转换器更好。如果您需要多个 NotProperty 实例,它还会生成更多代码。
    • MVVM 不是不写代码,而是以声明方式解决问题。为此,转换器正确的解决方案。
    • 此解决方案的问题在于,如果您有 100 个对象,则必须为所有 100 个对象添加一个 IsNotReadOnly 属性。该属性必须是 DependencyProperty。这为所有 100 个对象或 1000 行代码添加了大约 10 行代码。转换器是 20 行代码。 1000 行或 20 行。你会选择哪一个?
    • 对此有句俗语:做一次,做两次,然后自动化。有疑问,我会在项目第一次需要它时使用这个答案,然后如果事情发展,我会使用接受的答案。但是预先制作转换器 sn-p 可能会降低使用难度。
    【解决方案5】:

    我做了一些非常相似的事情。我在幕后创建了我的属性,该属性仅在完成数据搜索后才启用组合框的选择。当我的窗口首次出现时,它会启动一个异步加载命令,但我不希望用户在它仍在加载数据时单击组合框(将是空的,然后将被填充)。因此,默认情况下该属性为 false,因此我在 getter 中返回逆。然后,当我搜索时,我将属性设置为 true,并在完成后返回 false。

    private bool _isSearching;
    public bool IsSearching
    {
        get { return !_isSearching; }
        set
        {
            if(_isSearching != value)
            {
                _isSearching = value;
                OnPropertyChanged("IsSearching");
            }
        }
    }
    
    public CityViewModel()
    {
        LoadedCommand = new DelegateCommandAsync(LoadCity, LoadCanExecute);
    }
    
    private async Task LoadCity(object pArg)
    {
        IsSearching = true;
    
        //**Do your searching task here**
    
        IsSearching = false;
    }
    
    private bool LoadCanExecute(object pArg)
    {
        return IsSearching;
    }
    

    然后对于组合框,我可以将它直接绑定到 IsSearching:

    <ComboBox ItemsSource="{Binding Cities}" IsEnabled="{Binding IsSearching}" DisplayMemberPath="City" />
    

    【讨论】:

      【解决方案6】:

      根据@Paul 的回答,我在 ViewModel 中写了以下内容:

      public bool ShowAtView { get; set; }
      public bool InvShowAtView { get { return !ShowAtView; } }
      

      我希望在这里有一个 sn-p 可以帮助某人,可能像我这样的新手。
      如果有错误,请告诉我!

      顺便说一句,我也同意@heltonbiker 的评论——这绝对是正确的方法只有在你不必使用它超过 3 次...

      【讨论】:

      • 不是完整的属性并且缺少“OnPropertyChanged”,这将不起作用。第一个或第二个答案是我正在使用的,视情况而定。除非您使用像 Prism 这样的框架,frameowkr 知道何时更新“引用”属性。然后在使用类似于您建议的东西(但具有完整属性)和回答 1 之间折腾
      【解决方案7】:

      我遇到了一个倒置问题,但是一个巧妙的解决方案。

      动机是 XAML 设计器会显示一个空控件,例如当没有数据上下文/没有MyValues(itemssource)时。

      初始代码:隐藏控制MyValues为空时。 改进的代码:当MyValues 不为空或为空时显示控件。

      当然问题是如何表达“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}">
      

      Ergo 设置绑定的默认值。当然,这不适用于所有类型的逆问题,但可以帮助我编写干净的代码。

      【讨论】:

        【解决方案8】:

        在您的视图模型中再添加一个属性,它将返回反向值。 并将其绑定到按钮。 喜欢;

        在视图模型中:

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

        在xml中:

        IsEnabled="{Binding IsNotReadOnly"}
        

        【讨论】:

        • 很好的答案。要添加的一件事,使用它,您最好在属性 IsReadOnly 的设置器中为 IsNotReadOnly 触发 PropertyChanged 事件。这样,您将确保 UI 得到正确更新。
        • 这应该是公认的答案,因为它是最简单的。
        【解决方案9】:

        这也适用于可为空的布尔值。

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

        【讨论】:

          【解决方案10】:

          我希望我的 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}"
          

          【讨论】:

          • 缺点是您必须更改所有将其与其他bool 类型表达式/变量进行比较/分配的代码,甚至不引用反值。相反,我会在 Boolean Struct 中添加一个“非”扩展方法。
          • 噢!没关系。对于Binding,忘记必须是PropertyMethod。我的“不利”声明仍然适用。顺便说一句,“布尔”“非”扩展方法对于避免“!”仍然有用。当它(通常是这种情况)嵌入到看起来像它的字符旁边时很容易错过的运算符(即一个/多个“(”和“l”和“I”)。
          【解决方案11】:

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

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

          【讨论】:

          • 似乎与转换器方法几乎相同。 WinForms 绑定中的FormatParse 事件大致相当于WPF 转换器。
          【解决方案12】:

          我会推荐使用https://quickconverter.codeplex.com/

          反转一个布尔值就这么简单: &lt;Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" /&gt;

          这加快了编写转换器通常所需的时间。

          【讨论】:

          • 当给某人 -1 时,最好解释一下原因。
          【解决方案13】:

          您可以使用 ValueConverter 为您反转 bool 属性。

          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
              }
          

          【讨论】:

          • 这里有几件事我必须考虑,这可能会让我选择@Paul 的答案而不是这个。编码时我一个人(现在),所以我需要一个“我”会记住的解决方案,我会一遍又一遍地使用它。我也觉得越罗嗦越好,而且创建一个逆属性非常明确,让我很容易记住,以及未来的开发者(我希望,我希望),能够快速看到我的这样做,也让他们更容易把我扔到众所周知的公共汽车下。
          • 根据您自己的论点,恕我直言,从长远来看,转换器解决方案会更好:您只需编写一次转换器,之后您就可以一遍又一遍地重复使用它。如果您选择新属性,则必须在每个需要它的类中重写它...
          • 我正在使用相同的方法...但它使熊猫 saaad... =(
          • ! 相比,那是一些冗长的代码......人们会付出疯狂的努力来将他们认为是“代码”的东西与那些糟糕的设计师区分开来。当我既是编码员又是设计师时,额外的痛苦。
          • 很多人,包括我自己,都会认为这是过度设计的典型例子。我建议使用下面的 Paul Alexander 帖子中的倒置属性。
          猜你喜欢
          • 1970-01-01
          • 2014-10-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多