web-dev-qa-db-fra.com

Liaison simple de WPF RadioButton?

Quel est le moyen le plus simple de lier un groupe de 3 boutons radio à une propriété de type int pour les valeurs 1, 2 ou 3?

48
Johnathan1

En fait, utiliser un convertisseur comme celui-ci rompt la liaison bidirectionnelle, et comme je l'ai dit plus haut, vous ne pouvez pas l'utiliser non plus avec des énumérations. La meilleure façon de faire est d'utiliser un style simple par rapport à un contrôle ListBox, comme ceci:

Remarque: contrairement à ce que DrWPF.com a déclaré dans son exemple, placez not ContentPresenter dans le RadioButton ou si vous ajoutez un élément avec un contenu tel qu'un bouton ou autre, vous ne pourrez pas mettre l'accent ou interagir avec elle. Cette technique résout cela. En outre, vous devez gérer le grisonnement du texte ainsi que la suppression des marges sur les étiquettes, sinon le rendu ne sera pas correct. Ce style gère les deux pour vous aussi.

<Style x:Key="RadioButtonListItem" TargetType="{x:Type ListBoxItem}" >

    <Setter Property="Template">
        <Setter.Value>

            <ControlTemplate TargetType="ListBoxItem">

                <DockPanel LastChildFill="True" Background="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Center" >

                    <RadioButton IsChecked="{TemplateBinding IsSelected}" Focusable="False" IsHitTestVisible="False" VerticalAlignment="Center" Margin="0,0,4,0" />

                    <ContentPresenter
                        Content             = "{TemplateBinding ContentControl.Content}"
                        ContentTemplate     = "{TemplateBinding ContentControl.ContentTemplate}"
                        ContentStringFormat = "{TemplateBinding ContentControl.ContentStringFormat}"
                        HorizontalAlignment = "{TemplateBinding Control.HorizontalContentAlignment}"
                        VerticalAlignment   = "{TemplateBinding Control.VerticalContentAlignment}"
                        SnapsToDevicePixels = "{TemplateBinding UIElement.SnapsToDevicePixels}" />

                </DockPanel>

            </ControlTemplate>

        </Setter.Value>

    </Setter>

</Style>

<Style x:Key="RadioButtonList" TargetType="ListBox">

    <Style.Resources>
        <Style TargetType="Label">
            <Setter Property="Padding" Value="0" />
        </Style>
    </Style.Resources>

    <Setter Property="BorderThickness" Value="0" />
    <Setter Property="Background"      Value="Transparent" />

    <Setter Property="ItemContainerStyle" Value="{StaticResource RadioButtonListItem}" />

    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBox}">
                <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>

    <Style.Triggers>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="TextBlock.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
        </Trigger>
    </Style.Triggers>

</Style>

<Style x:Key="HorizontalRadioButtonList" BasedOn="{StaticResource RadioButtonList}" TargetType="ListBox">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel Background="Transparent" Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>

Vous avez maintenant l'aspect des boutons radio, mais vous pouvez effectuer une liaison bidirectionnelle et vous pouvez utiliser une énumération. Voici comment...

<ListBox Style="{StaticResource RadioButtonList}"
    SelectedValue="{Binding SomeVal}"
    SelectedValuePath="Tag">

    <ListBoxItem Tag="{x:Static l:MyEnum.SomeOption}"     >Some option</ListBoxItem>
    <ListBoxItem Tag="{x:Static l:MyEnum.SomeOtherOption}">Some other option</ListBoxItem>
    <ListBoxItem Tag="{x:Static l:MyEnum.YetAnother}"     >Yet another option</ListBoxItem>

</ListBox>

De plus, comme nous avons explicitement séparé le style qui gêne le ListBoxItem plutôt que de le mettre en ligne, comme le montrent les autres exemples, vous pouvez maintenant créer un nouveau style pour personnaliser les éléments par élément, tels que l'espacement. (Cela ne fonctionnera pas si vous essayez simplement de cibler ListBoxItem car le style à clé remplace les cibles de contrôle génériques.)

