Как связать обратные логические свойства в WPF?


378

У меня есть объект, который имеет 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>


э-э-э, делай доброе дело, но не
завершай

Ответы:


488

Вы можете использовать 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
    }

8
Здесь я должен рассмотреть несколько вещей, которые, вероятно, заставят меня выбрать ответ Пола на этот вопрос. Я сам при кодировании (пока), поэтому мне нужно найти решение, которое я запомню, которое я буду использовать снова и снова. Я также чувствую, что чем меньше слов, тем лучше, и создание обратного свойства очень явно, что позволяет мне легко помнить, как и будущие разработчики (я надеюсь, я надеюсь), чтобы иметь возможность быстро увидеть то, что я делал, а также облегчал им бросать меня под пресловутый автобус.
Расс

17
По вашим собственным аргументам, IMHO, решение для конвертера лучше в долгосрочной перспективе: вам нужно написать конвертер только один раз, и после этого вы можете использовать его снова и снова. Если вы выберете новое свойство, вам придется переписывать его в каждом классе, в котором оно нуждается ...
Томас Левеск

51
Я использую тот же подход ... но он делает Saaad панда ... = (
Макс Галкин

27
По сравнению с !этим это какой-то скучный код ... Люди прилагают огромные усилия, чтобы отделить то, что они считают "кодом", от этих бедных дизайнеров. Очень больно, когда я и кодер, и дизайнер.
Роман Старков

10
многие люди, в том числе и я, сочли бы это ярким примером чрезмерной инженерии. Я предлагаю использовать перевернутое свойство как в посте Пола Александра ниже.
Кристиан Вестман

99

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


5
Я просто решил ту же проблему, используя этот подход, и я согласен, что он не только более элегантен, но и гораздо более удобен в обслуживании, чем использование конвертера.
алимбада

28
Я бы не согласился, что этот подход лучше, чем преобразователь стоимости. Он также создает больше кода, если вам нужно несколько экземпляров NotProperty.
Тиру

25
MVVM не о том, чтобы не писать код, а о декларативном решении проблем. Для этого конвертер является правильным решением.
Джефф

14
Проблема с этим решением состоит в том, что если у вас есть 100 объектов, вам придется добавить свойство IsNotReadOnly ко всем 100 объектам. Это свойство должно быть DependencyProperty. Это добавляет около 10 строк кода ко всем 100 объектам или 1000 строк кода. Конвертер составляет 20 строк кода. 1000 строк или 20 строк. Что бы вы выбрали?
Rhyous

8
Для этого есть общее высказывание: сделай это один раз, сделай это дважды, а затем автоматизируй. Сомневаюсь, я бы использовал этот ответ в первый раз, когда он нужен в проекте, а затем, если что-то вырастет, я бы использовал принятый ответ. Но предварительно подготовленный фрагмент конвертера может сделать его менее сложным в использовании.
heltonbiker

71

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

3
Это отличный инструмент. Я хотел бы, чтобы это существовало 5 лет назад!
jugg1es

Блестящий инструмент, но падает в стилях. <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>'Binding' недопустим для Setter.Value 'ошибка времени компиляции
mcalex

21

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

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

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


19
Когда кто-то дает -1, было бы неплохо объяснить, почему.
Noxxys

16

Я хотел, чтобы мой XAML оставался настолько элегантным, насколько это возможно, поэтому я создал класс, чтобы обернуть bool, который находится в одной из моих общих библиотек, неявные операторы позволяют классу беспрепятственно использоваться как bool в code-behind

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

1
Недостатком является то, что вам придется изменить весь код, который сравнивает его с / назначает его другим boolвыражениям типов / переменным, даже не ссылаясь на обратное значение. Вместо этого я бы добавил метод расширения «Не» в Boolean Struct.
Том

1
Doh! Ничего. Вспомнить должен быть Propertyпротив Methodза Binding. Мое утверждение о «даунсайде» остается в силе. Кстати, метод «Boolean» «Not» по-прежнему полезен для избежания «!» Оператор, который легко пропустить, когда он (как это часто бывает) встроен рядом с символами, которые похожи на него (т. Е. Один / более "(" и "я" и "я").
Том

10

Этот также работает для обнуляемых 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
}

4

Добавьте еще одно свойство в вашу модель представления, которая будет возвращать обратное значение. И свяжи это с кнопкой. Подобно;

с точки зрения модели:

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

в xaml:

IsEnabled="{Binding IsNotReadOnly"}

1
Отличный ответ. Следует добавить, что с помощью этого вам лучше вызвать событие PropertyChanged для IsNotReadOnly в установщике для свойства IsReadOnly. При этом вы убедитесь, что пользовательский интерфейс обновляется правильно.
Муханнад

Это должен быть принятый ответ, так как он самый простой.
Габнаим

2

Не знаю, относится ли это к 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
Кажется, почти так же, как конвертер. Formatи Parseсобытия в привязках WinForms примерно эквивалентны конвертеру WPF.
Алехандро

2

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

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


2

Net .Net Core Solution 💡

Обрабатывает нулевую ситуацию и не выдает исключение, но возвращает, 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

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

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

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

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


2
Не будучи полным свойством и не имея OnPropertyChanged, это не сработает. 1-й или 2-й ответы - то, что я использую, в зависимости от случая. Если вы не используете фреймворк, такой как Prism, где frameowkr знает, когда обновлять «упомянутые» свойства. Тогда трудно между тем, что вы предложили (но с полным свойством), и ответом 1
Oyiwai

1

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

0

Я использую похожий подход, как @Ofaim

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

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

public bool EnableSaveButton => !jobSaved;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.