简单的 WPF 单选按钮绑定?

发布于 2024-08-02 04:53:54 字数 52 浏览 2 评论 0原文

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

情场扛把子 2024-08-09 04:53:54

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

我有一个 model.cs 类:

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

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

这是我的转换器:

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

当然,在 Window1 的资源中:

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

I came up with a simple solution.

I have a model.cs class with:

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:

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

<Window.Resources>
    <local:RadioBoolToIntConverter x:Key="radioBoolToIntConverter" />
</Window.Resources>
热血少△年 2024-08-09 04:53:54

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

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

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

注意:如果您不想默认检查一种方式,则不需要双向绑定。双向绑定是该解决方案的最大缺点。

优点:

  • 不需要后台代码
  • 不需要额外的类(IValue Converter)
  • 不需要额外的枚举
  • 不需要奇怪的绑定
  • 简单易懂
  • 不违反 MVVM(呵呵,至少我希望如此)

I am very surprised 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:

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:

<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 don't need two-way binding if you don't want to one checked by default. TwoWay binding is the biggest con of this solution.

Pros:

  • No need for code behind
  • No need for extra class (IValue Converter)
  • No need for extra enums
  • doesn't require bizarre binding
  • straightforward and easy to understand
  • doesn't violate MVVM (heh, at least I hope so)
南薇 2024-08-09 04:53:54

使用更简单的方法更新答案

请参阅在同一页面上发布的我的新答案,以获取完全不同且更简单的方法解决这个问题。这种新方法使用自定义的 IValueConverterBinding 子类,让您可以重新使用真正的 RadioButton 而不是风格繁重的 ListBox,如此处所示。

再次强调,除非您个人在使用 ListBox 子类时能获得其他好处,否则其他方法 是我现在推荐的。

原始答案

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

注意:与 DrWPF.com 在其示例中所述相反,请勿将 ContentPresenter 放入 RadioButton 内,否则如果您添加带有按钮或其他内容等内容的项目,您将无法设置焦点或与其交互。这项技术解决了这个问题。此外,您需要处理文本的灰色化以及删除标签上的边距,否则它将无法正确呈现。这种风格也可以满足您的需求。

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

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

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

另外,由于我们显式地分离出了针对 ListBoxItem 的样式,而不是将其内联,再次如其他示例所示,您现在可以从中创建一个新样式来自定义每个项目的内容间距等基础。 (如果您只是尝试以 ListBoxItem 为目标,则这将不起作用,因为键控样式会覆盖通用控制目标。)

下面是在每个项目的上方和下方放置 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>

Updated Answer with a Simpler Approach

Please see my new answer posted on this same page for a completely different, and much simpler approach to solving this issue. That new approach uses custom IValueConverter and Binding subclasses which let you go back to using a true RadioButton instead of a heavily-styled ListBox, as shown here.

Again, unless there are other benefits you'd personally gain when using a ListBox subclass, the other approach is what I now recommend.

Original Answer

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:

Note: Contrary to what DrWPF.com stated in their example, do not put 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.

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

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

<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>
同展鸳鸯锦 2024-08-09 04:53:54

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

这是代码:

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 用法:

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

希望有人在这段时间之后发现它很有用:)

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.RadioButton and declare two dependency properties RadioValue and RadioBinding. Then in the class code, override OnChecked and set the RadioBinding property value to that of the RadioValue property value. In the other direction, trap changes to the RadioBinding property using a callback, and if the new value is equal to the value of the RadioValue property, set its IsChecked property to 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:

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

神爱温柔 2024-08-09 04:53:54

我想出了使用从转换器返回的 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();
    }
}

用法:

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

单选按钮绑定:

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

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

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>
野心澎湃 2024-08-09 04:53:54

改进和现在推荐的答案(例如方法2.0)

虽然我在此页面上提供了接受的答案,如下所示评论者正确地指出,尽管它功能强大且相当灵活,但当您只想按原样使用简单的 RadioButton 控件时,它的复杂性远非理想。

因此,我提出了一种更新的方法,如下所示,它使用自定义 IValueConverter 及其 ConvertBack< 中的 Binding.DoNothing 的强大功能/code> 方法,一种与 RadioButton 控件进行双向绑定的神奇酱汁,现在可以按预期工作。

RadioButtonValueConverter

让我们看一下转换器本身:

public class RadioButtonValueConverter : MarkupExtension, IValueConverter {

    public RadioButtonValueConverter(object optionValue)
        => OptionValue = optionValue;

