wpf radiobutton group




Comment lier RadioButtons à une énumération? (6)

Étendu les grandes idées ci-dessus avec la possibilité de lier les boutons radio à tout type (énumération, booléen, chaîne de caractères, entier, etc.) et fourni un exemple de code de travail ici:

http://www.codeproject.com/Tips/720497/Binding-Radio-Buttons-to-a-Single-Property

J'ai une énumération comme celle-ci:

public enum MyLovelyEnum
{
  FirstSelection,
  TheOtherSelection,
  YetAnotherOne
};

J'ai une propriété dans mon DataContext:

public MyLovelyEnum VeryLovelyEnum { get; set; }

Et j'ai trois RadioButtons dans mon client WPF.

<RadioButton Margin="3">First Selection</RadioButton>
<RadioButton Margin="3">The Other Selection</RadioButton>
<RadioButton Margin="3">Yet Another one</RadioButton>

Maintenant, comment lier les RadioButtons à la propriété pour une liaison bidirectionnelle correcte?


Basé sur le EnumToBooleanConverter de Scott. J'ai remarqué que la méthode ConvertBack ne fonctionne pas sur le code Enum with flags.

J'ai essayé le code suivant:

public class EnumHasFlagToBooleanConverter : IValueConverter
    {
        private object _obj;
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            _obj = value;
            return ((Enum)value).HasFlag((Enum)parameter);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value.Equals(true))
            {
                if (((Enum)_obj).HasFlag((Enum)parameter))
                {
                    // Do nothing
                    return Binding.DoNothing;
                }
                else
                {
                    int i = (int)_obj;
                    int ii = (int)parameter;
                    int newInt = i+ii;
                    return (NavigationProjectDates)newInt;
                }
            }
            else
            {
                if (((Enum)_obj).HasFlag((Enum)parameter))
                {
                    int i = (int)_obj;
                    int ii = (int)parameter;
                    int newInt = i-ii;
                    return (NavigationProjectDates)newInt;

                }
                else
                {
                    // do nothing
                    return Binding.DoNothing;
                }
            }
        }
    }

La seule chose que je n'arrive pas à faire est de faire une distribution de int à targetType donc je l'ai fait codé en dur à NavigationProjectDates , l'énumération que j'utilise. Et, targetType == NavigationProjectDates ...

Edit pour plus générique Flags Enum convertisseur:

    public class FlagsEnumToBooleanConverter : IValueConverter {
        private int _flags=0;
        public object Convert(object value, Type targetType, object parameter, string language) {
            if (value == null) return false;
            _flags = (int) value;
            Type t = value.GetType();
            object o = Enum.ToObject(t, parameter);
            return ((Enum)value).HasFlag((Enum)o);
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            if (value?.Equals(true) ?? false) {
                _flags = _flags | (int) parameter;
            }
            else {
                _flags = _flags & ~(int) parameter;
            }
            return _flags;
        }
    }

J'ai créé une nouvelle classe pour gérer Liaison RadioButtons et CheckBoxes à enums. Cela fonctionne pour les enums marqués (avec plusieurs sélections de cases à cocher) et les énumérations non marquées pour les cases à cocher à sélection unique ou les boutons radio. Il ne nécessite pas non plus de ValueConverters.

Cela peut sembler plus compliqué au début, cependant, une fois que vous avez copié cette classe dans votre projet, c'est fait. C'est générique, donc il peut facilement être réutilisé pour toute énumération.

public class EnumSelection<T> : INotifyPropertyChanged where T : struct, IComparable, IFormattable, IConvertible
{
  private T value; // stored value of the Enum
  private bool isFlagged; // Enum uses flags?
  private bool canDeselect; // Can be deselected? (Radio buttons cannot deselect, checkboxes can)
  private T blankValue; // what is considered the "blank" value if it can be deselected?

  public EnumSelection(T value) : this(value, false, default(T)) { }
  public EnumSelection(T value, bool canDeselect) : this(value, canDeselect, default(T)) { }
  public EnumSelection(T value, T blankValue) : this(value, true, blankValue) { }
  public EnumSelection(T value, bool canDeselect, T blankValue)
  {
    if (!typeof(T).IsEnum) throw new ArgumentException($"{nameof(T)} must be an enum type"); // I really wish there was a way to constrain generic types to enums...
    isFlagged = typeof(T).IsDefined(typeof(FlagsAttribute), false);

    this.value = value;
    this.canDeselect = canDeselect;
    this.blankValue = blankValue;
  }

  public T Value
  {
    get { return value; }
    set 
    {
      if (this.value.Equals(value)) return;
      this.value = value;
      OnPropertyChanged();
      OnPropertyChanged("Item[]"); // Notify that the indexer property has changed
    }
  }

