简单的 WPF RadioButton 绑定?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1317891/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 20:54:02  来源:igfitidea点击:

Simple WPF RadioButton Binding?

wpfbindingradio-button

提问by Johnathan1

What is the simplest way to bind a group of 3 radiobuttons to a property of type int for values 1, 2, or 3?

将一组 3 个单选按钮绑定到值为 1、2 或 3 的 int 类型属性的最简单方法是什么?

采纳答案by Mark A. Donohoe

Actually, using the converter like that breaks two-way binding, plus as I said above, you can't use that with enumerations either. The better way to do this is with a simple style against a ListBox, like this:

实际上,使用这样的转换器会破坏双向绑定,而且正如我上面所说,您也不能将其与枚举一起使用。更好的方法是对 ListBox 使用简单的样式,如下所示:

Note: Contrary to what DrWPF.com stated in their example, do notput the ContentPresenter inside the RadioButton or else if you add an item with content such as a button or something else, you will not be able to set focus or interact with it. This technique solves that. Also, you need to handle the graying of the text as well as removing of margins on labels or else it will not render correctly. This style handles both for you as well.

注:相反的是DrWPF.com在他们的例子说明,也没有把ContentPresenter的单选按钮内,否则,如果你的内容添加项目,如按钮或别的东西,你将无法设定对焦或相互作用与它. 这项技术解决了这个问题。此外,您需要处理文本变灰以及去除标签上的边距,否则将无法正确呈现。这种风格也适合你。

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

You now have the look and feel of radio buttons, but you can do two-way binding, and you can use an enumeration. Here's how...

您现在拥有单选按钮的外观和感觉,但您可以进行双向绑定,并且可以使用枚举。就是这样...

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

Also, since we explicitly separated out the style that tragets the ListBoxItem rather than putting it inline, again as the other examples have shown, you can now create a new style off of it to customize things on a per-item basis such as spacing. (This will not work if you simply try to target ListBoxItem as the keyed style overrides generic control targets.)

此外,由于我们明确地分离出标记 ListBoxItem 的样式而不是将其内联,正如其他示例所示,您现在可以创建一个新样式,以基于每个项目自定义内容,例如间距。(如果您只是尝试以 ListBoxItem 为目标,因为键控样式覆盖了通用控件目标,这将不起作用。)

Here's an example of putting a margin of 6 above and below each item. (Note how you have to explicitly apply the style via the ItemContainerStyle property and not simply targeting ListBoxItem in the ListBox's resource section for the reason stated above.)

这是在每个项目的上方和下方放置 6 个边距的示例。(请注意,出于上述原因,您必须如何通过 ItemContainerStyle 属性显式应用样式,而不是简单地针对 ListBox 的资源部分中的 ListBoxItem。)

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

回答by Johnathan1

I came up with a simple solution.

我想出了一个简单的解决方案。

I have a model.cs class with:

我有一个 model.cs 类:

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

I have Window1.xaml.cs file with DataContext set to model.cs. The xaml contains the radiobuttons:

我有 Window1.xaml.cs 文件,DataContext 设置为 model.cs。xaml 包含单选按钮:

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

Here is my converter:

这是我的转换器:

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

And of course, in Window1's resources:

当然,在 Window1 的资源中:

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

回答by wondra

I am very suprised nobody came up with this kind of solution to bind it against bool array. It might not be the cleanest, but it can be used very easily:

我很惊讶没有人想出这种解决方案来将它绑定到 bool 数组。它可能不是最干净的,但它可以很容易地使用:

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

in XAML:

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

NOTE: you dont need two-way binding if you dont want to one checked by default. TwoWay binding is the biggest cons of this solution.

注意:如果您不想默认选中一个,则不需要双向绑定。双向绑定是此解决方案的最大缺点。

Pros:

优点:

  • No need for code behind
  • No need for extra class (IValue Converter)
  • No Need for extra enums
  • doesnt require bizzare binding
  • straightforward and easy to understand
  • doesnt violate MVVM (heh, at least I hope so)
  • 不需要背后的代码
  • 不需要额外的类(IValue 转换器)
  • 不需要额外的枚举
  • 不需要奇怪的绑定
  • 简单易懂
  • 不违反 MVVM(呵呵,至少我希望如此)

回答by Aviad P.

I know it's way way overdue, but I have an alternative solution, which is lighter and simpler. Derive a class from System.Windows.Controls.RadioButtonand declare two dependency properties RadioValueand RadioBinding. Then in the class code, override OnCheckedand set the RadioBindingproperty value to that of the RadioValueproperty value. In the other direction, trap changes to the RadioBindingproperty using a callback, and if the new value is equal to the value of the RadioValueproperty, set its IsCheckedproperty to true.

