Comment lier des propriétés booléennes inverses dans WPF?

378

Ce que j'ai, c'est un objet qui a une IsReadOnlypropriété. Si cette propriété est vraie, je voudrais définir la IsEnabledpropriété d'un bouton (par exemple) sur false.

Je voudrais croire que je peux le faire aussi facilement que cela, IsEnabled="{Binding Path=!IsReadOnly}"mais cela ne fonctionne pas avec WPF.

Suis-je relégué à devoir passer par tous les paramètres de style? Cela semble trop verbeux pour quelque chose d'aussi simple que de mettre un booléen à l'inverse d'un autre booléen.

<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
la source
eh ms fait la bonne chose mais la complète
user1005462

Réponses:

488

Vous pouvez utiliser un ValueConverter qui inverse une propriété booléenne pour vous.

XAML:

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

Convertisseur:

[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
la source
8
Il y a quelques choses que je dois considérer ici, qui me feront probablement choisir la réponse de @ Paul par rapport à celle-ci. Je suis tout seul lors du codage (pour l'instant), donc je dois choisir une solution dont "je" me souviendrai, que j'utiliserai encore et encore. Je pense également que moins quelque chose est verbeux, mieux c'est, et créer une propriété inverse est très explicite, ce qui me permet de me souvenir facilement, ainsi que des futurs développeurs (j'espère, j'espère), pour pouvoir voir rapidement ce que je faisait, ainsi que de leur faciliter la tâche de me jeter sous le bus proverbial.
Russ
17
Selon vos propres arguments, à mon humble avis, la solution du convertisseur est meilleure à long terme: vous n'avez qu'à écrire le convertisseur une fois, et après cela, vous pouvez le réutiliser encore et encore. Si vous optez pour la nouvelle propriété, vous devrez la réécrire dans chaque classe qui en a besoin ...
Thomas Levesque
51
J'utilise la même approche ... mais ça fait panda saaad ... = (
Max Galkin
27
Comparé à !, c'est un code de longue haleine ... Les gens font des efforts fous pour séparer ce qu'ils pensent être du "code" de ces pauvres concepteurs. Extra extra douloureux quand je suis à la fois codeur et designer.
Roman Starkov
10
beaucoup de gens, y compris moi-même, considéreraient cela comme un excellent exemple de suringénierie. Je suggère d'utiliser une propriété inversée comme dans le post de Paul Alexander ci-dessous.
Christian Westman
99

Avez-vous envisagé une IsNotReadOnlypropriété? Si l'objet lié est un ViewModel dans un domaine MVVM, la propriété supplémentaire est parfaitement logique. S'il s'agit d'un modèle d'entité direct, vous pouvez envisager la composition et la présentation d'un ViewModel spécialisé de votre entité au formulaire.

Paul Alexander
la source
5
Je viens de résoudre le même problème en utilisant cette approche et je conviens que non seulement il est plus élégant, mais beaucoup plus facile à entretenir que d'utiliser un convertisseur.
alimbada
28
Je ne suis pas d'accord pour dire que cette approche est meilleure que le convertisseur de valeur. Il produit également plus de code si vous avez besoin de plusieurs instances NotProperty.
Thiru
25
MVVM ne consiste pas à ne pas écrire de code, il s'agit de résoudre les problèmes de manière déclarative. À cette fin, le convertisseur est la bonne solution.
Jeff
14
Le problème avec cette solution est que si vous avez 100 objets, vous devrez ajouter une propriété IsNotReadOnly à tous les 100 objets. Cette propriété devrait être un DependencyProperty. Cela ajoute environ 10 lignes de code aux 100 objets ou 1 000 lignes de code. Le convertisseur est composé de 20 lignes de code. 1000 lignes ou 20 lignes. Lequel choisiriez-vous?
Rhyous
8
Il y a un dicton commun pour cela: faites-le une fois, faites-le deux fois, puis automatisez. Dans le doute, j'utiliserais cette réponse la première fois qu'elle serait nécessaire dans un projet, puis si les choses grandissaient, j'utiliserais la réponse acceptée. Mais avoir l'extrait de code prédéfini pourrait rendre l'utilisation beaucoup moins difficile.
heltonbiker
71

Avec la reliure standard, vous devez utiliser des convertisseurs qui semblent peu venteux. Donc, je vous recommande de regarder mon projet CalcBinding , qui a été développé spécialement pour résoudre ce problème et quelques autres. Avec la liaison avancée, vous pouvez écrire des expressions avec de nombreuses propriétés source directement dans xaml. Dites, vous pouvez écrire quelque chose comme:

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

ou

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

ou

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

ou

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

où A, B, C, IsChecked - propriétés de viewModel et cela fonctionnera correctement

Alex141
la source
6
Bien que QuickConverter soit plus puissant, je trouve le mode CalcBinding lisible - utilisable.
xmedeko
3
Ceci est un excellent outil. J'aurais aimé qu'il existe il y a 5 ans!
jugg1es
Outil brillant, mais tombe dans les styles. <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>obtient une 'liaison' n'est pas valide pour Setter.Value 'erreur de compilation
mcalex
21

Je recommanderais d'utiliser https://quickconverter.codeplex.com/

Inverser un booléen est alors aussi simple que: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

Cela accélère le temps normalement nécessaire pour écrire des convertisseurs.

Noxxys
la source
19
Lorsque vous donnez un -1 à quelqu'un, ce serait bien d'expliquer pourquoi.
Noxxys
16

Je voulais que mon XAML reste aussi élégant que possible, j'ai donc créé une classe pour envelopper le bool qui réside dans l'une de mes bibliothèques partagées, les opérateurs implicites permettent à la classe d'être utilisée comme bool dans le code-behind de manière transparente

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

}

Les seules modifications nécessaires à votre projet sont de faire en sorte que la propriété que vous souhaitez inverser renvoie ceci au lieu de bool

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

Et dans le suffixe XAML, la liaison avec Value ou Invert

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"
jevansio
la source
1
L'inconvénient est que vous devez modifier tout le code qui le compare avec / lui est attribué à d'autres boolexpressions / variables de type, même sans référencer la valeur inverse. J'ajouterais à la place une méthode d'extension "Non" à la Boolean Struct.
Tom
1
Ah! Ça ne fait rien. Forgot devait être Propertycontre Methodpour Binding. Ma déclaration de «inconvénient» s'applique toujours. Btw, la méthode d'extension «non» «booléenne» est toujours utile pour éviter le «!» Opérateur qui est facilement manqué quand il (comme c'est souvent le cas) est intégré à côté des caractères qui lui ressemblent (c.-à-d. Un / plusieurs "(" et "l" et "je"))
Tom
10

Celui-ci fonctionne également pour les bools nullables.

 [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
la source
4

Ajoutez une propriété de plus dans votre modèle de vue, qui renverra la valeur inverse. Et liez ça au bouton. Comme;

en vue modèle:

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

en xaml:

IsEnabled="{Binding IsNotReadOnly"}
MMM
la source
1
Très bonne réponse. Une chose à ajouter, en utilisant cela, vous feriez mieux de déclencher l'événement PropertyChanged pour IsNotReadOnly dans le setter de la propriété IsReadOnly. Avec cela, vous vous assurerez que l'interface utilisateur est correctement mise à jour.
Muhannad
Cela devrait être la réponse acceptée car c'est la plus simple.
gabnaim
2

Je ne sais pas si cela est pertinent pour XAML, mais dans ma simple application Windows, j'ai créé la liaison manuellement et ajouté un gestionnaire d'événements 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;
}
Simon Dobson
la source
1
Semble à peu près la même que l'approche du convertisseur. Formatet les Parseévénements dans les liaisons WinForms sont à peu près équivalents au convertisseur WPF.
Alejandro
2

J'ai eu un problème d'inversion, mais une bonne solution.

La motivation était que le concepteur XAML afficherait un contrôle vide, par exemple lorsqu'il n'y avait pas de datacontext / no MyValues(itemssource).

Code initial: masquer le contrôle lorsqu'il MyValuesest vide. Code amélioré: affichez le contrôle quand il MyValuesn'est PAS nul ou vide.

Bien entendu, le problème est de savoir comment exprimer «1 ou plusieurs éléments», ce qui est l'opposé de 0 élément.

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

Je l'ai résolu en ajoutant:

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

Ergo définissant la valeur par défaut pour la liaison. Bien sûr, cela ne fonctionne pas pour toutes sortes de problèmes inverses, mais cela m'a aidé avec du code propre.

EricG
la source
2

💡 .Net Core Solution 💡

Gère une situation nulle et ne renvoie pas d'exception, mais retourne truesi aucune valeur n'est présentée; sinon prend le booléen entré et l'inverse.

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 J'aime mettre toutes mes statistiques de convertisseur dans le fichier app.xaml donc je n'ai pas à les redéclarer dans les fenêtres / pages / contrôles du projet.

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

Pour être clair, converters:c'est l'espace de noms de l'implémentation de classe réelle ( xmlns:converters="clr-namespace:ProvingGround.Converters").

ΩmegaMan
la source
1

Suite à la réponse de @ Paul, j'ai écrit ce qui suit dans le ViewModel:

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

J'espère qu'avoir un extrait ici aidera quelqu'un, probablement un débutant comme moi.
Et s'il y a une erreur, faites-le moi savoir!

BTW, je suis également d'accord avec le commentaire de @heltonbiker - c'est certainement l'approche correcte uniquement si vous n'avez pas à l'utiliser plus de 3 fois ...

Ofaim
la source
2
N'étant pas une propriété complète et manquant d'un "OnPropertyChanged" cela ne fonctionnera pas. La première ou la deuxième réponse est ce que j'utilise, selon le cas. Sauf si vous utilisez un framework comme Prism où le frameowkr sait quand mettre à jour les propriétés "référencées". Ensuite, c'est un tirage au sort entre l'utilisation de quelque chose comme ce que vous avez suggéré (mais avec une propriété complète), et la réponse 1
Oyiwai
1

J'ai fait quelque chose de très similaire. J'ai créé ma propriété dans les coulisses qui a permis la sélection d'une zone de liste déroulante UNIQUEMENT si elle avait fini de rechercher des données. Lorsque ma fenêtre apparaît pour la première fois, elle lance une commande chargée asynchrone mais je ne veux pas que l'utilisateur clique sur la zone de liste déroulante pendant le chargement des données (serait vide, puis renseignée). Donc, par défaut, la propriété est fausse, je renvoie donc l'inverse dans le getter. Ensuite, lorsque je recherche, je définis la propriété sur true et de nouveau sur false une fois terminé.

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

Ensuite, pour la zone de liste déroulante, je peux le lier directement à IsSearching:

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

J'utilise une approche similaire comme @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
la source