Come associare le proprietà booleane inverse in WPF?


378

Quello che ho è un oggetto che ha una IsReadOnlyproprietà. Se questa proprietà è vera, vorrei impostare la IsEnabledproprietà su un pulsante, (ad esempio), su false.

Vorrei credere che posso farlo con la stessa facilità IsEnabled="{Binding Path=!IsReadOnly}"ma che non vola con WPF.

Sono relegato a dover passare attraverso tutte le impostazioni di stile? Sembra troppo prolisso per qualcosa di così semplice come impostare un bool sull'inverso di un altro 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>


eh ms fai una cosa buona ma incompleta
user1005462

Risposte:


488

È possibile utilizzare un ValueConverter che inverte una proprietà bool per te.

XAML:

IsEnabled="{Binding Path=IsReadOnly, Converter={StaticResource InverseBooleanConverter}}"

Converter:

[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
Ci sono alcune cose che devo considerare qui, che probabilmente mi faranno scegliere la risposta di @ Paul su questa. Sono da solo durante la programmazione (per ora), quindi ho bisogno di cercare una soluzione che "io" ricorderò, che userò più e più volte. Sento anche che qualcosa di meno prolisso è il migliore, e la creazione di una proprietà inversa è molto esplicita, rendendo facile per me ricordare, così come i futuri sviluppatori (Spero, Spero), di essere in grado di vedere rapidamente ciò che stava facendo, oltre a rendere più facile per loro gettarmi sotto il proverbiale bus.
Russ,

17
Con i tuoi stessi argomenti, IMHO la soluzione del convertitore è migliore a lungo termine: devi solo scrivere il convertitore una volta e dopo puoi riutilizzarlo più volte. Se scegli la nuova proprietà, dovrai riscriverla in ogni classe che ne ha bisogno ...
Thomas Levesque,

51
Sto usando lo stesso approccio ... ma fa panda saaad ... = (
Max Galkin

27
Rispetto a questo !, è un codice prolisso ... Le persone fanno sforzi folli per separare ciò che ritengono essere "codice" da quei poveri designer. Molto più doloroso quando sono sia il programmatore che il designer.
Roman Starkov,

10
molte persone, incluso me stesso, lo considererebbero un ottimo esempio di ingegneria eccessiva. Suggerisco di utilizzare una proprietà invertita come nel post di Paul Alexander di seguito.
Christian Westman,

99

Hai considerato una IsNotReadOnlyproprietà? Se l'oggetto da associare è un ViewModel in un dominio MVVM, la proprietà aggiuntiva ha perfettamente senso. Se si tratta di un modello Entity diretto, è possibile prendere in considerazione la composizione e la presentazione al modulo di un ViewModel specializzato della propria entità.


5
Ho appena risolto lo stesso problema usando questo approccio e sono d'accordo che non solo è più elegante, ma molto più gestibile rispetto all'utilizzo di un convertitore.
alimbada,

28
Non sarei d'accordo sul fatto che questo approccio sia migliore del convertitore di valore. Produce anche più codice se sono necessarie più istanze di NotProperty.
Giovedì

25
MVVM non si tratta di non scrivere codice, ma di risolvere i problemi in modo dichiarativo. A tal fine, il convertitore è la soluzione corretta.
Jeff,

14
Il problema con questa soluzione è che se si dispone di 100 oggetti, è necessario aggiungere una proprietà IsNotReadOnly a tutti i 100 oggetti. Quella proprietà dovrebbe essere una DependencyProperty. Ciò aggiunge circa 10 righe di codice a tutti i 100 oggetti o 1000 righe di codice. Il convertitore è composto da 20 righe di codice. 1000 linee o 20 linee. Quale sceglieresti?
Rhyous,

8
C'è un detto comune per questo: fallo una volta, fallo due volte, quindi automatizza. Nel dubbio, userei questa risposta la prima volta che è necessaria in un progetto, e quindi se le cose crescono, userei la risposta accettata. Ma avere lo snippet del convertitore pre-creato potrebbe renderlo meno difficile da usare.
Heltonbiker,

71

Con la rilegatura standard devi usare convertitori che sembrano poco ventosi. Quindi, ti consiglio di guardare il mio progetto CalcBinding , che è stato sviluppato appositamente per risolvere questo problema e alcuni altri. Con il bind avanzato puoi scrivere espressioni con molte proprietà sorgente direttamente in xaml. Dì, puoi scrivere qualcosa del tipo:

<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}" />

dove A, B, C, IsChecked - proprietà di viewModel e funzionerà correttamente


6
Sebbene QuickConverter sia più potente, trovo leggibile la modalità CalcBinding - utilizzabile.
xmedeko,

3
Questo è un grande strumento. Vorrei che esistesse 5 anni fa!
jugg1es

Strumento brillante, ma ricade negli stili. <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>ottiene un 'Binding' non valido per l'errore di compilazione Setter.Value '
mcalex

21

Consiglierei di utilizzare https://quickconverter.codeplex.com/

Invertire un valore booleano è quindi semplice come: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

Ciò accelera il tempo normalmente necessario per scrivere i convertitori.


19
Quando dai un -1 a qualcuno, sarebbe bello spiegare il perché.
Noxxys,

16

Volevo che il mio XAML rimanesse il più elegante possibile, quindi ho creato una classe per avvolgere il bool che risiede in una delle mie librerie condivise, gli operatori impliciti consentono alla classe di essere usata come bool nel code-behind senza problemi

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

}