Voici un exemple de mettre une marge de 6 au-dessus et au-dessous de chaque élément. (Notez que vous devez appliquer explicitement le style via la propriété ItemContainerStyle et ne pas simplement cibler ListBoxItem dans la section des ressources de ListBox pour la raison indiquée ci-dessus.)

<Window.Resources>
    <Style x:Key="SpacedRadioButtonListItem" TargetType="ListBoxItem" BasedOn="{StaticResource RadioButtonListItem}">
        <Setter Property="Margin" Value="0,6" />
    </Style>
</Window.Resources>

<ListBox Style="{StaticResource RadioButtonList}"
    ItemContainerStyle="{StaticResource SpacedRadioButtonListItem}"
    SelectedValue="{Binding SomeVal}"
    SelectedValuePath="Tag">

    <ListBoxItem Tag="{x:Static l:MyEnum.SomeOption}"     >Some option</ListBoxItem>
    <ListBoxItem Tag="{x:Static l:MyEnum.SomeOtherOption}">Some other option</ListBoxItem>
    <ListBoxItem Tag="{x:Static l:MyEnum.YetAnother}"     >Ter another option</ListBoxItem>

</ListBox>
32
MarqueIV

Je suis venu avec une solution simple.

J'ai une classe model.cs avec:

private int _isSuccess;
public int IsSuccess { get { return _isSuccess; } set { _isSuccess = value; } }

J'ai le fichier Window1.xaml.cs avec DataContext défini sur model.cs. Le xaml contient les radiobuttons:

<RadioButton IsChecked="{Binding Path=IsSuccess, Converter={StaticResource radioBoolToIntConverter}, ConverterParameter=1}" Content="one" />
<RadioButton IsChecked="{Binding Path=IsSuccess, Converter={StaticResource radioBoolToIntConverter}, ConverterParameter=2}" Content="two" />
<RadioButton IsChecked="{Binding Path=IsSuccess, Converter={StaticResource radioBoolToIntConverter}, ConverterParameter=3}" Content="three" />

Voici mon convertisseur:

public class RadioBoolToIntConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int integer = (int)value;
        if (integer==int.Parse(parameter.ToString()))
            return true;
        else
            return false;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return parameter;
    }
}

Et bien sûr, dans les ressources de Window1:

<Window.Resources>
    <local:RadioBoolToIntConverter x:Key="radioBoolToIntConverter" />
</Window.Resources>
78
Johnathan1

Je suis très surpris que personne ne propose ce type de solution pour le lier contre bool array. Ce n’est peut-être pas le plus propre, mais il peut être utilisé très facilement: 

private bool[] _modeArray = new bool[] { true, false, false};
public bool[] ModeArray
{
    get { return _modeArray ; }
}
public int SelectedMode
{
    get { return Array.IndexOf(_modeArray, true); }
}

en XAML:

<RadioButton GroupName="Mode" IsChecked="{Binding Path=ModeArray[0], Mode=TwoWay}"/>
<RadioButton GroupName="Mode" IsChecked="{Binding Path=ModeArray[1], Mode=TwoWay}"/>
<RadioButton GroupName="Mode" IsChecked="{Binding Path=ModeArray[2], Mode=TwoWay}"/>

REMARQUE: vous n'avez pas besoin de liaison bidirectionnelle si vous ne voulez pas une cochée par défaut. La liaison à deux voies est le principal inconvénient de cette solution.

