¿Cómo unir propiedades booleanas inversas en WPF?

378

Lo que tengo es un objeto que tiene una IsReadOnlypropiedad. Si esta propiedad es verdadera, me gustaría establecer la IsEnabledpropiedad en un Botón, (por ejemplo), en falso.

Me gustaría creer que puedo hacerlo tan fácilmente como IsEnabled="{Binding Path=!IsReadOnly}"eso, 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 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>
Russ
fuente
Relacionado: stackoverflow.com/questions/534575/…
UuDdLrLrSs
eh ms hacer algo bueno pero incompleto
user1005462

Respuestas:

488

Puede usar un ValueConverter que invierta una propiedad bool por 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
    }
Chris Nicol
fuente
8
Hay algunas cosas que debo considerar aquí, que probablemente me harán elegir la respuesta de @ Paul sobre esta. Estoy solo cuando codifico (por ahora), así que necesito ir con una solución que "yo" recordaré, que usaré una y otra vez. También siento que cuanto menos extendido sea algo, mejor, y crear una propiedad inversa es muy explícito, lo que me facilita recordar, así como a los futuros desarrolladores (I Hope, I Hope), poder ver rápidamente lo que estaba haciendo, además de facilitarles que me arrojaran debajo del autobús proverbial.
Russ
17
Según sus propios argumentos, en mi humilde opinión, la solución del convertidor es mejor a largo plazo: solo tiene que escribir el convertidor una vez, y después de eso puede reutilizarlo una y otra vez. Si vas para la nueva propiedad, tendrá que volver a escribir en todas las clases que lo necesita ...
Thomas Levesque
51
Estoy usando el mismo enfoque ... pero hace panda saaad ... = (
Max Galkin
27
Comparado con !, ese es un código de largo aliento ... La gente hace grandes esfuerzos para separar lo que sienten que es "código" de esos diseñadores pobres. Extra extra doloroso cuando soy tanto el codificador como el diseñador.
Roman Starkov
10
mucha gente, incluyéndome a mí, consideraría que este es un excelente ejemplo de ingeniería excesiva. Sugiero usar una propiedad invertida como en la publicación de Paul Alexander a continuación.
Christian Westman
99

¿Has considerado una IsNotReadOnlypropiedad? Si el objeto que está vinculado 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 ViewModel especializado de su entidad al formulario.

Paul Alexander
fuente
55
Simplemente resolví el mismo problema usando este enfoque y estoy de acuerdo en que no solo es más elegante, sino mucho más fácil de mantener que usar un convertidor.
alimbada
28
No estoy de acuerdo con que este enfoque sea mejor que el convertidor de valor. También produce más código si necesita varias instancias de NotProperty.
Thiru
25
MVVM no se trata de no escribir código, se trata de resolver problemas declarativamente. Para ese fin, el convertidor es la solución correcta.
Jeff
14
El problema con esta solución es que si tiene 100 objetos, deberá agregar una propiedad IsNotReadOnly a los 100 objetos. Esa propiedad tendría que ser una DependencyProperty. Eso agrega aproximadamente 10 líneas de código a los 100 objetos o 1000 líneas de código. El convertidor tiene 20 líneas de código. 1000 líneas o 20 líneas. ¿Cuál escogerías?
Rhyous
8
Hay un dicho común para esto: hacerlo una vez, hacerlo dos veces y luego automatizar. En caso de duda, usaría esta respuesta la primera vez que la necesite en un proyecto, y luego, si las cosas crecen, usaría la respuesta aceptada. Pero tener el fragmento del convertidor prefabricado puede hacer que sea mucho más difícil de usar.
heltonbiker
71

Con el enlace estándar necesita usar convertidores que parezcan poco ventosos. Por lo tanto, le recomiendo que mire 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. Digamos que 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

Alex141
fuente
66
Aunque QuickConverter es más potente, creo que el modo CalcBinding es legible, utilizable.
xmedeko
3
Esta es una gran herramienta. ¡Ojalá existiera hace 5 años!
jugg1es
Herramienta brillante, pero se cae en estilos. <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>obtiene un 'Enlace' no es válido para Setter. Error de tiempo de compilación del valor
mcalex
21

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.

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

Quería que mi XAML siguiera siendo 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 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 Value o Invert

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"
jevansio
fuente
1
Lo malo es que tendría que cambiar todo el código que lo comparó con / lo asignó a otras boolExpresiones / Variables de tipo, incluso sin hacer referencia al valor inverso. En su lugar, agregaría un método de extensión "No" al Boolean Struct.
Tom
1
Doh! No importa. Olvidado tuvo que ser Propertyvs. Methodpara Binding. Mi declaración de "desventaja" todavía se aplica. Por cierto, el método de extensión "No" booleano sigue siendo útil para evitar el "!" Operador que se pierde fácilmente cuando (como suele ser el caso) está incrustado junto a los caracteres que se parecen (es decir, uno / más "(" 'y "l" y "I").
Tom
10

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
}
Andreas
fuente
4

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

a la vista modelo:

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

en xaml:

IsEnabled="{Binding IsNotReadOnly"}
MMM
fuente
1
Gran respuesta. Una cosa para agregar, al usar esto es mejor que aumente el evento PropertyChanged para IsNotReadOnly en el configurador de la propiedad IsReadOnly. Con esto, se asegurará de que la IU se actualice correctamente.
Muhannad
Esta debería ser la respuesta aceptada, ya que es la más simple.
gabnaim
2

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;
}
Simon Dobson
fuente
1
Parece más o menos lo mismo que el enfoque del convertidor. Formaty los Parseeventos en los enlaces WinForms son aproximadamente equivalentes al convertidor WPF.
Alejandro
2

