Как связать обратные логические свойства в WPF? - PullRequest
337 голосов
/ 24 июня 2009

У меня есть объект, обладающий свойством IsReadOnly. Если это свойство имеет значение true, я хотел бы установить для свойства IsEnabled для кнопки (например) значение false.

Мне бы хотелось верить, что я могу сделать это так же легко, как IsEnabled="{Binding Path=!IsReadOnly}", но это не работает с WPF.

Должен ли я пройти через все настройки стиля? Просто кажется слишком многословным для чего-то столь же простого, как установка одного bool в обратное значение другого bool.

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

Ответы [ 11 ]

437 голосов
/ 24 июня 2009

Вы можете использовать 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
    }
94 голосов
/ 24 июня 2009

Рассматривали ли вы IsNotReadOnly собственность? Если привязываемый объект является ViewModel в домене MVVM, тогда дополнительное свойство имеет смысл. Если это прямая модель сущностей, вы можете рассмотреть состав и представление в форму специализированной модели представления вашей сущности.

61 голосов
/ 06 декабря 2014

При стандартной привязке вам нужно использовать конвертеры, которые выглядят немного ветрено. Итак, я рекомендую вам взглянуть на мой проект 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, и он будет работать правильно

Гудлак!

18 голосов
/ 07 августа 2014

Я бы рекомендовал использовать https://quickconverter.codeplex.com/

Инвертировать логическое значение так же просто, как: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

Это ускоряет время, обычно необходимое для написания конвертеров.

13 голосов
/ 04 августа 2015

Я хотел, чтобы мой 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;
    }

}

Единственные изменения, которые необходимо внести в ваш проект, - это заставить свойство, которое вы хотите инвертировать, вернуть это вместо bool

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

А в постфиксе XAML связывание с Value или Invert

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"
8 голосов
/ 23 марта 2016

Этот также работает для недействительных bools.

 [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
}
2 голосов
/ 04 марта 2015

Не знаю, относится ли это к 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;
}
1 голос
/ 05 февраля 2018

Я сделал что-то очень похожее. Я создал свое свойство за кулисами, что позволило выбрать комбинированный список ТОЛЬКО в том случае, если он завершил поиск данных. Когда мое окно появляется впервые, оно запускает асинхронную загруженную команду, но я не хочу, чтобы пользователь нажимал на поле со списком, пока он все еще загружает данные (будет пустым, затем будет заполнен). Так что по умолчанию свойство имеет значение false, поэтому я возвращаю обратное значение в геттере. Затем, когда я ищу, я устанавливаю для свойства значение 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" />
1 голос
/ 09 января 2018

Следуя ответу Пола, я написал в ViewModel следующее:

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

Я надеюсь, что наличие фрагмента здесь поможет кому-то, возможно, новичку, как и я.
И если есть ошибка, пожалуйста, дайте мне знать!

Кстати, я также согласен с комментарием @heltonbiker - это, безусловно, правильный подход , только если вам не нужно использовать его более 3 раз ...

1 голос
/ 26 октября 2017

У меня была проблема с инверсией, но решение было удачным.

Мотивация заключалась в том, что разработчик XAML показывал пустой элемент управления, например когда не было текста данных / нет MyValues (источник).

Исходный код: скрыть контроль, когда 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 устанавливает значение по умолчанию для привязки. Конечно, это не работает для всех видов обратных задач, но помогло мне с чистым кодом.

...