modern - wpf styles templates
¿Cómo enlazar propiedades booleanas inversas en WPF? (11)
Lo que tengo es un objeto que tiene una propiedad IsReadOnly
. Si esta propiedad es verdadera, me gustaría establecer la propiedad IsEnabled
en un botón, (por ejemplo), en falso.
Me gustaría creer que puedo hacerlo tan fácilmente como IsEnabled="{Binding Path=!IsReadOnly}"
pero eso no vuela con WPF.
¿Estoy relegado a tener que pasar por todas las configuraciones de estilo? Simplemente parece demasiado prolijo para algo tan simple como configurar un bool en la inversa de otro 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>
¿Ha considerado una propiedad IsNotReadOnly? Si el objeto que se está enlazando es un ViewModel en un dominio MVVM, la propiedad adicional tiene mucho sentido. Si se trata de un modelo de entidad directo, puede considerar la composición y presentar un modelo de vista especializado de su entidad al formulario.
Agregue una propiedad más en su modelo de vista, que devolverá el valor inverso. Y atar eso al botón. Me gusta;
en vista modelo:
public bool IsNotReadOnly{get{return !IsReadOnly;}}
en xaml:
IsEnabled="{Binding IsNotReadOnly"}
Con la encuadernación estándar necesita usar convertidores que se vean poco ventosos. Por lo tanto, te recomiendo que veas mi proyecto CalcBinding , que fue desarrollado especialmente para resolver este problema y algunos otros. Con el enlace avanzado, puede escribir expresiones con muchas propiedades de origen directamente en xaml. Oye, puedes escribir algo como:
<Button IsEnabled="{c:Binding Path=!IsReadOnly}" />
o
<Button Content="{c:Binding ElementName=grid, Path=ActualWidth+Height}"/>
o
<Label Content="{c:Binding A+B+C }" />
o
<Button Visibility="{c:Binding IsChecked, FalseToVisibility=Hidden}" />
donde A, B, C, IsChecked - propiedades de viewModel y funcionará correctamente
¡Buena suerte!
Este también funciona para bools anulables.
[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
}
Hice algo muy similar. Creé mi propiedad entre bambalinas que permitía la selección de un cuadro combinado SOLAMENTE si había terminado de buscar datos. Cuando aparece mi ventana por primera vez, ejecuta un comando cargado asíncrono pero no quiero que el usuario haga clic en el cuadro combinado mientras todavía está cargando datos (estaría vacío, luego se llenaría). Entonces, por defecto, la propiedad es falsa, así que devuelvo el inverso en el getter. Luego, cuando estoy buscando, establezco la propiedad en verdadero y de nuevo en falso cuando finalizo
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;
}
Entonces para el combobox puedo enlazarlo directamente a IsSearching:
<ComboBox ItemsSource="{Binding Cities}" IsEnabled="{Binding IsSearching}" DisplayMemberPath="City" />
No sé si esto es relevante para XAML, pero en mi aplicación de Windows simple creé el enlace manualmente y agregué un controlador de eventos de formato.
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;
}
Puedes usar un ValueConverter que invierte una propiedad bool para ti.
XAML:
IsEnabled="{Binding Path=IsReadOnly, Converter={StaticResource InverseBooleanConverter}}"
Convertidor:
[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
}
Quería que mi XAML se mantuviera lo más elegante posible, así que creé una clase para envolver el bool que reside en una de mis bibliotecas compartidas, los operadores implícitos permiten que la clase se use como un bool en el código subyacente sin problemas
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;
}
}
Los únicos cambios necesarios para su proyecto son hacer que la propiedad que desea invertir devuelva esto en lugar de bool
public InvertableBool IsActive
{
get
{
return true;
}
}
Y en el postfix XAML el enlace con Valor o Invertir
IsEnabled="{Binding IsActive.Value}"
IsEnabled="{Binding IsActive.Invert}"
Siguiendo la respuesta de @ Paul, escribí lo siguiente en el ViewModel:
public bool ShowAtView { get; set; }
public bool InvShowAtView { get { return !ShowAtView; } }
Espero que tener un fragmento aquí ayude a alguien, probablemente novato como yo.
Y si hay un error, por favor hágamelo saber!
Por cierto, también estoy de acuerdo con el comentario de @heltonbiker: definitivamente es el enfoque correcto solo si no tienes que usarlo más de 3 veces ...
Tuve un problema de inversión, pero una solución limpia.
La motivación fue que el diseñador de XAML mostraría un control vacío, por ejemplo, cuando no había ningún datacontext / no MyValues
(itemssource).
Código inicial: ocultar control cuando MyValues
está vacío. Código mejorado: muestra el control cuando MyValues
NO es nulo o está vacío.
Por supuesto, el problema es cómo expresar ''1 o más elementos'', que es lo opuesto a 0 elementos.
<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>
Lo resolví agregando:
<DataTrigger Binding="{Binding MyValues.Count, FallbackValue=0, TargetNullValue=0}">
Ergo configurando el predeterminado para el enlace. Por supuesto, esto no funciona para todo tipo de problemas inversos, pero me ayudó con el código limpio.
Yo recomendaría usar https://quickconverter.codeplex.com/
Invertir un booleano es tan simple como: <Button IsEnabled="{qc:Binding ''!$P'', P={Binding IsReadOnly}}" />
Eso acelera el tiempo que normalmente se necesita para escribir convertidores.