Tuve 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, por ejemplo, cuando no había datacontext / no MyValues(itemssource).

Código inicial: oculta el control cuando MyValuesestá vacío. Código mejorado: muestra el control cuando MyValuesNO 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 establece el valor predeterminado para el enlace. Por supuesto, esto no funciona para todo tipo de problemas inversos, pero me ayudó con un código limpio.

EricG
fuente
2

💡 .Net Core Solution 💡

Maneja situaciones nulas y no arroja una excepción, pero regresa truesi no se presenta ningún valor; de lo contrario toma el booleano ingresado y lo invierte.

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 Me gusta poner todas mis estadísticas de convertidor en el archivo app.xaml para no tener que volver a declararlas en las ventanas / páginas / controles del proyecto.

<Application.Resources>
    <converters:BooleanToReverseConverter x:Key="BooleanToReverseConverter"/>
    <local:FauxVM x:Key="VM" />
</Application.Resources>

Para que quede claro converters:es el espacio de nombres para la implementación real de la clase ( xmlns:converters="clr-namespace:ProvingGround.Converters").

ΩmegaMan
fuente
1

Siguiendo la respuesta de @ Paul, escribí lo siguiente en ViewModel:

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

Espero que tener un fragmento aquí ayude a alguien, probablemente novato como soy.
Y si hay un error, ¡házmelo saber!

Por cierto, también estoy de acuerdo con el comentario de @heltonbiker: definitivamente es el enfoque correcto solo si no tiene que usarlo más de 3 veces ...

Ofaim
fuente
2
Al no ser una propiedad completa y carecer de un "OnPropertyChanged", esto no funcionará. La primera o segunda respuesta es lo que estoy usando, dependiendo del caso. A menos que esté utilizando un marco como Prism donde el frameowkr sabe cuándo actualizar las propiedades "referidas". Luego es un cambio entre usar algo como lo que sugirió (pero con toda la propiedad) y responder 1
Oyiwai,
1

Hice algo muy similar Creé mi propiedad detrás de escena que permitía la selección de un cuadro combinado SOLO si había terminado de buscar datos. Cuando mi ventana aparece por primera vez, inicia 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 captador. Luego, cuando estoy buscando, configuro la propiedad en verdadero y de nuevo 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, para el cuadro combinado, puedo vincularlo directamente a IsSearching:

<ComboBox ItemsSource="{Binding Cities}" IsEnabled="{Binding IsSearching}" DisplayMemberPath="City" />
GregN
fuente
0

Yo uso un enfoque similar como @Ofaim

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

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

public bool EnableSaveButton => !jobSaved;
hombre de alma
fuente