Cómo unir RadioButtons a una enumeración?


Tengo una enumeración como esta:

public enum MyLovelyEnum
{
  FirstSelection,
  TheOtherSelection,
  YetAnotherOne
};

Tengo una propiedad en mi DataContext:

public MyLovelyEnum VeryLovelyEnum { get; set; }

Y tengo tres botones de radio en mi cliente WPF.

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

Ahora, ¿cómo enlazo los botones de radio a la propiedad para un enlace bidireccional adecuado?

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

9 answers

Podría usar un convertidor más genérico

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
}

Y en la Parte XAML se usa:

<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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-09 09:30:09

Puede simplificar aún más la respuesta aceptada. En lugar de escribir las enumeraciones como cadenas en xaml y hacer más trabajo en su convertidor del necesario, puede pasar explícitamente el valor de enumeración en lugar de una representación de cadena, y como comentó CrimsonX, los errores se producen en tiempo de compilación en lugar de en tiempo de ejecución:

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>

Luego simplifique el convertidor:

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

Nota - Múltiples grupos de RadioButtons en el mismo contenedor (Feb 17 ' 11):

En xaml, si los botones de opción comparten el mismo contenedor principal, seleccionar uno anulará la selección de todos los demás dentro de ese contenedor (incluso si están vinculados a una propiedad diferente). Así que trate de mantener sus RadioButton que están vinculados a una propiedad común agrupados en su propio contenedor como un panel de pila. En los casos en que los botones de radio relacionados no puedan compartir un contenedor padre único, establezca la propiedad GroupName de cada botón de radio en un valor común para agruparlos lógicamente.

Nota - Tipo de enumeración anidado en una clase (Apr 28 ' 11):

Si su tipo de enumeración está anidado en una clase (en lugar de directamente en el espacio de nombres), es posible que pueda usar la sintaxis '+' para acceder a la enumeración en XAML como se indica en una respuesta (no marcada) a la pregunta No se puede encontrar el tipo de enumeración para referencia estática en WPF:

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

Debido a esto Problema de Microsoft Connect , sin embargo, el diseñador en VS2010 ya no cargará indicando "Type 'local:YourClass+YourNestedEnumType' was not found.", pero el proyecto compila y se ejecuta correctamente. Por supuesto, puede evitar este problema si puede mover su tipo de enumeración al espacio de nombres directamente.

Editar (Dec 16 '10):

Gracias a anon por sugerir la devolución de la encuadernación.doNothing en lugar de DependencyProperty.UnsetValue.

Editar (Abril 5 '11):

Si-else de ConvertBack simplificado para usar un Ternario Operador.

Editar (Jan 27 '12):

Si usa banderas de enumeración, el convertidor sería el siguiente:
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;
    }
}

Editar (Mayo 7 '15):

En caso de una enumeración Nullable (que es no preguntado en la pregunta, pero puede ser necesario en algunos casos, por ejemplo, OR devolviendo null desde DB o siempre que tenga sentido que en la lógica del programa no se proporcione el valor), recuerde agregar una comprobación null inicial en el Método Convert y devolver el valor bool apropiado, que es típicamente falso (si no desea que se seleccione ningún botón de opción), como a continuación:
    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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:57

Para la respuesta EnumToBooleanConverter: En lugar de devolver DependencyProperty.UnsetValue considere devolver el enlace.doNothing para el caso en el que el botón de opción IsChecked valor se convierte en false. El primero indica un problema (y podría mostrar al usuario un rectángulo rojo o indicadores de validación similares) mientras que el segundo solo indica que no se debe hacer nada, que es lo que se quiere en ese caso.

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-05 15:47:32

Usaría los botones de radio en un ListBox, y luego me enlazaría al SelectedValue.

Este es un hilo más antiguo sobre este tema, pero la idea base debería ser la misma: 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-29 11:51:53

Para UWP, no es tan simple: Debe saltar a través de un aro adicional para pasar un valor de campo como parámetro.

Ejemplo 1

Válido tanto para WPF como para UWP.

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

Ejemplo 2

Válido tanto para WPF como para UWP.

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

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

Ejemplo 3

Válido solo para WPF!

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

UWP no soporta x:Static por lo que El ejemplo 3 está fuera de discusión; suponiendo que vaya con el Ejemplo 1 , el resultado es más detallado codificar. El ejemplo 2 es un poco mejor, pero todavía no es ideal.

Solución

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

Luego, para cada tipo que desee soportar, defina un convertidor que boxee el tipo de enumeración.

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

La razón por la que debe estar en caja es porque aparentemente no hay manera de hacer referencia al tipo en el método ConvertBack; el boxeo se encarga de eso. Si va con cualquiera de los dos primeros ejemplos, solo puede hacer referencia al tipo de parámetro, eliminando la necesidad de heredar de un cuadro clase; si desea hacerlo todo en una línea y con la menor cantidad de detalle posible, esta última solución es ideal.

El uso se asemeja a El ejemplo 2, pero es, de hecho, menos detallado.

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

El inconveniente es que debe definir un convertidor para cada tipo que desee soportar.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-18 06:04:05

Este trabajo para Casilla de verificación también.

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

Enlace de una sola enumeración a varias casillas de verificación.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-02 14:06:49

Amplió las grandes ideas anteriores con la capacidad de enlazar botones de radio a cualquier tipo (enumeración, booleano, cadena, entero, etc.) y proporcionó un código de ejemplo de trabajo aquí:

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-04 07:21:53

He creado una nueva clase para manejar los botones de enlace y las casillas de verificación para enumerar. Funciona para enumeraciones marcadas (con múltiples selecciones de casillas de verificación) y enumeraciones no marcadas para casillas de verificación de selección única o botones de opción. Tampoco requiere convertidores de valor en absoluto.

Esto podría parecer más complicado al principio, sin embargo, una vez que copies esta clase en tu proyecto, está hecho. Es genérico, por lo que se puede reutilizar fácilmente para cualquier enumeración.

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

Y para cómo usarlo, digamos que tienes una enumeración para ejecutar una tarea de forma manual o automática, y se puede programar para cualquier día de la semana, y algunas opciones opcionales...

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
}

Ahora, esto es lo fácil que es usar esta clase:

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

Y esto es lo fácil que es vincular casillas de verificación y botones de opción con esta clase:

<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. Cuando se cargue la interfaz de usuario, se seleccionará el botón de opción "Manual" y puede alterar su selección entre "Manual" o "Automático", pero cualquiera de ellos siempre debe ser elegido.
  2. Todos los días de la semana no estarán marcados, pero cualquier número de ellos se pueden marcar o desmarcar.
  3. "Opción A "y" Opción B " inicialmente no estarán marcadas. Puede marcar uno u otro, marcando uno desmarcará el otro (similar a los botones de radio), pero ahora también puede desmarcar ambos (lo que no puede hacer con el botón de radio de WPF, por lo que la casilla de verificación se usa aquí)
 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-02 19:22:34

Basado en el EnumToBooleanConverter de Scott. Me di cuenta de que el método ConvertBack no funciona en la enumeración con código de banderas.

He intentado el siguiente código:

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

Lo único que no puedo conseguir para trabajar es hacer un molde de int a targetType así que lo hice hardcoded a NavigationProjectDates, la enumeración que uso. Y, targetType == NavigationProjectDates...


Editar para banderas más genéricas 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-28 04:05:11