2009-06-24 18 views
302

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.Cómo enlazar propiedades booleanas inversas en WPF?

Me gustaría creer que puedo hacerlo tan fácilmente como IsEnabled="{Binding Path=!IsReadOnly}" pero eso no funciona con WPF.

¿Estoy relegado a tener que pasar por todos los ajustes de estilo? Simplemente parece demasiado prolijo para algo tan simple como establecer un bool en el inverso 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> 

Respuesta

381

Puede utilizar un ValueConverter que invierta una propiedad bool para usted.

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 
    } 
+5

Hay algunas cosas que tengo que considerar aquí, que probablemente harán a escoger @ respuesta de Pablo sobre éste. Estoy solo cuando estoy programando (por ahora), así que tengo que ir con una solución que "yo" recordará, que usaré una y otra vez. También siento que cuanto menos verbal es mejor, y crear una propiedad inversa es muy explícito, lo que me permite recordarlo fácilmente, así como los futuros desarrolladores (Espero, espero), para poder ver rápidamente lo que estaba haciendo, además de facilitarles que me arrojaran bajo el proverbial autobús. – Russ

+14

Según sus propios argumentos, en mi humilde opinión, la solución de conversión es mejor a largo plazo: solo tiene que escribir el convertidor una vez, y luego puede reutilizarlo una y otra vez. Si elige la nueva propiedad, tendrá que volver a escribirla en todas las clases que la necesiten ... –

+0

Chris, Thomas Quiero que ambos sepan que, aunque elegí la respuesta de Paul como "la respuesta", y no lo hago Creo que es justo cambiar esa opción tan tarde en el juego. Estoy de acuerdo en que ambos tenían razón, y el convertidor se ha vuelto mucho más útil para mí, especialmente cuando uso valores de framework en los que no tengo control. – Russ

88

¿Ha considerado una propiedad IsNotReadOnly? Si el objeto enlazado es un ViewModel en un dominio MVVM, entonces 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.

+4

Acabo de resolver el mismo problema utilizando este enfoque y estoy de acuerdo en que no solo es más elegante, sino mucho más fácil de mantener que el uso de un convertidor. – alimbada

+0

También tiene menos implicaciones de rendimiento, lo cual es especialmente importante en cosas como Windows Phone. –

+3

IMO, esta es la solución preferida. MVVM es el camino a seguir. Mientras que un convertidor hace el trabajo, si puede evitar el código en primer lugar ... +1 de mí. –

13

Yo recomiendo usar https://quickconverter.codeplex.com/

invirtiendo un valor lógico es tan simple como: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

que acelera el tiempo que normalmente se necesita para escribir convertidores.

+9

Al dar un -1 a alguien, sería bueno explicar por qué. – Noxxys

48

Con el encuadernado estándar, necesita utilizar convertidores con poco viento. Por lo tanto, te recomiendo que mires 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. Por ejemplo, puede 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

¡Goodluck!

+5

Aunque QuickConverter es más poderoso, encuentro que el modo CalcBinding es legible: utilizable. – xmedeko

+0

Esta es una gran herramienta. ¡Ojalá existiera hace 5 años! – jugg1es

1

No sé si esto es relevante para XAML, pero en mi aplicación simple de Windows, 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; 
} 
+0

Parece más o menos lo mismo que el enfoque del convertidor. Los eventos 'Format' y' Parse' en enlaces WinForms son más o menos equivalentes al convertidor WPF. – Alejandro

10

quería que mi XAML para permanecer tan elegante como sea posible así que creé una clase para envolver el bool que reside en una de mis librerías compartidas, los operadores implícitos permiten la clase para ser utilizado como un bool en código- detrás de la perfección

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 la propiedad que desea invertir devolver este lugar de bool

public InvertableBool IsActive 
    { 
     get 
     { 
      return true; 
     } 
    } 

Y en el postfix XAML el bindi ng con Valor o Invertir

IsEnabled="{Binding IsActive.Value}" 

IsEnabled="{Binding IsActive.Invert}" 
+0

Lo malo es que tendría que cambiar todo el código que lo comparó con/lo asignó a otras expresiones/variables de tipo 'bool' incluso sin hacer referencia al valor inverso. En su lugar, agregaría un método de extensión "No" al 'Boolean'' Struct'. – Tom

+0

Doh! No importa. Olvidó que tenía que ser 'Property' contra' Method' para 'Binding'. Mi declaración "Abajo" todavía se aplica. Por cierto, el método de extensión "No" Booleano "sigue siendo útil para evitar el"! " Operador que se echa de menos fácilmente cuando (como suele ser el caso) está incrustado al lado de caracteres que se parecen (es decir, uno/más "(" 's y "l" y "I"). – Tom

5

Este también funciona para boles nullable.

[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 
} 
0

Agregue una propiedad más en su modelo de vista, que devolverá el valor inverso. Y atar ese botón. Me gusta;

en vista de modelo:

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

en xaml:

IsEnabled="{Binding IsNotReadOnly"} 
0

I tenía un problema de inversión, pero una solución ordenada.

La motivación era que el diseñador de XAML mostraría un control vacío, p. cuando no había datacontext/no MyValues (itemssource).

Código inicial: ocultar control cuando MyValues está vacío. Código mejorado: show 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 contrario de 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 resuelto mediante la adición de: establecer

<DataTrigger Binding="{Binding MyValues.Count, FallbackValue=0, TargetNullValue=0}"> 

Ergo el valor predeterminado para la unión. Por supuesto, esto no funciona para todo tipo de problemas inversos, pero me ayudó con código limpio.

0

Siguiendo @ respuesta de Pablo, escribí lo siguiente en el modelo de vista:

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

espero tener un fragmento aquí ayudará a alguien, probablemente novato como yo.
Y si hay un error, ¡házmelo saber!

Por cierto, también estoy de acuerdo con el comentario @heltonbiker - es definitivamente el enfoque correcto sólo si usted no tiene que utilizar más de 3 veces ...

0

hice algo muy similar. Creé mi propiedad detrás de escena que permitía la selección de un cuadro combinado SÓLO si había terminado de buscar datos.Cuando aparece por primera vez la ventana, inicia un comando cargado de manera sincronizada, pero no quiero que el usuario haga clic en el cuadro combinado mientras todavía está cargando datos (estaría vacío y luego se llenaría). Entonces, por defecto, la propiedad es falsa, así que devuelvo la inversa en el getter. Luego, cuando estoy buscando, establezco la propiedad en verdadero y de regreso en falso cuando se completa.

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

Luego de la lista desplegable que se puede unir directamente a la IsSearching:

<ComboBox ItemsSource="{Binding Cities}" IsEnabled="{Binding IsSearching}" DisplayMemberPath="City" /> 
Cuestiones relacionadas