我知道它已经过期了,但我有一个替代解决方案,它更轻、更简单。派生一个类System.Windows.Controls.RadioButton并声明两个依赖属性RadioValueRadioBinding。然后在类代码中,覆盖OnChecked并将RadioBinding属性值设置为属性值的RadioValue值。在另一个方向,RadioBinding使用回调捕获对属性的更改,如果新值等于RadioValue属性的值,则将其IsChecked属性设置为true

Here's the 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);
    }
}

XAML usage:

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

Hope someone finds this useful after all this time :)

希望有人在这么长时间后发现这很有用:)

回答by Martin

This example might be seem a bit lengthy, but its intention should be quite clear.

这个例子可能看起来有点冗长,但它的意图应该很清楚。

It uses 3 Boolean properties in the ViewModel called, FlagForValue1, FlagForValue2and FlagForValue3. Each of these 3 properties is backed by a single private field called _intValue.

它在 ViewModel 中使用了 3 个布尔属性,称为FlagForValue1FlagForValue2FlagForValue3。这 3 个属性中的每一个都由一个名为_intValue.

The 3 Radio buttons of the view (xaml) are each bound to its corresponding Flag property in the view model. This means the radio button displaying "Value 1" is bound to the FlagForValue1bool property in the view model and the other two accordingly.

视图 (xaml) 的 3 个单选按钮都绑定到视图模型中对应的 Flag 属性。这意味着显示“值 1”的单选按钮绑定到FlagForValue1视图模型中的bool 属性,其他两个相应地。

When setting one of the properties in the view model (e.g. FlagForValue1), its important to also raise property changed events for the other two properties (e.g. FlagForValue2, and FlagForValue3) so the UI (WPF INotifyPropertyChangedinfrastructure) can selected / deselect each radio button correctly.

在视图模型中设置其中一个属性(例如FlagForValue1)时,重要的是还为其他两个属性(例如FlagForValue2, 和FlagForValue3)引发属性更改事件,以便 UI(WPFINotifyPropertyChanged基础结构)可以正确选择/取消选择每个单选按钮。

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

The xaml looks like this:

xaml 看起来像这样:

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

回答by Mauro Sampietro

Sometimes it is possible to solve it in the model like this: Suppose you have 3 boolean properties OptionA, OptionB, OptionC.

有时可以像这样在模型中解决它:假设您有 3 个布尔属性 OptionA、OptionB、OptionC。

XAML:

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

You get the idea. Not the cleanest thing, but easy.

你明白了。不是最干净的东西,但很容易。

回答by ghord

I've come up with solution using Binding.DoNothingreturned from converter which doesn't break two-way binding.

我想出了使用Binding.DoNothing从转换器返回的解决方案,该解决方案不会破坏双向绑定。

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

Radio button bindings:

单选按钮绑定:

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

回答by bigworld12

I created an attached property based on Aviad's Answer which doesn't require creating a new class

我根据 Aviad 的答案创建了一个附加属性,不需要创建新类

public static class RadioButtonHelper
{
    [AttachedPropertyBrowsableForType(typeof(RadioButton))]
    public static object GetRadioValue(DependencyObject obj) => obj.GetValue(RadioValueProperty);
    public static void SetRadioValue(DependencyObject obj, object value) => obj.SetValue(RadioValueProperty, value);
    public static readonly DependencyProperty RadioValueProperty =
        DependencyProperty.RegisterAttached("RadioValue", typeof(object), typeof(RadioButtonHelper), new PropertyMetadata(new PropertyChangedCallback(OnRadioValueChanged)));

    private static void OnRadioValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is RadioButton rb)
        {
            rb.Checked -= OnChecked;
            rb.Checked += OnChecked;
        }
    }

    public static void OnChecked(object sender, RoutedEventArgs e)
    {
        if (sender is RadioButton rb)
        {
            rb.SetCurrentValue(RadioBindingProperty, rb.GetValue(RadioValueProperty));
        }
    }

    [AttachedPropertyBrowsableForType(typeof(RadioButton))]
    public static object GetRadioBinding(DependencyObject obj) => obj.GetValue(RadioBindingProperty);
    public static void SetRadioBinding(DependencyObject obj, object value) => obj.SetValue(RadioBindingProperty, value);

    public static readonly DependencyProperty RadioBindingProperty =
        DependencyProperty.RegisterAttached("RadioBinding", typeof(object), typeof(RadioButtonHelper), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnRadioBindingChanged)));

    private static void OnRadioBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is RadioButton rb && rb.GetValue(RadioValueProperty).Equals(e.NewValue))
        {
            rb.SetCurrentValue(RadioButton.IsCheckedProperty, true);
        }
    }
}

usage :

用法 :

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