Avantages:

  • Pas besoin de code derrière
  • Pas besoin de cours supplémentaires (IValue Converter)
  • Pas besoin d'énums supplémentaires
  • ne nécessite pas de liaison bizzare
  • simple et facile à comprendre
  • ne viole pas MVVM (hé, au moins je l'espère)
23
wondra

Je sais que c'est très tardif, mais j'ai une solution alternative, plus légère et plus simple. Dérivez une classe de System.Windows.Controls.RadioButton et déclarez deux propriétés de dépendance RadioValue et RadioBinding. Ensuite, dans le code de classe, remplacez OnChecked et définissez la valeur de la propriété RadioBinding sur celle de la valeur de la propriété RadioValue. Dans l'autre sens, Trap modifie la propriété RadioBinding à l'aide d'un rappel et si la nouvelle valeur est égale à la valeur de la propriété RadioValue, définissez sa propriété IsChecked sur true.

Voici le code:

public class MyRadioButton : RadioButton
{
    public object RadioValue
    {
        get { return (object)GetValue(RadioValueProperty); }
        set { SetValue(RadioValueProperty, value); }
    }

    // Using a DependencyProperty as the backing store for RadioValue.
       This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RadioValueProperty =
        DependencyProperty.Register(
            "RadioValue", 
            typeof(object), 
            typeof(MyRadioButton), 
            new UIPropertyMetadata(null));

    public object RadioBinding
    {
        get { return (object)GetValue(RadioBindingProperty); }
        set { SetValue(RadioBindingProperty, value); }
    }

    // Using a DependencyProperty as the backing store for RadioBinding.
       This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RadioBindingProperty =
        DependencyProperty.Register(
            "RadioBinding", 
            typeof(object), 
            typeof(MyRadioButton), 
            new FrameworkPropertyMetadata(
                null, 
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 
                OnRadioBindingChanged));

    private static void OnRadioBindingChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        MyRadioButton rb = (MyRadioButton)d;
        if (rb.RadioValue.Equals(e.NewValue))
            rb.SetCurrentValue(RadioButton.IsCheckedProperty, true);
    }

    protected override void OnChecked(RoutedEventArgs e)
    {
        base.OnChecked(e);
        SetCurrentValue(RadioBindingProperty, RadioValue);
    }
}

Utilisation de XAML:

<my:MyRadioButton GroupName="grp1" Content="Value 1"
    RadioValue="val1" RadioBinding="{Binding SelectedValue}"/>
<my:MyRadioButton GroupName="grp1" Content="Value 2"
    RadioValue="val2" RadioBinding="{Binding SelectedValue}"/>
<my:MyRadioButton GroupName="grp1" Content="Value 3"
    RadioValue="val3" RadioBinding="{Binding SelectedValue}"/>
<my:MyRadioButton GroupName="grp1" Content="Value 4"
    RadioValue="val4" RadioBinding="{Binding SelectedValue}"/>

J'espère que quelqu'un trouvera cela utile après tout ce temps :)

15
Aviad P.

Parfois, il est possible de le résoudre dans le modèle comme ceci: Supposons que vous ayez 3 propriétés booléennes OptionA, OptionB, OptionC.

XAML:

<RadioButton IsChecked="{Binding OptionA}"/>
<RadioButton IsChecked="{Binding OptionB}"/>
<RadioButton IsChecked="{Binding OptionC}"/>

CODE:

private bool _optionA;
public bool OptionA
{
    get { return _optionA; }
    set
    {
        _optionA = value;
        if( _optionA )
        {
             this.OptionB= false;
             this.OptionC = false;
        }
    }
}

private bool _optionB;
public bool OptionB
{
    get { return _optionB; }
    set
    {
        _optionB = value;
        if( _optionB )
        {
            this.OptionA= false;
            this.OptionC = false;
        }
    }
}

private bool _optionC;
public bool OptionC
{
    get { return _optionC; }
    set
    {
        _optionC = value;
        if( _optionC )
        {
            this.OptionA= false;
            this.OptionB = false;
        }
    }
}

Vous avez l’idée …… Pas la chose la plus propre, mais facile.

1
Mauro Sampietro

Cet exemple peut sembler un peu long, mais son intention devrait être assez claire.