Le uniche modifiche necessarie al tuo progetto sono di rendere la proprietà che vuoi invertire restituire questo invece di bool

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

E nel postfix XAML l'associazione con Valore o Inverti

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"

1
Il rovescio della medaglia è che dovresti cambiare tutto il codice che lo ha confrontato con / assegnato ad altre boolespressioni / variabili di tipo anche senza fare riferimento al valore inverso. Vorrei invece aggiungere un metodo di estensione "Non" al Boolean Struct.
Tom,

1
Doh! Non importa. Ho dimenticato di essere Propertycontro Methodper Binding. La mia dichiarazione "Svantaggio" si applica ancora. A proposito, il metodo di estensione "Non" "booleano" è ancora utile per evitare il "!" Operatore che si perde facilmente quando (come spesso accade) è incorporato accanto a caratteri che sembrano simili (ovvero uno / più "(" "e" l "e" I ").
Tom

10

Questo funziona anche per bool null.

 [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

Aggiungi un'altra proprietà nel tuo modello di visualizzazione, che restituirà un valore inverso. E legalo al bottone. Piace;

in vista modello:

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

in xaml:

IsEnabled="{Binding IsNotReadOnly"}

1
Bella risposta. Una cosa da aggiungere, usando questo, è meglio generare l'evento PropertyChanged per IsNotReadOnly nel setter per la proprietà IsReadOnly. Con questo ti assicurerai che l'interfaccia utente venga aggiornata correttamente.
Muhannad,

Questa dovrebbe essere la risposta accettata in quanto è la più semplice.
gabnaim

2

Non so se questo è rilevante per XAML, ma nella mia semplice app di Windows ho creato manualmente l'associazione e aggiunto un gestore di eventi Format.

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
Sembra praticamente uguale all'approccio del convertitore. Formate gli Parseeventi nei collegamenti di WinForms sono approssimativamente equivalenti al convertitore WPF.
Alejandro,

2

Ho avuto un problema di inversione, ma una soluzione chiara.

La motivazione era che il designer XAML avrebbe mostrato un controllo vuoto, ad es. Quando non c'era datacontext / no MyValues(itemssource).

Codice iniziale: nasconde il controllo quando MyValuesè vuoto. Codice migliorato: mostra il controllo quando MyValuesNON è nullo o vuoto.

Ovviamente il problema è come esprimere "1 o più elementi", che è l'opposto di 0 elementi.

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

L'ho risolto aggiungendo:

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

Ergo impostando il valore predefinito per l'associazione. Naturalmente questo non funziona per tutti i tipi di problemi inversi, ma mi ha aiutato con un codice pulito.


2

💡 .Net Core Solution 💡

Gestisce la situazione nulla e non genera un'eccezione, ma restituisce truese non viene presentato alcun valore; altrimenti prende il booleano immesso e lo inverte.

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 Mi piace mettere tutte le statistiche dei miei convertitori nel file app.xaml in modo da non doverle dichiarare nuovamente nelle finestre / pagine / controlli del progetto.

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

Per essere chiari converters:è lo spazio dei nomi per l'implementazione effettiva della classe ( xmlns:converters="clr-namespace:ProvingGround.Converters").


1

Seguendo la risposta di @ Paul, ho scritto quanto segue nel ViewModel:

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

Spero che avere uno snippet qui aiuti qualcuno, probabilmente un principiante come me.
E se c'è un errore, per favore fatemi sapere!

A proposito, sono anche d'accordo con il commento di @heltonbiker - è sicuramente l'approccio corretto solo se non devi usarlo più di 3 volte ...


2
Non essendo una proprietà completa e privo di "OnPropertyChanged" non funzionerà. La prima o la seconda risposta sono ciò che sto usando, a seconda del caso. A meno che non si stia utilizzando un framework come Prism in cui frameowkr sa quando aggiornare le proprietà "referenziate". Quindi è un sorteggio tra l'utilizzo di qualcosa di simile a quello che hai suggerito (ma con proprietà complete) e rispondere 1
Oyiwai,

1

Ho fatto qualcosa di molto simile. Ho creato la mia proprietà dietro le quinte che ha consentito la selezione di una casella combinata SOLO se avesse terminato la ricerca di dati. Quando viene visualizzata per la prima volta la mia finestra, avvia un comando caricato asincrono ma non voglio che l'utente faccia clic sulla casella combinata mentre sta ancora caricando i dati (sarebbe vuoto, quindi sarebbe popolato). Quindi, per impostazione predefinita, la proprietà è falsa, quindi restituisco l'inverso nel getter. Quindi, quando cerco, imposto la proprietà su true e al termine su 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;
}

Quindi per la casella combinata posso collegarlo direttamente a IsSearching:

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

0

Uso un approccio simile come @Ofaim

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

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

public bool EnableSaveButton => !jobSaved;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.