WPF 中的数据绑定单选按钮列表

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/805015/
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:19:47  来源:igfitidea点击:

Data bound radio button list in WPF

wpfradio-buttondatabound

提问by Scott O.

I have a list of options in a data object, and I want to make the equivalent of a radio button list to allow the user to select one and only one of them. Functionality similar to a databound combo box, but in radio button format.

我在数据对象中有一个选项列表,我想制作一个单选按钮列表的等效项,以允许用户选择其中一个且仅一个。功能类似于数据绑定组合框,但采用单选按钮格式。

Silly me, I thought this would be built in, but no. How do you do it?

愚蠢的我,我以为这是内置的,但没有。你怎么做呢?

回答by Scott O.

Basically, after reviewing the google results, I started with the info from an MSDN discussion thread where Dr. WPF provided an answer, which talks about styling a ListBox to look right. However, when the listbox is disabled, the background was an annoying color that I couldn't get rid of for the life of me, until I read the MSDN example of the ListBox ControlTemplate, which shows the secret Border element that was kicking my background butt.

基本上,在查看了 google 结果后,我从MSDN 讨论线程中的信息开始,WPF 博士该线程中提供了一个答案,其中讨论了如何将 ListBox 样式化以使其看起来正确。然而,当列表框被禁用时,背景是一种令人讨厌的颜色,我一生都无法摆脱,直到我阅读了 ListBox ControlTemplate 的 MSDN 示例,该示例显示了正在踢我背景的秘密边框元素屁股。

So, the final answer here was this style:

所以,这里的最终答案是这种风格:

<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
    <!-- ControlTemplate taken from MSDN http://msdn.microsoft.com/en-us/library/ms754242.aspx -->
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
    <Setter Property="MinWidth" Value="120"/>
    <Setter Property="MinHeight" Value="95"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBox">
                <Border Name="Border" Background="Transparent"
                        BorderBrush="Transparent"
                        BorderThickness="0"
                        CornerRadius="2">
                    <ScrollViewer Margin="0" Focusable="false">
                        <StackPanel Margin="2" IsItemsHost="True" />
                    </ScrollViewer>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter TargetName="Border" Property="Background"
                                Value="Transparent" />
                        <Setter TargetName="Border" Property="BorderBrush"
                                Value="Transparent" />
                    </Trigger>
                    <Trigger Property="IsGrouping" Value="true">
                        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="{x:Type ListBoxItem}" >
                <Setter Property="Margin" Value="2" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ListBoxItem}">
                            <Border Name="theBorder" Background="Transparent">
                                <RadioButton Focusable="False" IsHitTestVisible="False"
                                             IsChecked="{TemplateBinding IsSelected}">
                                    <ContentPresenter />
                                </RadioButton>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>
</Style>

Which provides a ControlTemplate for, and styles, the ListBox and the Items. And it gets used like this:

它为 ListBox 和 Items 提供 ControlTemplate 和样式。它是这样使用的:

<ListBox Grid.Column="1" Grid.Row="0" x:Name="TurnChargeBasedOnSelector" Background="Transparent"
    IsEnabled="{Binding Path=IsEditing}"
    Style="{StaticResource RadioButtonList}"
    ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainForm}}, Path=DataContext.RampTurnsBasedOnList}"
    DisplayMemberPath="Description" SelectedValuePath="RampTurnsBasedOnID"
    SelectedValue="{Binding Path=RampTurnsBasedOnID, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"/>

The more I spend time with WPF, the more I think it makes the trivial insanely difficult and the insanely difficult trivial. Enjoy. -Scott

我花在 WPF 上的时间越多,我就越认为它会使琐碎的事情变得非常困难,而使非常困难的琐事变得非常困难。享受。-斯科特

回答by Scott O.

Bind the listbox to the ItemsSource of a ListBox with a list of objects that have a property Name (this can change)

将列表框绑定到 ListBox 的 ItemsSource,其中包含具有属性 Name 的对象列表(这可以更改)

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

important GroupName="radioList"

重要的GroupName="radioList"

回答by Scott O.

Super Simple, MVVM friendly, leveraging DataTemplates for types. XAML:

超级简单,MVVM 友好,利用 DataTemplates 类型。XAML:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525">

