radiobutton - Lista de botones de radio de datos enlazados en WPF
radiobutton wpf (7)
Básicamente, después de revisar los resultados de Google, comencé con la información de un hilo de discusión de MSDN donde el Dr. WPF proporcionó una respuesta , que habla sobre el diseño de un ListBox para que se vea bien. Sin embargo, cuando el cuadro de lista está deshabilitado, el fondo era un color molesto del que no pude deshacerme de por vida, hasta que leí el ejemplo de MSDN de la placa de control ListBox , que muestra el elemento de borde secreto que estaba pateando mi fondo extremo.
Entonces, la respuesta final aquí fue este estilo:
<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
<!-- ControlTemplate taken from MSDN http://msdn.microsoft.com/en-us/library/ms754242.aspx -->
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="MinWidth" Value="120"/>
<Setter Property="MinHeight" Value="95"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border Name="Border" Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
CornerRadius="2">
<ScrollViewer Margin="0" Focusable="false">
<StackPanel Margin="2" IsItemsHost="True" />
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Border" Property="Background"
Value="Transparent" />
<Setter TargetName="Border" Property="BorderBrush"
Value="Transparent" />
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="theBorder" Background="Transparent">
<RadioButton Focusable="False" IsHitTestVisible="False"
IsChecked="{TemplateBinding IsSelected}">
<ContentPresenter />
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
Que proporciona un ControlTemplate para, y estilos, el cuadro de lista y los elementos. Y se usa así:
<ListBox Grid.Column="1" Grid.Row="0" x:Name="TurnChargeBasedOnSelector" Background="Transparent"
IsEnabled="{Binding Path=IsEditing}"
Style="{StaticResource RadioButtonList}"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainForm}}, Path=DataContext.RampTurnsBasedOnList}"
DisplayMemberPath="Description" SelectedValuePath="RampTurnsBasedOnID"
SelectedValue="{Binding Path=RampTurnsBasedOnID, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"/>
Cuanto más tiempo paso con WPF, más creo que hace que lo trivial sea increíblemente difícil y lo increíblemente difícil trivial. Disfrutar. -Scott
Tengo una lista de opciones en un objeto de datos, y quiero hacer el equivalente a una lista de botones de opción para permitir al usuario seleccionar una y solo una de ellas. Funcionalidad similar a un cuadro combinado de base de datos, pero en formato de botón de radio.
Tonto de mí, pensé que esto estaría incorporado, pero no. ¿Cómo lo haces?
He hecho esto a través de un ValueConverter
que convierte una enum
en un bool
. Al pasar el valor de enumeración que su botón de opción representa como el ConverterParameter
, el convertidor devuelve si este botón de opción se debe verificar o no.
<Window.Resources>
<Converters:EnumConverter x:Key="EnumConverter" />
</Window.Resources>
<RadioButton IsChecked="{Binding Path=MyEnum, Mode=TwoWay,
Converter={StaticResource EnumConverter},
ConverterParameter=Enum1}"}
Content="Enum 1" />
<RadioButton IsChecked="{Binding Path=MyEnum, Mode=TwoWay,
Converter={StaticResource EnumConverter},
ConverterParameter=Enum2}"}
Content="Enum 2" />
EnumConverter
se define de la siguiente manera:
public class EnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType.IsAssignableFrom(typeof(Boolean)) && targetType.IsAssignableFrom(typeof(String)))
throw new ArgumentException("EnumConverter can only convert to boolean or string.");
if (targetType == typeof(String))
return value.ToString();
return String.Compare(value.ToString(), (String)parameter, StringComparison.InvariantCultureIgnoreCase) == 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType.IsAssignableFrom(typeof(Boolean)) && targetType.IsAssignableFrom(typeof(String)))
throw new ArgumentException("EnumConverter can only convert back value from a string or a boolean.");
if (!targetType.IsEnum)
throw new ArgumentException("EnumConverter can only convert value to an Enum Type.");
if (value.GetType() == typeof(String))
{
return Enum.Parse(targetType, (String)value, true);
}
//We have a boolean, as for binding to a checkbox. we use parameter
if ((Boolean)value)
return Enum.Parse(targetType, (String)parameter, true);
return null;
}
}
Tenga en cuenta que no estoy vinculado a la lista de enumeraciones para generar los botones de opción, los he hecho a mano. Si desea completar la lista de botones de radio a través de un enlace, creo que deberá cambiar el enlace IsChecked
a un MultiBinding
que se vincula tanto al valor actual como al valor de enumeración de la radio, porque no puede usar un enlace en ConverterParameter
.
Hice trampa:
Mi solución fue enlazar el cuadro de lista programáticamente, ya que eso es todo lo que parecía funcionar para mí:
if (mUdData.Telephony.PhoneLst != null)
{
lbPhone.ItemsSource = mUdData.Telephony.PhoneLst;
lbPhone.SelectedValuePath = "ID";
lbPhone.SelectedValue = mUdData.Telephony.PrimaryFaxID;
}
El XAML se ve así:
<ListBox.ItemTemplate >
<DataTemplate >
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<RadioButton
IsChecked="{Binding Path=PrimaryPhoneID}"
GroupName="Phone"
x:Name="rbPhone"
Content="{Binding Path=PrimaryPhoneID}"
Checked="rbPhone_Checked"/>
<CheckBox Grid.Column="2" IsEnabled="False" IsChecked="{Binding Path=Active}" Content="{Binding Path=Number}" ></CheckBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
Y en mi caso, leer el valor del botón de opción tal como está seleccionado tiene el siguiente aspecto:
private void rbPhone_Checked(object sender, RoutedEventArgs e)
{
DataRowView dvFromControl = null;
dvFromControl = (DataRowView)((RadioButton)sender).DataContext;
BindData.Telephony.PrimaryPhoneID = (int)dvFromControl["ID"];
}
Espero que ayude a alguien.
Lo siento, me gustaría poner esta respuesta a la publicación de Scott O como un comentario en su publicación, pero todavía no tengo la reputación de hacerlo. Me gustó mucho su respuesta, ya que era una solución de estilo único y, por lo tanto, no requería ningún código adicional o creación de un control personalizado, etc.
Sin embargo, tuve un problema cuando intenté usar controles dentro de ListBoxItems. Cuando uso este estilo no puedo enfocar ninguno de los controles contenidos debido a esta línea:
<RadioButton Focusable="False" IsHitTestVisible="False" IsChecked="{TemplateBinding IsSelected}">
El botón de opción debe desactivar Focusable e IsHitTestVisible para que el enlace IsChecked funcione correctamente. Para solucionar esto, cambié el IsChecked de TemplateBinding a un enlace regular, lo que me permitió convertirlo en un enlace de dos vías. La eliminación de la configuración ofensiva me dio esta línea:
<RadioButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected, Mode=TwoWay}">
Lo que ahora me permite enfocar cualquier control contenido en ListBoxItems como se esperaba.
Espero que esto ayude.
Me inspiré en la entrada del blog de Jon Benson , pero modifiqué su solución para usar enumeraciones que tienen un atributo de descripción. Así que las partes clave de la solución se convirtieron en:
Enumerador con descripciones.
public enum AgeRange {
[Description("0 - 18 years")]
Youth,
[Description("18 - 65 years")]
Adult,
[Description("65+ years")]
Senior,
}
Código para leer descripciones y devolver los pares clave / valor para el enlace.
public static class EnumHelper
{
public static string ToDescriptionString(this Enum val)
{
var attribute =
(DescriptionAttribute)
val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false).
SingleOrDefault();
return attribute == default(DescriptionAttribute) ? val.ToString() : attribute.Description;
}
public static List<KeyValuePair<string,string>> GetEnumValueDescriptionPairs(Type enumType)
{
return Enum.GetValues(enumType)
.Cast<Enum>()
.Select(e => new KeyValuePair<string, string>(e.ToString(), e.ToDescriptionString()))
.ToList();
}
}
Su proveedor de datos de objetos en XAML
<ObjectDataProvider
ObjectType="{x:Type local:EnumHelper}"
MethodName="GetEnumValueDescriptionPairs"
x:Key="AgeRanges">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:AgeRange" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
Su ListBox en XAML
<ListBox
ItemsSource="{Binding Source={StaticResource AgeRanges}}"
SelectedValue="{Binding SelectedAgeRange}"
SelectedValuePath="Key">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Content="{Binding Value}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
La propiedad (por ejemplo, en su modelo de vista) a la que está vinculado
public class YourViewModel : INotifyPropertyChanged
{
private AgeRange _selectedAgeRange;
public AgeRange SelectedAgeRange
{
get { return _selectedAgeRange; }
set
{
if (value != _selectedAgeRange)
{
_selectedAgeRange = value;
OnPropertyChanged("SelectedAgeRange");
}
}
}
}
Súper simple, amigable con MVVM, aprovechando DataTemplates para los tipos. XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Option}">
<RadioButton Focusable="False"
IsHitTestVisible="False"
Content="{Binding Display}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}">
</RadioButton>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Options}" SelectedItem="{Binding SelectedOption}"/>
</Grid>
Ver modelo, etc:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Vm();
}
}
public class Vm
{
public Option[] Options { get { return new Option[] {
new Option() { Display = "A" },
new Option() { Display = "B" },
new Option() { Display = "C" } }; } }
public Option SelectedOption { get; set; }
}
public class Option
{
public string Display { get; set; }
}
Si ajusta su opción en un tipo específico (o probablemente ya lo esté). Simplemente puede configurar una plantilla de datos para ese tipo, WPF la usará automáticamente. (Defina DataTemplate en los recursos de ListBox para limitar el alcance de donde se aplicará DataTemplate).
También use el nombre del grupo en el DataTemplate para establecer el grupo si lo desea.
Esto es mucho más simple que cambiar la plantilla de control, sin embargo, significa que obtienes una línea azul en los elementos seleccionados. (De nuevo, nada que un poco de estilo no pueda arreglar).
WPF es simple cuando sabes cómo.
Vincule el cuadro de lista al ItemsSource de un ListBox con una lista de objetos que tienen un nombre de propiedad (esto puede cambiar)
<ListBox Name="RadioButtonList">
<ListBox.ItemTemplate >
<DataTemplate >
<RadioButton GroupName="radioList" Tag="{Binding}" Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
important GroupName = "radioList"