  [IndexerName("Item")]
  public bool this[T key]
  {
    get
    {
      int iKey = (int)(object)key;
      return isFlagged ? ((int)(object)value & iKey) == iKey : value.Equals(key);
    }
    set
    {
      if (isFlagged)
      {
        int iValue = (int)(object)this.value;
        int iKey = (int)(object)key;

        if (((iValue & iKey) == iKey) == value) return;

        if (value)
          Value = (T)(object)(iValue | iKey);
        else
          Value = (T)(object)(iValue & ~iKey);
      }
      else
      {
        if (this.value.Equals(key) == value) return;
        if (!value && !canDeselect) return;

        Value = value ? key : blankValue;
      }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

  private void OnPropertyChanged([CallerMemberName] string propertyName = "")
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

Et pour l'utiliser, disons que vous avez une énumération pour exécuter une tâche manuellement ou automatiquement, et peut être programmé pour n'importe quel jour de la semaine, et quelques options optionnelles ...

public enum StartTask
{
  Manual,
  Automatic
}

[Flags()]
public enum DayOfWeek
{
  Sunday = 1 << 0,
  Monday = 1 << 1,
  Tuesday = 1 << 2,
  Wednesday = 1 << 3,
  Thursday = 1 << 4,
  Friday = 1 << 5,
  Saturday = 1 << 6
}

public enum AdditionalOptions
{
  None = 0,
  OptionA,
  OptionB
}

Maintenant, voici comment il est facile d'utiliser cette classe:

public class MyViewModel : ViewModelBase
{
  public MyViewModel()
  {
    StartUp = new EnumSelection<StartTask>(StartTask.Manual);
    Days = new EnumSelection<DayOfWeek>(default(DayOfWeek));
    Options = new EnumSelection<AdditionalOptions>(AdditionalOptions.None, true, AdditionalOptions.None);
  }

  public EnumSelection<StartTask> StartUp { get; private set; }
  public EnumSelection<DayOfWeek> Days { get; private set; }
  public EnumSelection<AdditionalOptions> Options { get; private set; }
}

Et voici comment il est facile de lier des cases à cocher et des boutons radio avec cette classe:

<StackPanel Orientation="Vertical">
  <StackPanel Orientation="Horizontal">
    <!-- Using RadioButtons for exactly 1 selection behavior -->
    <RadioButton IsChecked="{Binding StartUp[Manual]}">Manual</RadioButton>
    <RadioButton IsChecked="{Binding StartUp[Automatic]}">Automatic</RadioButton>
  </StackPanel>
  <StackPanel Orientation="Horizontal">
    <!-- Using CheckBoxes for 0 or Many selection behavior -->
    <CheckBox IsChecked="{Binding Days[Sunday]}">Sunday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Monday]}">Monday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Tuesday]}">Tuesday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Wednesday]}">Wednesday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Thursday]}">Thursday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Friday]}">Friday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Saturday]}">Saturday</CheckBox>
  </StackPanel>
  <StackPanel Orientation="Horizontal">
    <!-- Using CheckBoxes for 0 or 1 selection behavior -->
    <CheckBox IsChecked="{Binding Options[OptionA]}">Option A</CheckBox>
    <CheckBox IsChecked="{Binding Options[OptionB]}">Option B</CheckBox>
  </StackPanel>
</StackPanel>
  1. Lorsque l'interface utilisateur se charge, le bouton radio "Manuel" est sélectionné et vous pouvez modifier votre sélection entre "Manuel" ou "Automatique" mais l'un d'entre eux doit toujours être sélectionné.
  2. Chaque jour de la semaine ne sera pas coché, mais n'importe quel nombre d'entre eux peut être coché ou décoché.
  3. "Option A" et "Option B" seront toutes les deux initialement décochées. Vous pouvez cocher l'un ou l'autre, cocher l'un décoche l'autre (similaire à RadioButtons), mais maintenant vous pouvez aussi décocher les deux (ce que vous ne pouvez pas faire avec RadioButton de WPF, c'est pourquoi CheckBox est utilisé ici)


Pour UWP, ce n'est pas si simple: vous devez passer par un autre cerceau pour passer une valeur de champ en tant que paramètre.

Exemple 1

Valide pour WPF et UWP.

<MyControl>
    <MyControl.MyProperty>
        <Binding Converter="{StaticResource EnumToBooleanConverter}" Path="AnotherProperty">
            <Binding.ConverterParameter>
                <MyLibrary:MyEnum>Field</MyLibrary:MyEnum>
            </Binding.ConverterParameter>
        </MyControl>
    </MyControl.MyProperty>
</MyControl>

Exemple 2

Valide pour WPF et UWP.

...
<MyLibrary:MyEnum x:Key="MyEnumField">Field</MyLibrary:MyEnum>
...

<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={StaticResource MyEnumField}}"/>

Exemple 3

Valable uniquement pour WPF!

<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static MyLibrary:MyEnum.Field}}"/>

UWP ne supporte pas x:Static donc l' exemple 3 est hors de question; En supposant que vous allez avec l' exemple 1 , le résultat est un code plus détaillé. L'exemple 2 est légèrement meilleur, mais toujours pas idéal.

