2009-06-24 20 views
302

Quello che ho è un oggetto che ha una proprietà IsReadOnly. Se questa proprietà è true, desidero impostare la proprietà su un pulsante (ad esempio) su false.Come associare le proprietà booleane inverse in WPF?

Mi piacerebbe credere che posso farlo facilmente come IsEnabled="{Binding Path=!IsReadOnly}" ma che non vola con WPF.

Sono retrocesso a dover passare attraverso tutte le impostazioni di stile? Sembra troppo prolisso per qualcosa di così semplice come impostare un bool nell'inverso di un altro bool.

risposta

381

È possibile utilizzare un ValueConverter che inverte una proprietà bool per l'utente.

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

Ci sono alcune cose che devo prendere in considerazione qui, che probabilmente farà a scegliere @ risposta di Paolo su questo. Sono da solo quando codifico (per ora), quindi ho bisogno di andare con una soluzione che "I" ricorderà, che userò più e più volte. Inoltre, sento che la parola meno prolissa è la migliore, e la creazione di una proprietà inversa è molto esplicita, rendendo facile per me ricordare, così come i futuri sviluppatori (I Hope, I Hope), essere in grado di vedere rapidamente quello che stavo facendo, oltre a rendere più facile per loro gettarmi sotto il proverbiale autobus. – Russ

+14

Secondo i tuoi argomenti, IMHO la soluzione di conversione è migliore a lungo termine: devi solo scrivere il convertitore una volta, e dopo puoi riutilizzarlo più e più volte. Se vai per la nuova proprietà, dovrai riscriverla in ogni classe che ne ha bisogno ... –

+0

Chris, Thomas, voglio che lo sappiate entrambi, anche se ho scelto la risposta di Paolo come "la risposta", e io non lo faccio Penso che sia giusto cambiarlo scegliendo questo in ritardo nel gioco, concedo qui che hai entrambi ragione, e il convertitore è diventato molto più utile per me, specialmente quando uso valori di framework in cui non ho alcun controllo. – Russ

88

Avete considerato una proprietà IsNotReadOnly? Se l'oggetto associato è un ViewModel in un dominio MVVM, la proprietà aggiuntiva ha perfettamente senso. Se si tratta di un modello Entità diretto, potresti prendere in considerazione la composizione e presentare un modello di visualizzazione specializzato della tua entità al modulo.

+4

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

+0

Ha anche meno implicazioni sulle prestazioni, che è particolarmente importante in cose come Windows Phone. –

+3

IMO, questa è la soluzione preferita. MVVM è la strada da percorrere. Mentre un convertitore fa il lavoro, se puoi evitare il codice in primo luogo ... +1 da me. –

13

mi consiglia di utilizzare https://quickconverter.codeplex.com/

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

che accelera il tempo normalmente necessario per scrivere convertitori.

+9

Quando si dà un -1 a qualcuno, sarebbe bello spiegare perché. – Noxxys

48

Con il binding standard è necessario utilizzare 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 l'associazione avanzata è possibile scrivere espressioni con molte proprietà di origine direttamente in xaml. Dire, si può scrivere qualcosa di simile:

<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

Goodluck!

+5

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

+0

Questo è un ottimo strumento. Vorrei che esistesse 5 anni fa! – jugg1es

1

Non so se questo è rilevante per XAML, ma nella mia semplice app di Windows ho creato manualmente il binding 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; 
} 
+0

Sembra quasi l'equivalente dell'approccio del convertitore. Gli eventi 'Format' e' Parse' nei collegamenti WinForms sono approssimativamente equivalenti al convertitore WPF. – Alejandro

10

volevo che il mio XAML di rimanere il più elegante possibile, così ho creato una classe per avvolgere il bool che risiede in una delle mie librerie condivise, gli operatori impliciti consentono la classe da utilizzare come un bool in code- dietro senza soluzione di continuità

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 progetto sono per rendere la proprietà che si vuole invertire restituire questo invece di bool

public InvertableBool IsActive 
    { 
     get 
     { 
      return true; 
     } 
    } 

E nel XAML postfix il bindi ng con Valore o Inverti

IsEnabled="{Binding IsActive.Value}" 

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

Lo svantaggio è che dovresti cambiare tutto il codice che l'ha confrontato con/assegnato ad altre espressioni/variabili di tipo 'bool', anche senza fare riferimento al valore inverso. Vorrei invece aggiungere un metodo di estensione "Not" a 'Boolean'' Struct'. – Tom

+0

Doh! Non importa. Dimenticato doveva essere 'Property' vs.' Method' per 'Binding'. La mia affermazione "Downside" si applica ancora. Btw, il "booleano" "Not" Extension Method è ancora utile per evitare il "!" Operatore che viene facilmente perso quando (come spesso accade) è incorporato accanto ai caratteri che lo assomigliano (cioè uno/più "(" s "e" l "e" I "). – Tom

5

Questo funziona anche per bool 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

Aggiungi un'altra proprietà nel tuo modello di vista, che restituirà il valore inverso. E legalo al pulsante. Come;

in vista del modello:

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

in XAML:

IsEnabled="{Binding IsNotReadOnly"} 
0

ho avuto un problema di inversione, ma una soluzione ordinata.

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

codice iniziale: nascondi controllo quando MyValues è vuoto. Codice migliorato: mostra il controllo quando MyValues NON è 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> 

ho risolto aggiungendo: impostando

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

Ergo predefinito per il legame. Ovviamente questo non funziona per tutti i tipi di problemi inversi, ma mi ha aiutato con il codice pulito.

0

seguito @ risposta di Paolo, ho scritto quanto segue nella ViewModel:

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

Spero che avere un frammento qui aiuterà qualcuno, probabilmente newbie come lo sono io.
E se c'è un errore, per favore fatemelo sapere!

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

0

ho fatto qualcosa di molto simile. Ho creato la mia proprietà dietro le quinte che permetteva la selezione di una casella combinata SOLO se aveva terminato la ricerca dei 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 combobox 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 sto cercando, imposta la proprietà su true e torna a false quando 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; 
} 

Poi per il combobox che può legarsi direttamente al IsSearching:

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