Il utilise 3 propriétés booléennes dans le ViewModel appelé, FlagForValue1, FlagForValue2 et FlagForValue3. Chacune de ces 3 propriétés est sauvegardée par un seul champ privé appelé _intValue.

Les 3 boutons radio de la vue (xaml) sont chacun liés à la propriété Flag correspondante dans le modèle de vue. Cela signifie que le bouton radio affichant "Valeur 1" est lié à la propriété FlagForValue1 bool dans le modèle de vue et aux deux autres en conséquence.

Lors de la définition d'une des propriétés dans le modèle d'affichage (par exemple, FlagForValue1), il est important de générer également des événements de propriétés modifiées pour les deux autres propriétés (par exemple, FlagForValue2 et FlagForValue3) afin que l'interface utilisateur (infrastructure WPF INotifyPropertyChanged) puisse sélectionner/désélectionner chaque bouton radio. correctement. 

    private int _intValue;

    public bool FlagForValue1
    {
        get
        {
            return (_intValue == 1) ? true : false;
        }
        set
        {
            _intValue = 1;
            RaisePropertyChanged("FlagForValue1");
            RaisePropertyChanged("FlagForValue2");
            RaisePropertyChanged("FlagForValue3");
        }
    }

    public bool FlagForValue2
    {
        get
        {
            return (_intValue == 2) ? true : false;
        }
        set
        {
            _intValue = 2;
            RaisePropertyChanged("FlagForValue1");
            RaisePropertyChanged("FlagForValue2");
            RaisePropertyChanged("FlagForValue3");
        }
    }

    public bool FlagForValue3
    {
        get
        {
            return (_intValue == 3) ? true : false;
        }
        set
        {
            _intValue = 3;
            RaisePropertyChanged("FlagForValue1");
            RaisePropertyChanged("FlagForValue2");
            RaisePropertyChanged("FlagForValue3");
        }
    }

Le xaml ressemble à ceci:

                <RadioButton GroupName="Search" IsChecked="{Binding Path=FlagForValue1, Mode=TwoWay}"
                             >Value 1</RadioButton>

                <RadioButton GroupName="Search" IsChecked="{Binding Path=FlagForValue2, Mode=TwoWay}"
                             >Value 2</RadioButton>

                <RadioButton GroupName="Search" IsChecked="{Binding Path=FlagForValue3, Mode=TwoWay}"
                             >Value 3</RadioButton>
0
Martin

J'ai trouvé une solution en utilisant Binding.DoNothing renvoyé par convertisseur qui ne rompt pas la liaison bidirectionnelle.

public class EnumToCheckedConverter : IValueConverter
{
    public Type Type { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value.GetType() == Type)
        {
            try
            {
                var parameterFlag = Enum.Parse(Type, parameter as string);

                if (Equals(parameterFlag, value))
                {
                    return true;
                }
            }
            catch (ArgumentNullException)
            {
                return false;
            }
            catch (ArgumentException)
            {
                throw new NotSupportedException();
            }

            return false;
        }
        else if (value == null)
        {
            return false;
        }

        throw new NotSupportedException();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value is bool check)
        {
            if (check)
            {
                try
                {
                    return Enum.Parse(Type, parameter as string);
                }
                catch(ArgumentNullException)
                {
                    return Binding.DoNothing;
                }
                catch(ArgumentException)
                {
                    return Binding.DoNothing;
                }
            }

            return Binding.DoNothing;
        }

        throw new NotSupportedException();
    }
}

Usage:

<converters:EnumToCheckedConverter x:Key="SourceConverter" Type="{x:Type monitor:VariableValueSource}" />

Liaisons de boutons radio:

<RadioButton GroupName="ValueSource" 
             IsChecked="{Binding Source, Converter={StaticResource SourceConverter}, ConverterParameter=Function}">Function</RadioButton>
0
ghord