Solution

public abstract class EnumToBooleanConverter<TEnum> : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var Parameter = parameter as string;

        if (Parameter == null)
            return DependencyProperty.UnsetValue;

        if (Enum.IsDefined(typeof(TEnum), value) == false)
            return DependencyProperty.UnsetValue;

        return Enum.Parse(typeof(TEnum), Parameter).Equals(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        var Parameter = parameter as string;
        return Parameter == null ? DependencyProperty.UnsetValue : Enum.Parse(typeof(TEnum), Parameter);
    }
}

Ensuite, pour chaque type que vous souhaitez prendre en charge, définissez un convertisseur qui encadre le type enum.

public class MyEnumToBooleanConverter : EnumToBooleanConverter<MyEnum>
{
    //Nothing to do!
}

La raison pour laquelle il doit être encadré est parce qu'il n'y a apparemment aucun moyen de référencer le type dans la méthode ConvertBack ; la boxe s'en occupe. Si vous utilisez l'un des deux premiers exemples, vous pouvez simplement référencer le type de paramètre, ce qui élimine le besoin d'hériter d'une classe encadrée; si vous souhaitez tout faire en une ligne et avec le moins de verbosité possible, cette dernière solution est idéale.

L'utilisation ressemble à l' exemple 2 , mais est, en fait, moins verbeuse.

<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter=Field}"/>

L'inconvénient est que vous devez définir un convertisseur pour chaque type que vous souhaitez soutenir.


Vous pouvez encore simplifier la réponse acceptée. Au lieu de taper les énumérations comme chaînes dans xaml et de faire plus de travail dans votre convertisseur que nécessaire, vous pouvez passer explicitement la valeur enum au lieu d'une représentation chaîne, et comme CrimsonX l'a commenté, les erreurs sont levées au lieu de runtime:

ConverterParameter = {x: Statique local: YourEnumType.Enum1}

<StackPanel>
    <StackPanel.Resources>          
        <local:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />          
    </StackPanel.Resources>
    <RadioButton IsChecked="{Binding Path=YourEnumProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:YourEnumType.Enum1}}" />
    <RadioButton IsChecked="{Binding Path=YourEnumProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:YourEnumType.Enum2}}" />
</StackPanel>

Ensuite, simplifiez le convertisseur:

public class EnumToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value.Equals(parameter);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value.Equals(true) ? parameter : Binding.DoNothing;
    }
}

Remarque - Plusieurs groupes de RadioButtons dans le même conteneur (17 février 11):

Dans xaml, si les boutons radio partagent le même conteneur parent, en sélectionner un en supprimera tous les autres (même s'ils sont liés à une autre propriété). Essayez donc de garder vos RadioButton liés à une propriété commune regroupés dans leur propre conteneur comme un panneau de pile. Dans les cas où vos RadioButtons associés ne peuvent pas partager un conteneur parent unique, définissez la propriété GroupName de chaque RadioButton sur une valeur commune pour les regrouper logiquement.

Remarque - Type Enum imbriqué dans une classe (28 avril 11):

Si votre type enum est imbriqué dans une classe (plutôt que directement dans l'espace de noms), vous pouvez utiliser la syntaxe '+' pour accéder à l'énumération en XAML comme indiqué dans une réponse (non marquée) à la question Impossible de trouver type enum pour référence statique dans WPF :

ConverterParameter = {x: Statique local: YourClass + YourNestedEnumType.Enum1}

En raison de ce problème de Microsoft Connect , cependant, le concepteur dans VS2010 ne se charge plus en indiquant "Type 'local:YourClass+YourNestedEnumType' was not found." , mais le projet compile et s'exécute avec succès. Bien sûr, vous pouvez éviter ce problème si vous pouvez déplacer votre type enum directement dans l'espace de noms.

Modifier (16 décembre 2010):

Merci à anon de suggérer renvoyer Binding.DoNothing plutôt que DependencyProperty.UnsetValue.

Edit (5 avril 11):

ConvertBack simplifié if-else pour utiliser un opérateur ternaire.

Edit (27 janvier 12):

Si vous utilisez des drapeaux Enum, le convertisseur sera comme suit:

public class EnumToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((Enum)value).HasFlag((Enum)parameter);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value.Equals(true) ? parameter : Binding.DoNothing;
    }
}

Modifier (7 mai 15):

Dans le cas d'un Enum Nullable (qui n'est pas demandé dans la question, mais peut être nécessaire dans certains cas, par exemple ORM retournant null de DB ou quand il peut être logique que dans la logique du programme la valeur n'est pas fournie), n'oubliez pas d'ajouter une vérification null nulle initiale dans la méthode Convert et renvoie la valeur boolique appropriée, qui est généralement false (si vous ne voulez aucun bouton radio sélectionné), comme ci-dessous:

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null) {
            return false; // or return parameter.Equals(YourEnumType.SomeDefaultValue);
        }
        return value.Equals(parameter);
    }






radio-button