Jak połączyć Radiobuttony z enum?

Mam takie enum:

public enum MyLovelyEnum
{
  FirstSelection,
  TheOtherSelection,
  YetAnotherOne
};

I got a property in my DataContext:

public MyLovelyEnum VeryLovelyEnum { get; set; }
I mam trzy Radiobuttony w moim kliencie WPF.
<RadioButton Margin="3">First Selection</RadioButton>
<RadioButton Margin="3">The Other Selection</RadioButton>
<RadioButton Margin="3">Yet Another one</RadioButton>

Jak teraz powiązać Radiobuttony z właściwością dla właściwego dwukierunkowego wiązania?

Author: H.B., 2008-12-29

9 answers

Możesz użyć bardziej ogólnego konwertera

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
}

I w części XAML używasz:

<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>
 343
Author: Lars,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-09 09:30:09

Możesz dodatkowo uprościć zaakceptowaną odpowiedź. Zamiast wpisywać enum jako ciągi znaków w xaml i wykonywać więcej pracy w konwerterze niż jest to konieczne, możesz jawnie przekazać wartość enum zamiast reprezentacji łańcuchowej, a jak skomentował CrimsonX, błędy są wyrzucane w czasie kompilacji, a nie w trybie runtime:

ConverterParameter={X: Static 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>

Następnie uprość konwerter:

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

Uwaga-wiele grup Radiobuttony w tym samym pojemniku (Feb 17 '11):

W xaml, jeśli przyciski radiowe mają ten sam kontener nadrzędny, to wybranie jednego spowoduje usunięcie wszystkich innych kontenerów w tym kontenerze (nawet jeśli są powiązane z inną właściwością). Więc staraj się zachować RadioButton, które są powiązane ze wspólną właściwością zgrupowane razem we własnym kontenerze, jak panel stosu. Jeśli powiązane przyciski RadioButton nie mogą współdzielić jednego kontenera nadrzędnego, ustaw właściwość GroupName każdego Radiobuttona na wspólna wartość logicznie je grupować.

Uwaga-Typ Enum zagnieżdżony w klasie (Apr 28 '11):

Jeśli twój typ enum jest zagnieżdżony w klasie (a nie bezpośrednio w przestrzeni nazw), możesz użyć składni'+', aby uzyskać dostęp do enum w XAML, jak podano w (nie zaznaczonej) odpowiedzi na pytanie nie można znaleźć typu enum dla statycznego odniesienia w WPF :

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

W związku z tym problem z Microsoft Connect , jednak projektant w VS2010 nie będzie już ładował się z informacją "Type 'local:YourClass+YourNestedEnumType' was not found.", ale projekt kompiluje się i uruchamia pomyślnie. Oczywiście możesz uniknąć tego problemu, jeśli możesz przenieść swój typ enum bezpośrednio do przestrzeni nazw.

Edit (Dec 16 '10):

Dzięki anon za sugestię zwrotu oprawy.DoNothing a nie DependencyProperty.UnsetValue.

Edit (Apr 5 '11):

Uproszczony ConvertBack ' s if-else do użycia trójnika Centrala.

Edit (Jan 27 '12):

Jeśli używasz znaczników Enum, konwerter będzie wyglądał następująco:
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;
    }
}

Edit (May 7 '15):

W przypadku wartości Nullable Enum (tzn. Nie zadanej w pytaniu, ale może być potrzebne w niektórych przypadkach, np. ORM zwracający null Z DB lub gdy w logice programu wartość nie jest podana), należy pamiętać o dodaniu początkowego sprawdzenia null W metodzie Convert i zwróceniu odpowiedniej wartości bool, tzn. zazwyczaj false (jeśli nie chcesz wybrać żadnego przycisku radiowego), jak poniżej:
    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);
    }
 512
Author: Scott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:57

Dla odpowiedzi EnumToBooleanConverter: Zamiast zwracać DependencyProperty.UnsetValue rozważ zwrócenie wiązania.DoNothing dla Przypadku, Gdy przycisk radiowy Ischected wartość staje się false. Pierwszy wskazuje problem (i może pokazać użytkownikowi czerwony prostokąt lub podobne wskaźniki walidacji) , podczas gdy drugi po prostu wskazuje, że nic nie należy robić, co jest w tym pożądane case.

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

 25
Author: anon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-05 15:47:32

Użyłbym Radiobuttonów w Listboxie, a następnie powiązał z SelectedValue.

To jest starszy wątek o tym temacie, ale podstawowa idea powinna być taka sama: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/323d067a-efef-4c9f-8d99-fecf45522395/

 5
Author: Martin Moser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-29 11:51:53

Dla UWP nie jest to takie proste: musisz przeskoczyć przez dodatkową obręcz, aby przekazać wartość pola jako parametr.

Przykład 1

Ważne zarówno dla WPF, jak i UWP.

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

Przykład 2

Ważne zarówno dla WPF, jak i UWP.

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

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

Przykład 3

Ważne tylko dla WPF!

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

UWP nie obsługuje x:Static, więc przykład 3 nie wchodzi w grę; zakładając, że wybierzesz przykład 1 , wynik jest bardziej szczegółowy kod. przykład 2 jest nieco lepszy, ale wciąż nie idealny.

Rozwiązanie

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

Następnie, dla każdego typu, który chcesz obsługiwać, zdefiniuj konwerter, który ustawia typ enum.

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

Powodem, dla którego musi być boksowany, jest to, że pozornie nie ma sposobu, aby odwołać się do typu w metodzie ConvertBack; Boks zajmuje się tym. Jeśli wybierzesz jeden z dwóch pierwszych przykładów, możesz po prostu odwołać się do typu parametru, eliminując potrzebę dziedziczenia z pudełka class; jeśli chcesz zrobić to wszystko w jednym wierszu i z najmniejszą możliwą szczegółowością, to drugie rozwiązanie jest idealne.

Użycie przypomina przykład 2 , ale w rzeczywistości jest mniej gadatliwy.

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

Minusem jest to, że musisz zdefiniować konwerter dla każdego typu, który chcesz obsługiwać.

 3
Author: James M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-18 06:04:05

To działa również dla Checkbox .

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

Powiązanie pojedynczego enum z wieloma checkboxami.

 2
Author: Ali Bayat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-02 14:06:49

Rozszerzono powyższe wspaniałe pomysły o możliwość przypisania przycisków radiowych do dowolnego typu (enumeration, Boolean, string, integer, itp.) i dostarczył działający przykładowy kod tutaj:

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

 1
Author: votrubac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-04 07:21:53

Stworzyłem nową klasę do obsługi bindowania Radiobuttonów i Checkboxów do enum. Działa to dla oznaczonych enum (z wieloma zaznaczeniami), a nie oznaczonych enum dla pól wyboru pojedynczego zaznaczenia lub przycisków opcji. Nie wymaga również ValueConverters w ogóle.

To może wyglądać na bardziej skomplikowane na początku, jednak, gdy skopiujesz tę klasę do swojego projektu, to gotowe. Jest ogólny, więc można go łatwo użyć ponownie dla dowolnego 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));
  }
}

