Wie binde inverse boolesche Eigenschaften in WPF binden?

378

Was ich habe, ist ein Objekt, das eine IsReadOnlyEigenschaft hat. Wenn diese Eigenschaft wahr ist, möchte ich die IsEnabledEigenschaft für eine Schaltfläche (zum Beispiel) auf falsch setzen.

Ich würde gerne glauben, dass ich es so einfach machen kann, IsEnabled="{Binding Path=!IsReadOnly}"aber das fliegt nicht mit WPF.

Muss ich alle Stileinstellungen durchlaufen? Scheint einfach zu wortreich für etwas so Einfaches wie das Setzen eines Bools auf die Umkehrung eines anderen Bools.

<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
quelle
Siehe auch
UuDdLrLrSs
eh ms gut machen, aber unvollständig
user1005462

Antworten:

488

Sie können einen ValueConverter verwenden, der eine bool-Eigenschaft für Sie invertiert.

XAML:

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

Konverter:

[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
quelle
8
Es gibt ein paar Dinge, die ich hier berücksichtigen muss, die mich wahrscheinlich dazu bringen werden, die Antwort von @ Paul über diese zu wählen. Ich bin beim Codieren (vorerst) alleine, daher muss ich eine Lösung wählen, an die sich "ich" erinnern wird und die ich immer wieder verwenden werde. Ich habe auch das Gefühl, dass je weniger wortreich etwas ist, desto besser, und das Erstellen einer inversen Eigenschaft ist sehr explizit, was es mir leicht macht, mich daran zu erinnern, sowie zukünftige Entwickler (ich hoffe, ich hoffe), schnell zu sehen, was ich bin tat es und machte es ihnen leichter, mich unter den sprichwörtlichen Bus zu werfen.
Russ
17
Nach Ihren eigenen Argumenten ist IMHO die Konverterlösung auf lange Sicht besser: Sie müssen den Konverter nur einmal schreiben und können ihn danach immer wieder verwenden. Wenn Sie sich für die neue Immobilie entscheiden, müssen Sie sie in jeder Klasse, die sie benötigt, neu schreiben ...
Thomas Levesque
51
Ich benutze den gleichen Ansatz ... aber es macht Panda Saaad ... = (
Max Galkin
27
Im Vergleich dazu !ist das ein langwieriger Code ... Die Leute geben sich wahnsinnig viel Mühe, um das, was sie für "Code" halten, von diesen armen Designern zu trennen. Besonders schmerzhaft, wenn ich sowohl Programmierer als auch Designer bin.
Roman Starkov
10
Viele Leute, einschließlich mir, würden dies als ein Paradebeispiel für Überentwicklung betrachten. Ich schlage vor, eine invertierte Eigenschaft wie im folgenden Beitrag von Paul Alexander zu verwenden.
Christian Westman
99

Haben Sie eine IsNotReadOnlyImmobilie in Betracht gezogen ? Wenn das zu bindende Objekt ein ViewModel in einer MVVM-Domäne ist, ist die zusätzliche Eigenschaft durchaus sinnvoll. Wenn es sich um ein direktes Entitätsmodell handelt, können Sie die Komposition in Betracht ziehen und dem Formular ein spezielles ViewModel Ihrer Entität präsentieren.

Paul Alexander
quelle
5
Ich habe gerade das gleiche Problem mit diesem Ansatz gelöst und bin damit einverstanden, dass es nicht nur eleganter, sondern auch viel wartbarer ist als die Verwendung eines Konverters.
Alimbada
28
Ich würde nicht zustimmen, dass dieser Ansatz besser ist als der Wertekonverter. Es wird auch mehr Code erzeugt, wenn Sie mehrere NotProperty-Instanzen benötigen.
Thiru
25
Bei MVVM geht es nicht darum, keinen Code zu schreiben, sondern Probleme deklarativ zu lösen. Zu diesem Zweck der Konverter ist die richtige Lösung.
Jeff
14
Das Problem bei dieser Lösung besteht darin, dass Sie bei 100 Objekten allen 100 Objekten eine IsNotReadOnly-Eigenschaft hinzufügen müssen. Diese Eigenschaft müsste eine DependencyProperty sein. Das fügt allen 100 Objekten oder 1000 Codezeilen ungefähr 10 Codezeilen hinzu. Der Konverter besteht aus 20 Codezeilen. 1000 Zeilen oder 20 Zeilen. Welches würdest du nehmen?
Rhyous
8
Hierfür gibt es ein allgemeines Sprichwort: einmal, zweimal und dann automatisieren. Im Zweifel würde ich diese Antwort verwenden, wenn sie zum ersten Mal in einem Projekt benötigt wird, und wenn die Dinge wachsen, würde ich die akzeptierte Antwort verwenden. Wenn das Konverter-Snippet jedoch vorgefertigt ist, ist die Verwendung möglicherweise weniger schwierig.
Heltonbiker
71

Bei Standardbindung müssen Sie Konverter verwenden, die wenig windig aussehen. Ich empfehle Ihnen daher, sich mein Projekt CalcBinding anzusehen , das speziell zur Lösung dieses und einiger anderer Probleme entwickelt wurde. Mit der erweiterten Bindung können Sie Ausdrücke mit vielen Quelleneigenschaften direkt in xaml schreiben. Sagen Sie, Sie können so etwas schreiben wie:

<Button IsEnabled="{c:Binding Path=!IsReadOnly}" />

oder

<Button Content="{c:Binding ElementName=grid, Path=ActualWidth+Height}"/>

oder

<Label Content="{c:Binding A+B+C }" />

oder

<Button Visibility="{c:Binding IsChecked, FalseToVisibility=Hidden}" />

Dabei sind A, B, C, IsChecked - Eigenschaften von viewModel und es wird ordnungsgemäß funktionieren

Alex141
quelle
6
Obwohl QuickConverter leistungsfähiger ist, finde ich den CalcBinding-Modus lesbar - verwendbar.
Xmedeko
3
Dies ist ein großartiges Werkzeug. Ich wünschte, es gäbe es vor 5 Jahren!
jugg1es
Geniales Werkzeug, fällt aber in Stilen um. <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>erhält eine 'Bindung' ist nicht gültig für Setter.Value 'Kompilierungszeitfehler
mcalex
21

Ich würde empfehlen, https://quickconverter.codeplex.com/ zu verwenden.

Das Invertieren eines Booleschen Werts ist dann so einfach wie: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

Dies beschleunigt die normalerweise zum Schreiben von Konvertern benötigte Zeit.

Noxxys
quelle
19
Wenn Sie jemandem eine -1 geben, wäre es schön zu erklären, warum.
Noxxys
16

Ich wollte, dass meine XAML so elegant wie möglich bleibt, also habe ich eine Klasse erstellt, um den Bool zu verpacken, der sich in einer meiner gemeinsam genutzten Bibliotheken befindet. Die impliziten Operatoren ermöglichen die nahtlose Verwendung der Klasse als Bool in CodeBehind

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

}

Die einzigen Änderungen, die an Ihrem Projekt erforderlich sind, bestehen darin, dass die Eigenschaft, die Sie invertieren möchten, diese anstelle von bool zurückgibt

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

Und im XAML-Postfix die Bindung entweder mit Value oder Invert

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"
jevansio
quelle
1
Der Nachteil ist, dass Sie den gesamten Code ändern müssen, der ihn mit anderen boolTypausdrücken / Variablen verglichen / zugewiesen hat, auch wenn er nicht auf den inversen Wert verweist. Ich würde stattdessen eine "Nicht" -Erweiterungsmethode hinzufügen Boolean Struct.
Tom
1
Doh! Keine Ursache. Passwort sein musste Propertyvs. Methodfür Binding. Meine "Downside" -Aussage gilt weiterhin. Übrigens ist die "Boolesche" "Nicht" -Erweiterungsmethode immer noch nützlich, um das "!" Operator, der leicht übersehen wird, wenn er (wie so oft) neben Zeichen eingebettet ist, die so aussehen (dh ein / mehrere "(" und "l" und "ich").
Tom
10

Dieser funktioniert auch für nullfähige Bools.

 [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
quelle
4

Fügen Sie Ihrem Ansichtsmodell eine weitere Eigenschaft hinzu, die den umgekehrten Wert zurückgibt. Und binde das an den Knopf. Mögen;

im Ansichtsmodell:

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

in xaml:

IsEnabled="{Binding IsNotReadOnly"}
MMM
quelle
1
Gute Antwort. Wenn Sie dies hinzufügen, können Sie das PropertyChanged-Ereignis für IsNotReadOnly im Setter für die Eigenschaft IsReadOnly besser auslösen. Damit stellen Sie sicher, dass die Benutzeroberfläche korrekt aktualisiert wird.
Muhannad
Dies sollte die akzeptierte Antwort sein, da es die einfachste ist.
Gabnaim
2

Ich weiß nicht, ob dies für XAML relevant ist, aber in meiner einfachen Windows-App habe ich die Bindung manuell erstellt und einen Format-Ereignishandler hinzugefügt.

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
quelle
1
Scheint ziemlich gleich zu sein wie der Konverter-Ansatz. Formatund ParseEreignisse in WinForms-Bindungen entsprechen in etwa dem WPF-Konverter.
Alejandro
2

Ich hatte ein Inversionsproblem, aber eine ordentliche Lösung.

Die Motivation war, dass der XAML-Designer ein leeres Steuerelement anzeigt, z. B. wenn kein Datenkontext / no MyValues(itemssource) vorhanden ist.

Anfangscode: Steuerung ausblenden , wenn sie MyValuesleer ist. Verbesserte Code: Show Kontrolle , wenn MyValuesnicht null oder leer.

Das Problem ist natürlich, wie man "1 oder mehr Elemente" ausdrückt, was das Gegenteil von 0 Elementen ist.

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

Ich habe es gelöst, indem ich hinzugefügt habe:

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

Ergo wird die Standardeinstellung für die Bindung festgelegt. Natürlich funktioniert dies nicht bei allen Arten von inversen Problemen, hat mir aber mit sauberem Code geholfen.

EricG
quelle
2

💡 .Net Core Solution 💡

Behandelt die Nullsituation und löst keine Ausnahme aus, gibt jedoch zurück, truewenn kein Wert angezeigt wird. Andernfalls wird der eingegebene Boolesche Wert verwendet und umgekehrt.

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 Ich möchte alle meine Konverterstatiken in die Datei app.xaml einfügen, damit ich sie nicht in den Fenstern / Seiten / Steuerelementen des Projekts neu deklarieren muss.

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

Klar converters:ist der Namespace zur eigentlichen Klassenimplementierung ( xmlns:converters="clr-namespace:ProvingGround.Converters").

ΩmegaMan
quelle
1

Nach der Antwort von @ Paul habe ich Folgendes im ViewModel geschrieben:

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

Ich hoffe, dass ein Ausschnitt hier jemandem hilft, wahrscheinlich Neuling wie ich.
Und wenn es einen Fehler gibt, lassen Sie es mich bitte wissen!

Übrigens stimme ich auch dem Kommentar von @heltonbiker zu - es ist definitiv der richtige Ansatz, nur wenn Sie ihn nicht mehr als dreimal verwenden müssen ...

Ofaim
quelle
2
Da es sich nicht um eine vollständige Eigenschaft handelt und kein "OnPropertyChanged" vorhanden ist, funktioniert dies nicht. Je nach Fall verwende ich die erste oder zweite Antwort. Es sei denn, Sie verwenden ein Framework wie Prism, bei dem der Frameowkr weiß, wann "verwiesene" Eigenschaften aktualisiert werden müssen. Dann ist es ein Wurf zwischen der Verwendung von etwas wie dem, was Sie vorgeschlagen haben (aber mit vollem Eigentum), und der Antwort 1
Oyiwai
1

Ich habe etwas sehr ähnliches gemacht. Ich habe meine Eigenschaft hinter den Kulissen erstellt, die die Auswahl einer Combobox NUR ermöglichte, wenn die Suche nach Daten abgeschlossen war. Wenn mein Fenster zum ersten Mal angezeigt wird, wird ein asynchron geladener Befehl gestartet, aber ich möchte nicht, dass der Benutzer auf die Combobox klickt, während noch Daten geladen werden (wäre leer und würde dann ausgefüllt). Standardmäßig ist die Eigenschaft also falsch, daher gebe ich die Umkehrung im Getter zurück. Wenn ich dann suche, setze ich die Eigenschaft auf true und nach Abschluss wieder auf 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;
}

Dann kann ich die Combobox direkt an IsSearching binden:

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

Ich benutze einen ähnlichen Ansatz wie @Ofaim

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

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

public bool EnableSaveButton => !jobSaved;
Soulflyman
quelle