473 Stimmen

Wie bindet man RadioButtons an eine Aufzählung?

Ich habe eine Aufzählung wie diese:

public enum MyLovelyEnum
{
    FirstSelection,
    TheOtherSelection,
    YetAnotherOne
};

Ich habe eine Eigenschaft in meinem DataContext:

public MyLovelyEnum VeryLovelyEnum { get; set; }

Und ich habe drei RadioButtons in meinem WPF-Client.

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

Wie binde ich nun die RadioButtons an die Eigenschaft für eine ordnungsgemäße Zwei-Wege-Bindung?

4 Stimmen

Wenn Sie dies tun möchten, ohne einzelne RadioButtons in Ihrer XAML zu spezifizieren, würde ich eine ListBox empfehlen, die an die Enum-Werte gebunden ist, wie diese o diese und die Elementvorlage überschrieben, um RadioButtons zu verwenden, wie diese .

3voto

Nick Punkte 4146

Ich habe eine neue Klasse erstellt, um die Bindung von RadioButtons und CheckBoxen an Enums zu behandeln. Sie funktioniert für markierte Enums (mit mehreren Auswahlmöglichkeiten) und nicht markierte Enums für einfach markierte Checkboxen oder RadioButtons. Sie benötigt auch keine ValueConverters mehr.

Dies mag auf den ersten Blick etwas komplizierter aussehen, aber sobald Sie diese Klasse in Ihr Projekt kopieren, ist es erledigt. Da sie generisch ist, kann sie leicht für jede Enum wiederverwendet werden.

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

Und für die Verwendung, sagen wir, Sie haben ein enum für die Ausführung einer Aufgabe manuell oder automatisch, und kann für alle Tage der Woche geplant werden, und einige optionale Optionen...

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
}

So einfach ist es, diese Klasse zu verwenden:

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

Und hier sehen Sie, wie einfach es ist, Kontrollkästchen und Optionsfelder mit dieser Klasse zu binden:

<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. Wenn die Benutzeroberfläche geladen wird, ist das Optionsfeld "Manuell" ausgewählt. Sie können Ihre Auswahl zwischen "Manuell" und "Automatisch" ändern, aber eine der beiden Optionen muss immer ausgewählt sein.
  2. Jeder Wochentag ist nicht markiert, aber eine beliebige Anzahl von Tagen kann markiert oder nicht markiert werden.
  3. "Option A" und "Option B" sind beide zunächst nicht markiert. Sie können entweder die eine oder die andere Option ankreuzen, wenn Sie eine Option ankreuzen, wird die andere nicht angekreuzt (ähnlich wie bei RadioButtons), aber jetzt können Sie auch beide ankreuzen (was Sie mit WPFs RadioButton nicht tun können, weshalb hier CheckBox verwendet wird)

2voto

Bravo Yeung Punkte 6738

Sie können die Optionsfelder dynamisch erstellen, ListBox kann Ihnen dabei helfen, und zwar ohne Konverter, ganz einfach.

Die konkreten Schritte sind unten aufgeführt:

  • eine ListBox erstellen und die ItemsSource für die Listbox als enum festlegen MyLovelyEnum und binden das SelectedItem der ListBox an die VeryLovelyEnum Eigentum.

  • dann werden die Radiobuttons für jeden ListBoxItem erstellt.

  • Schritt 1 : Fügen Sie das Enum zu den statischen Ressourcen für Ihr Window, UserControl oder Grid etc. hinzu.

    <Window.Resources>
        <ObjectDataProvider MethodName="GetValues"
                            ObjectType="{x:Type system:Enum}"
                            x:Key="MyLovelyEnum">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:MyLovelyEnum" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
  • Schritt 2 : Verwenden Sie die Listbox und Control Template um jedes Element als Radio-Button auszufüllen

    <ListBox ItemsSource="{Binding Source={StaticResource MyLovelyEnum}}" SelectedItem="{Binding VeryLovelyEnum, Mode=TwoWay}" >
        <ListBox.Resources>
            <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <RadioButton
                                Content="{TemplateBinding ContentPresenter.Content}"
                                IsChecked="{Binding Path=IsSelected,
                                RelativeSource={RelativeSource TemplatedParent},
                                Mode=TwoWay}" />
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ListBox.Resources>
    </ListBox>

Der Vorteil ist: Wenn sich Ihre Enum-Klasse eines Tages ändert, müssen Sie die GUI (XAML-Datei) nicht aktualisieren.

Referenzen: https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/

1voto

Ali Bayat Punkte 2813

Diese Arbeit für Kontrollkästchen auch.

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

Bindung einer einzelnen Aufzählung an mehrere Kontrollkästchen.

1voto

Jeremy Bowyer Punkte 13

Eine Möglichkeit, damit umzugehen, wäre, separate bool-Eigenschaften in Ihrer ViewModel Klasse. Hier ist, wie ich mit einer solchen Situation umgegangen bin:

ViewModel:

public enum MyLovelyEnum { FirstSelection, TheOtherSelection, YetAnotherOne };
private MyLovelyEnum CurrentSelection;

public bool FirstSelectionProperty
{
    get
    {
        return CurrentSelection == MyLovelyEnum.FirstSelection;
    }
    set
    {
        if (value)
            CurrentSelection = MyLovelyEnum.FirstSelection;
    }
}

public bool TheOtherSelectionProperty
{
    get
    {
        return CurrentSelection == MyLovelyEnum.TheOtherSelection;
    }
    set
    {
        if (value)
            CurrentSelection = MyLovelyEnum.TheOtherSelection;
    }
}

public bool YetAnotherOneSelectionProperty
{
    get
    {
        return CurrentSelection == MyLovelyEnum.YetAnotherOne;
    }
    set
    {
        if (value)
            CurrentSelection = MyLovelyEnum.YetAnotherOne;
    }
}

XAML:

<RadioButton IsChecked="{Binding SimilaritySort, Mode=TwoWay}">Similarity</RadioButton>
<RadioButton IsChecked="{Binding DateInsertedSort, Mode=TwoWay}">Date Inserted</RadioButton>
<RadioButton IsChecked="{Binding DateOfQuestionSort, Mode=TwoWay}">Date of Question</RadioButton>
<RadioButton IsChecked="{Binding DateModifiedSort, Mode=TwoWay}">Date Modified</RadioButton>

Es ist nicht so robust oder dynamisch wie einige der anderen Lösungen, aber das Schöne daran ist, dass es sehr eigenständig ist und keine benutzerdefinierten Konverter oder ähnliches benötigt.

0voto

KenGey Punkte 386

Basierend auf dem EnumToBooleanConverter von Scott. Ich bemerkte, dass die ConvertBack-Methode nicht auf die Enum mit Flaggen-Code funktioniert.

Ich habe den folgenden Code ausprobiert:

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

Das Einzige, was ich nicht hinbekomme, ist ein Guss aus int a targetType also habe ich es fest codiert auf NavigationProjectDates , die von mir verwendete Aufzählung. Und, targetType == NavigationProjectDates ...


Bearbeiten für allgemeinere Flags Enum Konverter:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X