    public object OptionValue { get; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => value.Equals(OptionValue);

    public object ConvertBack(object isChecked, Type targetType, object parameter, CultureInfo culture)
        => (bool)isChecked        // Is this the checked RadioButton? If so...
            ? OptionValue         // Send 'OptionValue' back to update the associated binding. Otherwise...
            : Binding.DoNothing;  // Return Binding.DoNothing, telling the binding 'ignore this change'

    public override object ProvideValue(IServiceProvider serviceProvider)
        => this;
}

神奇之处在于在 ConvertBack 函数中使用 Binding.DoNothing。由于使用 RadioButton 控件,每个“组”只能有一个活动选项(即只有一个 IsChecked 设置为 true),我们确保只有具有该值的特定 RadioButton 才是其绑定更新源的唯一按钮。其他 RadioButton 实例上的那些根本不执行任何操作。

以下是如何使用它按照 OP 的要求绑定到 int 值(下面,“cv”是转换器代码所在的导入命名空间,传递给转换器的值是特定的 RadioButton 代表)...

<RadioButton Content="One"   IsChecked="{Binding SomeIntProp, Converter={cv:RadioButtonValueConverter 1}}" />
<RadioButton Content="Two"   IsChecked="{Binding SomeIntProp, Converter={cv:RadioButtonValueConverter 2}}" />
<RadioButton Content="Three" IsChecked="{Binding SomeIntProp, Converter={cv:RadioButtonValueConverter 3}}" />

使用自定义 Binding 子类进一步简化

虽然上述方法有效,但有很多重复的代码,并且 90% 的时间,您都没有这样做不要对绑定或转换器做任何特殊的事情。因此,让我们尝试使用为您设置转换器的 RadioButtonBinding 来简化事情。这是代码...

public class RadioButtonBinding : Binding {

    public RadioButtonBinding(string path, object optionValue)
    : base(path)
        => Converter = new RadioButtonValueConverter(optionValue);
}

通过这个新的绑定,调用站点大大简化了(这里,“b”是绑定代码所在的导入命名空间)...

<RadioButton Content="One"   IsChecked="{b:RadioButtonBinding SomeIntProp, 1}" />
<RadioButton Content="Two"   IsChecked="{b:RadioButtonBinding SomeIntProp, 2}" />
<RadioButton Content="Three" IsChecked="{b:RadioButtonBinding SomeIntProp, 3}" />

注意:确保您没有同时设置 Converter 参数,否则您将无法使用此参数!

绑定到枚举值

上面的示例涉及基本标量(例如 1、2、3) 。但是,如果我们想要的值是如下所示的枚举怎么办?

public enum TestEnum {
    yes,
    no,
    maybe,
    noIdea
}

语法是相同的,但在调用站点,我们需要更具体地说明要绑定的值,使其更加详细。 (例如,如果您尝试单独传递“yes”,它将被视为字符串,而不是枚举,因此它将无法通过相等性检查。)

这是转换器版本的调用站点(此处,“v”是枚举值所在的导入命名空间)...

<RadioButton Content="Yes"     IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonValueConverter {x:Static v:TestEnum.yes}}}" />
<RadioButton Content="No"      IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonValueConverter {x:Static v:TestEnum.no}}}" />
<RadioButton Content="Maybe"   IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonValueConverter {x:Static v:TestEnum.maybe}}}" />
<RadioButton Content="No Idea" IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonValueConverter {x:Static v:TestEnum.noIdea}}}" />

虽然更简单,但这是绑定版本的调用站点,更好,但仍然很冗长...

<RadioButton Content="Yes"     IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.yes}}" />
<RadioButton Content="No"      IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.no}}" />
<RadioButton Content="Maybe"   IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.maybe}}" />
<RadioButton Content="No Idea" IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.noIdea}}" />

枚举类型特定的变体

如果您知道您将绑定到特定的枚举类型在许多情况下,您可以通过将早期的 RadioButtonValueConverterRadioButtonBinding 子类化为特定于枚举的变体来简化上述过程。

下面正是使用上面定义的 TestEnum 进行的操作,如下所示...

// TestEnum-specific Converter
public class TestEnumConverter : RadioButtonValueConverter {

    public TestEnumConverter(TestEnum optionValue)
    : base(optionValue) {}
}

// TestEnum-specific Binding
public class TestEnumBinding : RadioButtonBinding {

    public TestEnumBinding(string path, TestEnum value)
    : base(path, value) { }
}

这是调用站点...