<Window.Resources>
    <DataTemplate DataType="{x:Type local:Option}">
        <RadioButton Focusable="False"
                IsHitTestVisible="False"
                Content="{Binding Display}"
                IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}">
        </RadioButton>
    </DataTemplate>
</Window.Resources>

<Grid>
    <ListBox ItemsSource="{Binding Options}" SelectedItem="{Binding SelectedOption}"/>
</Grid>

View Model, etc:

查看模型等:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new Vm();
    }
}

public class Vm
{
    public Option[] Options { get { return new Option[] { 
        new Option() { Display = "A" }, 
        new Option() { Display = "B" }, 
        new Option() { Display = "C" } }; } }
    public Option SelectedOption { get; set; }
}

public class Option
{
    public string Display { get; set; }
}

If you wrap your option into a specific type (or likely it is already). You can just set a DataTemplate for that type, WPF will automatically use it. (Define DataTemplate in ListBox resources to limit the scope of where the DataTemplate will be applied).

如果您将选项包装成特定类型(或者可能已经是)。您可以为该类型设置一个 DataTemplate,WPF 将自动使用它。(在 ListBox 资源中定义 DataTemplate 以限制将应用 DataTemplate 的范围)。

Also use group name in the DataTemplate to set the group if you want.

如果需要,还可以在 DataTemplate 中使用组名来设置组。

This is much simpler than changing the control template, however it does mean that you get a blue line on selected items. (Again, nothing a bit of styling can't fix).

这比更改控件模板要简单得多,但它确实意味着您会在所选项目上看到一条蓝线。(同样,没有一点造型无法解决的问题)。

WPF is simple when you know how.

当您知道如何操作时,WPF 很简单。

回答by Anthony Brien

I've done this through a ValueConverterthat converts an enumto a bool. By passing the enum value that your radio button represents as the ConverterParameter, the converter returns whether this radio button should be checked or not.

我已经通过ValueConverter将 an 转换enumbool. 通过传递单选按钮表示为 的枚举值,ConverterParameter转换器返回是否应检查此单选按钮。

<Window.Resources>
    <Converters:EnumConverter x:Key="EnumConverter" />
</Window.Resources>

<RadioButton IsChecked="{Binding Path=MyEnum, Mode=TwoWay, 
                                 Converter={StaticResource EnumConverter}, 
                                 ConverterParameter=Enum1}"}
             Content="Enum 1" />
<RadioButton IsChecked="{Binding Path=MyEnum, Mode=TwoWay, 
                                 Converter={StaticResource EnumConverter}, 
                                 ConverterParameter=Enum2}"}
             Content="Enum 2" />

EnumConverteris defined as follows:

EnumConverter定义如下:

public class EnumConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (targetType.IsAssignableFrom(typeof(Boolean)) && targetType.IsAssignableFrom(typeof(String)))
                throw new ArgumentException("EnumConverter can only convert to boolean or string.");
            if (targetType == typeof(String))
                return value.ToString();

            return String.Compare(value.ToString(), (String)parameter, StringComparison.InvariantCultureIgnoreCase) == 0;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (targetType.IsAssignableFrom(typeof(Boolean)) && targetType.IsAssignableFrom(typeof(String)))
                throw new ArgumentException("EnumConverter can only convert back value from a string or a boolean.");
            if (!targetType.IsEnum)
                throw new ArgumentException("EnumConverter can only convert value to an Enum Type.");

            if (value.GetType() == typeof(String))
            {
                return Enum.Parse(targetType, (String)value, true);
            }

            //We have a boolean, as for binding to a checkbox. we use parameter
            if ((Boolean)value)
                return Enum.Parse(targetType, (String)parameter, true);

            return null;
        }
    }

Note that I don't databind to the list of enums to generate the radio buttons, I've done them by hand. If you wanted to fill the list of radio buttons through a binding, I think you'll need to change the IsCheckedbinding to a MultiBindingwhich binds to both the current value and the radio's enum value, because you cannot use a binding on ConverterParameter.

请注意,我没有将数据绑定到枚举列表来生成单选按钮,而是手动完成的。如果您想通过绑定填充单选按钮列表,我认为您需要将IsChecked绑定更改为绑定到MultiBinding当前值和单选枚举值的 a,因为您不能在 上使用绑定ConverterParameter

