Comment lier des RadioButtons à un enum?
j'ai un enum comme ceci:
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?
9 réponses
vous pourriez utiliser un convertisseur plus générique
public class EnumBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return DependencyProperty.UnsetValue;
object parameterValue = Enum.Parse(value.GetType(), parameterString);
return parameterValue.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
return Enum.Parse(targetType, parameterString);
}
#endregion
}
et dans la partie XAML vous utilisez:
<Grid>
<Grid.Resources>
<l:EnumBooleanConverter x:Key="enumBooleanConverter" />
</Grid.Resources>
<StackPanel >
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>
</StackPanel>
</Grid>
vous pouvez simplifier davantage la réponse acceptée. Au lieu de taper les enums comme chaînes dans xaml et de faire plus de travail dans votre convertisseur que nécessaire, vous pouvez explicitement passer dans la valeur enum au lieu d'une représentation de chaîne, et comme le commente CrimsonX, les erreurs sont lancées à l'Heure de compilation plutôt qu'à l'exécution:
ConverterParameter={x:Static local:YourEnumType.Enumer1}
<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>
puis simplifier la 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;
}
}
Note - Plusieurs groupes de composants radiobutton dans le même conteneur (Fév 17 '11):
Dans xaml, si les boutons radio partagent le même conteneur parent, la sélection de l'un désélectionnera tous les autres à l'intérieur de ce conteneur (même s'ils sont liés à une propriété différente). Alors essayez de garder vos radiobuttons qui sont liés à une propriété commune groupés ensemble dans leur propre conteneur comme un panneau de pile. Dans les cas où votre les RadioButtons apparentés ne peuvent pas partager un conteneur parent simple, puis définir la propriété GroupName de chaque RadioButton à une valeur commune pour les grouper logiquement.
Note - type Enum imbriqué dans une classe (Apr 28 '11):
Si votre type enum est imbriqué dans une classe (plutôt que directement dans l'Espace-nom), vous pourriez être en mesure d'utiliser la syntaxe '+' pour accéder à l'enum dans XAML comme indiqué dans une réponse (non marquée) à la question impossible de trouver le type enum pour référence statique dans WPF :ConverterParameter={x: Static local: YourClass+ YourNestedEnumType.Enum1}
en raison de cette Microsoft Connect Issue , cependant, le concepteur dans VS2010 ne chargera plus indiquant "Type 'local:YourClass+YourNestedEnumType' was not found."
, mais le projet ne compiler et fonctionner avec succès. Bien sûr, vous pouvez éviter ce problème si vous pouvez déplacer votre type enum vers l'espace de noms directement.
Edit (Dec 16 '10):
Merci à anon d'avoir suggéré le retour de la reliure.DoNothing plutotque DependencyProperty.UnsetValue.
Edit (Apr 5 '11):
ConvertBack simplifié SI-AUTREMENT d'utiliser un opérateur ternaire.
Edit (Jan 27 '12):
Si vous utilisez les options 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;
}
}
Édition (Mai 7 '15):
Dans le cas d'un Enum null (c'est-à-dire pas demandé dans la question, mais peut être nécessaire dans certains cas, par exemple ORM retournant null DE DB ou chaque fois qu'il pourrait faire sens que dans la logique du programme la valeur n'est pas fournie), se rappeler d'ajouter un contrôle null initial dans la méthode Convert et retourner la valeur de bool appropriée, qui est généralement false (si vous ne voulez pas un 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);
}
Pour le EnumToBooleanConverter répondre: Au lieu de rendre DependencyProperty.UnsetValue envisagez de rendre Binding.DoNothing pour le cas où le bouton radio valeur IsChecked devient false. Le premier indique un problème (et peut montrer à l'utilisateur un rectangle rouge ou des indicateurs de validation similaires) tandis que le second indique simplement que rien ne doit être fait, ce qui est ce qui est voulu dans ce cas.
http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.convertback.aspx http://msdn.microsoft.com/en-us/library/system.windows.data.binding.donothing.aspx
j'utiliserais les RadioButtons dans un ListBox, puis je me lierais à la valeur sélectionnée.
c'est un fil plus ancien sur ce sujet, mais l'idée de base devrait être la même: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/323d067a-efef-4c9f-8d99-fecf45522395 /
pour UWP, ce n'est pas si simple: vous devez sauter à travers un cerceau supplémentaire pour passer une valeur de champ en 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 prend pas en charge x:Static
donc Exemple 3 est hors de la question; en supposant que vous aller avec Exemple 1 , le résultat est plus détaillé de code. Exemple 2 est un peu mieux, mais pas encore 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 voulez supporter, 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 qu'il n'y a apparemment aucun moyen de faire référence au type dans la méthode ConvertBack
; la boxe s'en occupe. Si vous allez avec l'un des deux premiers exemples, vous pouvez simplement référencer le type de paramètre, en éliminant la nécessité d'hériter d'une classe boxée; si vous souhaitez le faire tout en une ligne et avec le moins de verbosité possible, la dernière solution est idéale.
ressemble à Exemple 2 , mais est, en fait, moins verbeux.
<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter=Field}"/>
L'inconvénient est que vous devez définir un convertisseur de chaque type que vous souhaitez soutenir.
ce travail pour case à cocher aussi.
public class EnumToBoolConverter:IValueConverter
{
private int val;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int intParam = (int)parameter;
val = (int)value;
return ((intParam & val) != 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
val ^= (int)parameter;
return Enum.Parse(targetType, val.ToString());
}
}
liant un seul enum à plusieurs cases à cocher.
a étendu les grandes idées ci-dessus avec la capacité de lier des boutons radio à n'importe quel type (énumération, booléen, chaîne, entier, etc.) et fourni le code de l'échantillon de travail ici:
http://www.codeproject.com/Tips/720497/Binding-Radio-Buttons-to-a-Single-Property
j'ai créé une nouvelle classe pour relier les RadioButtons et les cases à cocher aux énums. Il fonctionne pour les enums marqués (avec des sélections de cases à cocher multiples) et les enums non marqués pour les cases à cocher ou les boutons radio. Il ne nécessite pas non plus de convertisseurs de valeur.
cela peut sembler plus compliqué au début, mais une fois que vous copiez cette classe dans votre projet, c'est fait. Il est générique, donc il peut facilement être réutilisé pour n'importe quel enum.
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 comment l'utiliser, disons que vous avez un enum pour l'exécution d'une tâche manuellement ou automatiquement, et peuvent être programmées pour les jours de la semaine, et quelques options...
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>
- lorsque L'interface utilisateur est chargée, le bouton radio "manuel" seront sélectionnés et vous pouvez modifier votre sélection entre "Manuel" ou "Automatique", mais l'un d'eux doit toujours être sélectionné.
- chaque jour de la semaine sera unchecked, mais n'importe quel nombre d'entre eux peuvent être vérifiés ou unchecked.
- "Option" et "Option B" seront tous deux initialement être décochée. Vous pouvez vérifier l'une ou l'autre, vérifiant une décochez l'autre, semblable aux composants radiobutton), mais maintenant vous pouvez aussi décocher les deux d'entre eux (vous ne pouvez pas faire avec WPF RadioButton, qui est pourquoi la Case à cocher est utilisée ici)
basé sur L'Énumératobooleanconverter de Scott. J'ai remarqué que la méthode ConvertBack ne fonctionne pas sur L'Enum avec le code 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 ne peux pas obtenir pour travailler est de faire un moulage de int
à targetType
donc je l'ai fait codé en dur à NavigationProjectDates
, l'enum que j'utilise. Et, targetType == NavigationProjectDates
...
Edit pour plus de générique Flags enum converter:
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; } }