简单的 WPF 单选按钮绑定?

Posted

技术标签:

【中文标题】简单的 WPF 单选按钮绑定?【英文标题】:Simple WPF RadioButton Binding? 【发布时间】:2010-11-22 00:27:32 【问题描述】:

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

【问题讨论】:

看看这篇博文:> WPF RadioButtons and data binding 请看Binding IsChecked property of RadioButton in WPF的解决方案,它就像一个魅力。 WPF 4.0 的原始问题已修复! 可以在这个答案中找到更好、更通用的解决方案:***.com/a/406798/414306 【参考方案1】:

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

我有一个 model.cs 类:

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

我有 DataContext 设置为 model.cs 的 Window1.xaml.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>

【讨论】:

你只需要改变你的转换器来解析枚举 @MarqueIV 这个问题很容易解决,如该问题已接受答案的第一条评论所示:***.com/questions/397556/… 一件事 @user1151923... 在这种情况下返回 'Binding.DoNothing' 而不是 'DependencyProperty.UnsetValue' 不合适吗? 比公认的答案更简洁——正是我想要的。 但是,请不要“如果(真)返回真;否则返回假;”构造... :-)【参考方案2】:

实际上,使用这样的转换器会破坏双向绑定,而且如上所述,您也不能将其与枚举一起使用。更好的方法是对 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 属性显式应用样式,而不是简单地将 ListBoxItem 定位到 ListBox 的资源部分。)

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

【讨论】:

+1,但如果那是the simplest way to bind a group of 3 radiobuttons to a property of type int for values 1, 2, or 3,它对 WPF 来说真的不是很好。我真的不明白为什么他们让最简单的事情变得如此困难和不必要的复杂 这实际上不仅仅是因为它设置了一个列表框的样式,它是一个单一的控件,这就是绑定工作如此简单的原因(一旦你完成了它的疯狂设置。)但是,是的,我同意 RadioButton 类肯定有一些绑定缺陷。当然你可以用代码隐藏更简单,但是在这里,你只需要做一次样式等,以后使用就容易多了。 天啊,简单吗?在充分尊重您的回答和努力的情况下,但称这种“简单风格”是很牵强的,不是吗?与 Winforms 相比,WPF 的核心思想和感知优势不是更好的数据绑定吗?在 WPF 上市多年后,一个简单的单选按钮(和单选按钮分组)如何打破绑定超出了我的理解。为 MS 感到羞耻。 这实际上是一个不公平的说法。例如,查看使用 ShowMeTheTemplate 的常规按钮的控件模板。它从简单,但它的使用,添加一个Button标签是非常简单的。你正在偷看我在这里所做的事情,但如果你只是将它添加到你的资源部分,然后忘记它,它也会很简单。 另外,想想你在评论什么……不同的无线电控制作用于一个单一的价值。多个控件意味着多个绑定。这种风格使单个控件(因此单个值)看起来像多个控件。这就是为什么这很棒。此外,这还可用于绑定到 IEnumerable 并动态创建项目。单选按钮根本无法做到这一点。【参考方案3】:

我知道它已经过期了,但我有一个替代解决方案,它更轻更简单。从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"/>

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

【讨论】:

一个非常好的解决方案,感谢发布。如此简洁且易于实施。 我当然做到了。 ~ 第 40 行应为: rb.SetCurrentValue(RadioButton.IsCheckedProperty, rb.RadioValue.Equals(e.NewValue));这样当绑定的值与指定值不同时,不会检查任何单选框。 (对不起,我似乎无法插入换行符的格式)。【参考方案4】:

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

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 转换器) 不需要额外的枚举 不需要奇怪的绑定 简单易懂 不违反 MVVM(呵呵,至少我希望如此)

【讨论】:

我也对此感到惊讶。此解决方案适用于 WPF 新手,只知道如何进行绑定并希望快速绑定单选按钮。这非常简单明了。 +1 这很好,在写出我自己的Converter 之前可以快速投入和测试。 +1 只是一个小建议:从 C# 6 开始,您将不再需要 backingfield。只需使用 AutoProperty: public bool[] ModeArray get; = 新 bool[] 真、假、假 ; 这怎么可能是数据驱动的?如果我从数据库加载值,如何以编程方式设置选项? @RayBrennan 此选项不太适合动态数据集(不认为在 .xaml 中可行)。如果您需要更复杂的使用,我建议选择更复杂的解决方案之一,即转换器(...或在代码隐藏中生成投标)。【参考方案5】:

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

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

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

在视图模型中设置其中一个属性(例如FlagForValue1)时,同时引发其他两个属性(例如FlagForValue2FlagForValue3)的属性更改事件也很重要,因此 UI (WPF @ 987654329@ Infrastructure)可以正确选择/取消选择每个单选按钮。

    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>

【讨论】:

请补充说明 丑得像罪,但基本上我也是这样做的。【参考方案6】:

有时可以像这样在模型中解决它: 假设您有 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;
        
    

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

【讨论】:

【参考方案7】:

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

【讨论】:

这非常棒(不过我希望它具有对枚举值的 IntelliSense 支持)。 FWIW,我已将 catch (ArgumentException) 位更改为以下内容,因此 XAML 中的错误更加清晰:throw new NotSupportedException($"Enum this.Type doesn't contain value 'parameter'.");【参考方案8】:

我根据 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"/>

【讨论】:

【参考方案9】:

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

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

【讨论】:

以上是关于简单的 WPF 单选按钮绑定?的主要内容,如果未能解决你的问题,请参考以下文章

将单选按钮组绑定到 WPF 中的属性

ItemsControl 上的 WPF MVVM 单选按钮

WPF + MVVM + RadioButton:使用单个属性处理绑定

WPF dataGrid 单选按钮

C# WPF:当单选按钮显示为图像时,数据网格中的单选按钮分组不起作用

PowerShell\WPF:使用数据模板的 PowerShell 中的空单选按钮对象