回答by Lachlan L.

Sorry, I'd like to put this response to Scott O's post as a comment on his post, but I do not yet have the reputation to do that. I really liked his answer as it was a style-only solution and hence didn't require any added code-behind or creating a custom-control, etc.

抱歉,我想将此回复作为对 Scott O 帖子的评论,但我还没有这样做的声誉。我真的很喜欢他的回答,因为它是一个仅限样式的解决方案,因此不需要任何添加的代码隐藏或创建自定义控件等。

However, I did have one issue when I then went to try using controls inside the ListBoxItems. When I use this style I am unable to focus any of the contained controls due to this line:

但是,当我尝试使用 ListBoxItems 中的控件时确实遇到了一个问题。当我使用此样式时,由于此行,我无法聚焦任何包含的控件:

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

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

The radio button needs to turn off Focusable and IsHitTestVisible for the IsChecked binding to work correctly. To get around this, I changed the IsChecked from a TemplateBinding to a regular binding, which allowed me to make it a two-way binding. Removing the offending settings gave me this line:

单选按钮需要关闭 Focusable 和 IsHitTestVisible 才能使 IsChecked 绑定正常工作。为了解决这个问题,我将 IsChecked 从 TemplateBinding 更改为常规绑定,这允许我使其成为双向绑定。删除有问题的设置给了我这一行:

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

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

Which now allows me to focus any controls contained in ListBoxItems as expected.

这现在允许我按预期关注 ListBoxItems 中包含的任何控件。

Hope this helps.

希望这可以帮助。

回答by Brian Hinchey

I took my inspiration from Jon Benson's blog entry, but modified his solution to use enumerations that have a description attribute. So the key parts of the solution became:

我从Jon Benson 的博客条目中获得灵感,但修改了他的解决方案以使用具有描述属性的枚举。所以解决方案的关键部分变成了:

Enumerator with descriptions

带描述的枚举器

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

Code for reading descriptions and returning key/value pairs for binding.

用于读取描述并返回键/值对以进行绑定的代码。

public static class EnumHelper
{
    public static string ToDescriptionString(this Enum val)
    {
        var attribute =
            (DescriptionAttribute)
            val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false).
                SingleOrDefault();
        return attribute == default(DescriptionAttribute) ? val.ToString() : attribute.Description;
    }

    public static List<KeyValuePair<string,string>> GetEnumValueDescriptionPairs(Type enumType)
    {
        return Enum.GetValues(enumType)
            .Cast<Enum>()
            .Select(e => new KeyValuePair<string, string>(e.ToString(), e.ToDescriptionString()))
            .ToList();
    }
}

Your Object Data Provider in XAML

XAML 中的对象数据提供程序

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

Your ListBox in XAML

XAML 中的列表框

<ListBox 
    ItemsSource="{Binding Source={StaticResource AgeRanges}}"
    SelectedValue="{Binding SelectedAgeRange}"
    SelectedValuePath="Key">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <RadioButton 
                IsChecked="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
                Content="{Binding Value}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The property (e.g. in your view model) that you are binding to

您绑定到的属性(例如在您的视图模型中)

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

回答by Brian Hinchey

I cheated:

我作弊了:

My solution was to bind the list box programaticly since that is all that seemed to work for me:

我的解决方案是以编程方式绑定列表框,因为这似乎对我有用:

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

The XAML looks like this:

XAML 看起来像这样:

                        <ListBox.ItemTemplate >

                        <DataTemplate >
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition></RowDefinition>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                                </Grid.ColumnDefinitions>

                                <RadioButton 
                                    IsChecked="{Binding Path=PrimaryPhoneID}" 
                                    GroupName="Phone" 
                                    x:Name="rbPhone"
                                    Content="{Binding Path=PrimaryPhoneID}"
                                    Checked="rbPhone_Checked"/>

                                <CheckBox Grid.Column="2" IsEnabled="False" IsChecked="{Binding Path=Active}" Content="{Binding Path=Number}" ></CheckBox>

                            </Grid>
                        </DataTemplate>
                    </ListBox.ItemTemplate>

And in my event to read the value of the radio button as it is selected looks like this:

在我的事件中,当它被选中时读取单选按钮的值看起来像这样:

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

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

    }

Hope that helps someone.

希望能帮助某人。