Как связать обратные логические свойства в WPF?
У меня есть объект, обладающий свойством IsReadOnly
. Если это свойство истинно, я хотел бы установить свойство IsEnabled
на Button (например) на 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>
Ответы
Ответ 1
Вы можете использовать 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
}
Ответ 2
Рассматривали ли вы собственность IsNotReadOnly
? Если привязываемый объект является ViewModel в домене MVVM, тогда дополнительное свойство имеет смысл. Если это прямая модель сущностей, вы можете рассмотреть состав и представление в форму специализированной модели представления вашей сущности.
Ответ 3
При стандартном привязке вам необходимо использовать конвертеры, которые выглядят немного ветреными. Поэтому я рекомендую вам посмотреть мой проект 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, и он будет работать правильно
Гудлак!
Ответ 4
Я бы рекомендовал использовать https://quickconverter.codeplex.com/
Инвертирование логического значения тогда просто:
<Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />
Это ускоряет время, необходимое для записи преобразователей.
Ответ 5
Я хотел, чтобы мой XAML оставался настолько изящным, насколько это возможно, поэтому я создал класс для обертывания 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;
}
}
И в postfix XAML привязка с помощью значения или инверсии
IsEnabled="{Binding IsActive.Value}"
IsEnabled="{Binding IsActive.Invert}"
Ответ 6
Это также работает для обнуляемых 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
}
Ответ 7
Не знаю, относится ли это к 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;
}
Ответ 8
Добавьте еще одно свойство в свою модель просмотра, которое вернет обратное значение.
И привяжите это к кнопке.
Как;
в поле зрения модели:
public bool IsNotReadOnly{get{return !IsReadOnly;}}
в xaml:
IsEnabled="{Binding IsNotReadOnly"}
Ответ 9
У меня была проблема с инверсией, но было аккуратное решение.
Мотивация заключалась в том, что дизайнер 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 устанавливает значение по умолчанию для привязки. Конечно, это не работает для всех видов обратных задач, но помогло мне с чистым кодом.
Ответ 10
После ответа @Paul я написал в ViewModel следующее:
public bool ShowAtView { get; set; }
public bool InvShowAtView { get { return !ShowAtView; } }
Я надеюсь, что наличие фрагмента здесь поможет кому-то, возможно, новичку, как и я.
И если есть ошибка, пожалуйста, дайте мне знать!
Кстати, я также согласен с комментарием @heltonbiker - это, безусловно, правильный подход , только если вам не нужно использовать его более 3 раз...
Ответ 11
Я сделал что-то очень похожее. Я создал свое свойство за кулисами, что позволило выбрать комбинированный список ТОЛЬКО в том случае, если он завершил поиск данных. Когда мое окно впервые появляется, оно запускает асинхронную загруженную команду, но я не хочу, чтобы пользователь нажимал на поле со списком, пока он все еще загружает данные (будет пустым, а затем будет заполнен). Так что по умолчанию свойство имеет значение 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" />
Ответ 12
Я использую похожий подход, как @Ofaim
private bool jobSaved = true;
private bool JobSaved
{
get => jobSaved;
set
{
if (value == jobSaved) return;
jobSaved = value;
OnPropertyChanged();
OnPropertyChanged("EnableSaveButton");
}
}
public bool EnableSaveButton => !jobSaved;