<!- Converter Variants -->
<RadioButton Content="Yes"     IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter yes}}" />
<RadioButton Content="No"      IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter no}}" />
<RadioButton Content="Maybe"   IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter maybe}}" />
<RadioButton Content="No Idea" IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter noIdea}}" />

<!- Binding Variants -->
<RadioButton Content="Yes"     IsChecked="{b:TestEnumBinding SomeTestEnumProp, yes}" />
<RadioButton Content="No"      IsChecked="{b:TestEnumBinding SomeTestEnumProp, no}" />
<RadioButton Content="Maybe"   IsChecked="{b:TestEnumBinding SomeTestEnumProp, maybe}" />
<RadioButton Content="No Idea" IsChecked="{b:TestEnumBinding SomeTestEnumProp, noIdea}" />

如您所见,XAML 解析器会自动为您处理字符串到枚举的转换使您的代码更易于阅读。没有比这更简单的了! :)

旁注:在更详细的声明中显式指定枚举值的版本的一个好处是,您可以自动完成枚举的情况。您无法通过为您转换字符串的特定于枚举类型的版本来实现这一点。但是,如果您使用无效的字符串值,后者将无法编译,因此需要权衡简洁性与自动完成便利性。

Improved and Now-Recommended Answer (e.g. approach 2.0)

While I provided the accepted answer to the question here on this page, as the commenters correctly call out, even though it's powerful and quite flexible, it's complexity is far from ideal when you just want to use simple RadioButton controls as-is.

As such, I've come up with a newer approach, shown below, that instead uses a custom IValueConverter and the power of Binding.DoNothing in it's ConvertBack method, the magic sauce that makes two-way bindings to RadioButton controls now work as one would expect.

The RadioButtonValueConverter

Let's take a look at the converter itself:

public class RadioButtonValueConverter : MarkupExtension, IValueConverter {

    public RadioButtonValueConverter(object optionValue)
        => OptionValue = optionValue;

    public object OptionValue { get; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => value.Equals(OptionValue);

    public object ConvertBack(object isChecked, Type targetType, object parameter, CultureInfo culture)
        => (bool)isChecked        // Is this the checked RadioButton? If so...
            ? OptionValue         // Send 'OptionValue' back to update the associated binding. Otherwise...
            : Binding.DoNothing;  // Return Binding.DoNothing, telling the binding 'ignore this change'

    public override object ProvideValue(IServiceProvider serviceProvider)
        => this;
}

The magic sauce is in the use of Binding.DoNothing in the ConvertBack function. Since with RadioButton controls, there can only be one active option per 'group' (i.e. only one with IsChecked set to true), we ensure only the specific RadioButton with that value is the only one whose binding updates the source. Those on the other RadioButton instances simply do nothing.

Here's how you use it to bind to an int value as the OP asked (below, 'cv' is the imported namespace where the converter code resides, and the value you pass to the converter is the value that particular RadioButton represents)...

<RadioButton Content="One"   IsChecked="{Binding SomeIntProp, Converter={cv:RadioButtonValueConverter 1}}" />
<RadioButton Content="Two"   IsChecked="{Binding SomeIntProp, Converter={cv:RadioButtonValueConverter 2}}" />
<RadioButton Content="Three" IsChecked="{Binding SomeIntProp, Converter={cv:RadioButtonValueConverter 3}}" />

Simplifying further with a custom Binding Subclass

While the above works, that's a lot of repeated code and for 90% of the time, you aren't doing anything special with the binding or converter. As such, let's try to simplify things with a RadioButtonBinding that sets up the converter for you. Here's the code...

public class RadioButtonBinding : Binding {

    public RadioButtonBinding(string path, object optionValue)
    : base(path)
        => Converter = new RadioButtonValueConverter(optionValue);
}

With this new binding, the call site is greatly simplified (here, 'b' is the imported namespace where the binding code resides)...

<RadioButton Content="One"   IsChecked="{b:RadioButtonBinding SomeIntProp, 1}" />
<RadioButton Content="Two"   IsChecked="{b:RadioButtonBinding SomeIntProp, 2}" />
<RadioButton Content="Three" IsChecked="{b:RadioButtonBinding SomeIntProp, 3}" />

Note: Make sure you don't also set the Converter argument or you will defeat the entire point of using this!

Binding to Enum Values

The above example dealt with basic scalars (e.g. 1, 2, 3). However, what if the value we want to is an enumeration such as the following?

public enum TestEnum {
    yes,
    no,
    maybe,
    noIdea
}

The syntax is the same, but at the call-site, we need to be more specific about the value we're binding to making it much more verbose. (For instance, if you try and pass 'yes' by itself, it will be treated as a string, not an enum, so it will fail the equality check.)

Here's the converter version's call-site (here, 'v' is the imported namespace where the enum values reside)...

<RadioButton Content="Yes"     IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonValueConverter {x:Static v:TestEnum.yes}}}" />
<RadioButton Content="No"      IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonValueConverter {x:Static v:TestEnum.no}}}" />
<RadioButton Content="Maybe"   IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonValueConverter {x:Static v:TestEnum.maybe}}}" />
<RadioButton Content="No Idea" IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonValueConverter {x:Static v:TestEnum.noIdea}}}" />

