Як зв’язати зворотні булеві властивості у WPF?


377

У мене є об’єкт, який має IsReadOnlyвластивість. Якщо це властивість вірно, я хотів би встановити 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>


е-мс, роби добре, але незавершив це
user1005462

Відповіді:


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
Я маю тут розглянути кілька речей, які, ймовірно, змусять мене вибрати відповідь @ Павла на цей. Я сам по собі кодую (поки що), тому мені потрібно підійти до рішення, яке «я» запам’ятає, яке я буду використовувати знову і знову. Я також відчуваю, що чим менше слово щось краще, і створення зворотної властивості є дуже явним, що полегшує мені запам’ятовування, а також майбутніх дияволів (я сподіваюся, сподіваюся), щоб швидко зрозуміти, що я робив, а також полегшував, щоб вони кинули мене під послідний автобус.
Russ

17
За власними аргументами, рішення IMHO для перетворювача краще в довгостроковій перспективі: вам потрібно записати перетворювач лише один раз, після чого ви можете використовувати його знову і знову. Якщо ви підете на нову власність, вам доведеться її переписати в кожен клас, який їй потрібен ...
Томас Левеск

51
Я використовую той самий підхід ... але це робить panda saaad ... = (
Макс Галкін

27
Порівняно з !цим, це якийсь довгодушний код ... Люди докладають шалених зусиль, щоб відокремити те, що, на їхню думку, є «кодом» від тих бідних дизайнерів. Додатковий біль боляче, коли я і кодер, і дизайнер.
Роман Старков

10
багато людей, включаючи мене, вважають це головним прикладом надмірної інженерії. Я пропоную скористатися перевернутим властивістю, як у публікації Павла Олександра нижче.
Крістіан Вестман

99

Чи вважали ви IsNotReadOnlyвласністю? Якщо об'єктом, який пов'язаний, є ViewModel у домені MVVM, то додаткове властивість має ідеальний сенс. Якщо це модель прямої особи, ви можете розглянути склад і представити спеціалізований ViewModel своєї сутності до форми.


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>отримує "Прив'язка" не вірна для Setter.Value "Помилка часу компіляції
mcalex

21

Я рекомендую використовувати https://quickconverter.codeplex.com/

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

Це прискорює час, необхідний для запису перетворювачів.


18
Даючи -1 комусь, було б непогано пояснити, чому.
Noxxys

16

Я хотів, щоб мій 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 прив'язка або до значення, або до Інвертувати

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"

1
Недоліком є ​​те, що вам доведеться змінити весь код, який порівнював його з / привласнив його іншим boolвидам виразів / змінних, навіть не посилаючись на зворотне значення. Я б замість цього додати "Не" метод розширення до Boolean Struct.
Том

1
До! Не звертай уваги. Згадати повинен бути Propertyпроти Methodза Binding. Моя заява "Далі" все ще діє. До речі, метод розширення "Булева" "Не" все ще корисний, щоб уникнути "!" Оператор, якого легко пропустити, коли він (як це часто буває) вбудований поруч із символами, схожими на нього (тобто один / більше "(" 'і "l"' і "Я")
Том

10

Цей також працює для змінних булів.

 [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. За допомогою цього ви переконаєтесь, що інтерфейс користувача оновлюється правильно.
Муханнад

Це має бути прийнятою відповіддю, оскільки вона є найпростішою.
gabnaim

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
Здається, майже все те ж саме, що і підхід до перетворення. Formatі Parseподії у прив'язках WinForms є приблизно еквівалентними конвертору WPF.
Алехандро

2

У мене була проблема з інверсією, але акуратне рішення.

Мотивація полягала в тому, що дизайнер 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, встановивши за замовчуванням прив'язку. Звичайно, це не працює для всіх видів зворотних проблем, але допомогло мені з чистим кодом.


2

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

Після відповіді @ Павла я написав у ViewModel таке:

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

Я сподіваюся, що фрагмент тут допоможе комусь, можливо, новачкові, як я.
І якщо є помилка, будь ласка, повідомте мене!

До речі, я також погоджуюся з коментарем @heltonbiker - це, безумовно, правильний підхід, тільки якщо вам не доведеться використовувати його більше 3 разів ...


2
Якщо ви не будете повною властивістю та не маєте "OnPropertyChanged", це не працюватиме. Перша чи друга відповіді - це те, що я використовую, залежно від конкретного випадку. Якщо ви не використовуєте таку структуру, як Prism, де frameowkr знає, коли потрібно оновити "згадані" властивості. Тоді це обман між використанням чогось такого, що ви запропонували (але з повною властивістю), і відповіддю 1
Oyiwai

1

Я зробив щось дуже схоже. Я створив свою власність за лаштунками, яка дала змогу вибирати комбінаційну скриньку ТОЛЬКО, якщо вона закінчила пошук даних. Коли моє вікно вперше з'являється, воно запускає завантажену асинхронною командою команду, але я не хочу, щоб користувач натискав комбобокс, поки він ще завантажує дані (буде порожнім, тоді буде заповнено). Тож за замовчуванням властивість хибна, тому я повертаю зворотне в getter. Тоді, коли я шукаю, я встановлюю властивість на істинне, а назад на хибне, коли завершено.

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.