Question

J'ai une liste d'options dans un objet de données et je souhaite créer l'équivalent d'une liste de boutons radio pour permettre à l'utilisateur de sélectionner une et une seule d'entre elles. Fonctionnalité similaire à une zone de liste déroulante databound, mais au format bouton radio.

Silly moi, je pensais que cela serait intégré, mais non. Comment faites-vous cela?

Était-ce utile?

La solution

En gros, après avoir examiné les résultats de Google, j'ai commencé par les informations de un fil de discussion MSDN où Dr. WPF a fourni une réponse , qui explique que le style d'un ListBox doit être correct. Toutefois, lorsque la zone de liste est désactivée, l’arrière-plan était une couleur gênante dont je ne pouvais me débarrasser à jamais, jusqu’à ce que je lise l'exemple MSDN du ListBox ControlTemplate , qui montre l'élément de bordure secret qui coupait mon arrière-plan.

Donc, la réponse finale ici était ce style:

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

Qui fournit un ControlTemplate pour, et des styles, le ListBox et les éléments. Et cela s’utilise comme ceci:

<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}"/>

Plus je passe de temps avec WPF, plus je pense que cela rend le trivial incroyablement difficile et le trivial incroyablement difficile. Prendre plaisir. -Scott

Autres conseils

Liez la zone de liste à la propriété ItemsSource d'un ListBox avec une liste d'objets ayant une propriété Nom (cela peut changer)

<ListBox Name="RadioButtonList">
   <ListBox.ItemTemplate >
        <DataTemplate >
             <RadioButton GroupName="radioList" Tag="{Binding}" Content="{Binding Name}"/>
         </DataTemplate>
                                                    </ListBox.ItemTemplate>
                                                </ListBox>

important Nom du groupe = " radioList & ";

Je l'ai fait via un ValueConverter qui convertit un enum en un bool. En transmettant la valeur d’énumération que votre bouton radio représente en tant que ConverterParameter, le convertisseur indique si ce bouton radio doit être coché ou non.

<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 est défini comme suit:

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

Notez que je ne crée pas de données dans la liste des énumérations pour générer les boutons radio, je les ai faites à la main. Si vous souhaitez remplir la liste des boutons radio via une liaison, je pense que vous devrez modifier la liaison IsChecked en une MultiBinding liaison à la fois à la valeur actuelle et à la valeur enum de la radio, car vous ne pouvez pas utiliser de fonction. la liaison sur <=>.

Super simple, compatible MVVM, exploitant DataTemplates pour les types. 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>

Voir le modèle, 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 vous emballez votre option dans un type spécifique (ou probablement déjà). Vous pouvez simplement définir un DataTemplate pour ce type, WPF l'utilisera automatiquement. (Définissez DataTemplate dans les ressources ListBox pour limiter la portée de l'application du DataTemplate).

Utilisez également le nom du groupe dans le DataTemplate pour définir le groupe si vous le souhaitez.

C’est beaucoup plus simple que de changer le modèle de contrôle, mais cela signifie que vous obtenez une ligne bleue sur les éléments sélectionnés. (Encore une fois, rien de style ne peut réparer).

WPF est simple quand vous savez comment faire.

Désolé, j'aimerais ajouter cette réponse au message de Scott O sous forme de commentaire, mais je n'ai pas encore la réputation de le faire. J'aimais beaucoup sa réponse, car il ne s'agissait que d'une solution de style et ne nécessitait donc pas de code-behind ni de création de contrôle personnalisé, etc.

Cependant, j’ai eu un problème lorsque j’ai ensuite essayé d’utiliser des contrôles dans ListBoxItems. Lorsque j'utilise ce style, je ne parviens pas à focaliser les contrôles contenus à cause de cette ligne:

<RadioButton Focusable="False" IsHitTestVisible="False" IsChecked="{TemplateBinding IsSelected}">

Le bouton radio doit désactiver Focusable et IsHitTestVisible pour que la liaison IsChecked fonctionne correctement. Pour contourner ce problème, j'ai remplacé IsChecked de TemplateBinding par une liaison normale, ce qui m'a permis d'en faire une liaison à double sens. Supprimer les paramètres incriminés m’a donné cette ligne:

<RadioButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected, Mode=TwoWay}">

Ce qui me permet maintenant de focaliser les contrôles contenus dans ListBoxItems comme prévu.

J'espère que cela vous aidera.

Je me suis inspiré du du blog de Jon Benson entrée , mais a modifié sa solution pour utiliser des énumérations ayant un attribut de description. Les éléments clés de la solution sont donc devenus:

Enumérateur avec descriptions

public enum AgeRange {
  [Description("0 - 18 years")]
  Youth,
  [Description("18 - 65 years")]
  Adult,
  [Description("65+ years")]
  Senior,
}

Code permettant de lire les descriptions et de renvoyer les paires clé / valeur pour la liaison.

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

Votre fournisseur de données d'objet en XAML

<ObjectDataProvider
    ObjectType="{x:Type local:EnumHelper}"
    MethodName="GetEnumValueDescriptionPairs"
    x:Key="AgeRanges">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="local:AgeRange" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

Votre 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>

Propriété (par exemple, dans votre modèle de vue) à laquelle vous vous associez

public class YourViewModel : INotifyPropertyChanged
{
  private AgeRange _selectedAgeRange;
  public AgeRange SelectedAgeRange
  {
    get { return _selectedAgeRange; }
    set 
    {
      if (value != _selectedAgeRange)
      {
        _selectedAgeRange = value;
        OnPropertyChanged("SelectedAgeRange");
      }
    }
  }
}

j'ai triché:

Ma solution a été de relier la zone de liste par programmation, car c'est tout ce qui semblait fonctionner pour moi:

            if (mUdData.Telephony.PhoneLst != null)
            {
                lbPhone.ItemsSource = mUdData.Telephony.PhoneLst;
                lbPhone.SelectedValuePath = "ID";
                lbPhone.SelectedValue = mUdData.Telephony.PrimaryFaxID;
            }

Le code XAML ressemble à ceci:

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

Et dans mon cas, lire la valeur du bouton radio tel qu'il est sélectionné ressemble à ceci:

    private void rbPhone_Checked(object sender, RoutedEventArgs e)
    {
        DataRowView dvFromControl = null;
        dvFromControl = (DataRowView)((RadioButton)sender).DataContext;

        BindData.Telephony.PrimaryPhoneID = (int)dvFromControl["ID"];

    }

L’espoir qui aide quelqu'un.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top