And while simpler, here's the binding version's call-site, better, but still verbose...

<RadioButton Content="Yes"     IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.yes}}" />
<RadioButton Content="No"      IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.no}}" />
<RadioButton Content="Maybe"   IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.maybe}}" />
<RadioButton Content="No Idea" IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.noIdea}}" />

Enum-Type-Specific Variants

If you know you will be binding to a particular enum type on many occasions, you can simplify the above by subclassing the earlier RadioButtonValueConverter and RadioButtonBinding to be enum-specific variants.

Below is doing exactly that with TestEnum defined above, like so...

// TestEnum-specific Converter
public class TestEnumConverter : RadioButtonValueConverter {

    public TestEnumConverter(TestEnum optionValue)
    : base(optionValue) {}
}

// TestEnum-specific Binding
public class TestEnumBinding : RadioButtonBinding {

    public TestEnumBinding(string path, TestEnum value)
    : base(path, value) { }
}

And here are the call sites...

<!- Converter Variants -->
<RadioButton Content="Yes"     IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter yes}}" />
<RadioButton Content="No"      IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter no}}" />
<RadioButton Content="Maybe"   IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter maybe}}" />
<RadioButton Content="No Idea" IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter noIdea}}" />

<!- Binding Variants -->
<RadioButton Content="Yes"     IsChecked="{b:TestEnumBinding SomeTestEnumProp, yes}" />
<RadioButton Content="No"      IsChecked="{b:TestEnumBinding SomeTestEnumProp, no}" />
<RadioButton Content="Maybe"   IsChecked="{b:TestEnumBinding SomeTestEnumProp, maybe}" />
<RadioButton Content="No Idea" IsChecked="{b:TestEnumBinding SomeTestEnumProp, noIdea}" />

As you can see, the XAML parser automatically handles the string-to-enum conversion for you making your code much easier to read. Can't get much simpler than that! :)

Sidenote: One nice thing about the versions where you explicitly specify the enum value in its more-verbose declaration is you get auto-completion for the enum's cases. You don't get that with the enum-type-specific versions that convert the string for you. However, the latter will fail to compile if you use an invalid string value so the tradeoff is brevity vs auto-complete convenience.

谁的新欢旧爱 2024-08-09 04:53:54

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

它在 ViewModel 中使用 3 个布尔属性,分别为 FlagForValue1FlagForValue2FlagForValue3
这 3 个属性中的每一个都由一个名为 _intValue 的私有字段支持。

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

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

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

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>

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, FlagForValue2 and FlagForValue3.
Each of these 3 properties is backed by a single private field called _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 FlagForValue1 bool property in the view model and the other two accordingly.

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 INotifyPropertyChanged infrastructure) can selected / deselect each radio button correctly.

    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:

                <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>
心如荒岛 2024-08-09 04:53:54

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

XAML:

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

代码:

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

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

Sometimes it is possible to solve it in the model like this:
Suppose you have 3 boolean properties 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;
        }
    }
}

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

太阳公公是暖光 2024-08-09 04:53:54

Aviad Ps 的回答效果很好。但是,我必须更改相等性检查以比较 OnRadioBindingChanged 中的字符串,否则将枚举与字符串值进行比较,并且最初不会检查单选按钮。

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

Aviad P.s answer works very well. However I had to change the equality check to compare strings in OnRadioBindingChanged otherwise the enum was compared to the string value and no radio button was checked initially.

    private static void OnRadioBindingChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        BindableRadioButton rb = (BindableRadioButton) d;
        if (rb.RadioValue.Equals(e.NewValue?.ToString()))
        {
            rb.SetCurrentValue(IsCheckedProperty, true);
        }
    }
空城缀染半城烟沙 2024-08-09 04:53:54

我根据 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);
        }
    }
}

用法:

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

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

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}"/>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文