I jak go używać, powiedzmy, że masz enum do uruchamiania zadania ręcznie lub automatycznie, i może być zaplanowane na dowolne dni tygodnia, a niektóre opcje opcjonalne...

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
}

Oto jak łatwo jest używać tej klasy:

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

A oto jak łatwo jest powiązać checkboxy i przyciski radiowe z tą klasą:

<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. po załadowaniu interfejsu zostanie wybrany przycisk radiowy "Manual" i możesz zmienić wybór między "Manual" lub "Automatic", ale każdy z nich musi być zawsze wybrane.
  2. każdy dzień tygodnia będzie niezaznaczony, ale Dowolna ich liczba może być zaznaczona lub niezaznaczona.
  3. " opcja A "i" Opcja B " będą początkowo niezaznaczone. Możesz zaznaczyć jeden lub drugi, zaznaczenie jednego spowoduje odznaczenie drugiego (podobnie jak Radiobuttony), ale teraz możesz również odznaczyć oba (czego nie możesz zrobić z Radiobuttonem WPF, dlatego pole wyboru jest tutaj używane)
 1
Author: Nick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-02 19:22:34

Na podstawie EnumToBooleanConverter od Scotta. Zauważyłem, że metoda ConvertBack nie działa na kodzie Enum with flags.

Wypróbowałem następujący kod:

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

Jedyną rzeczą, której nie mogę dostać się do pracy, jest wykonanie odlewu z int do targetType, więc zrobiłem go na twardo do NavigationProjectDates, enum, którego używam. I, targetType == NavigationProjectDates...


Edycja dla bardziej ogólnych Flag 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;
        }
    }
 0
Author: KenGey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-28 04:05:11