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

时间:2009-04-30 01:27:15

标签: wpf radio-button databound

我有一个数据对象中的选项列表,我想要相当于一个单选按钮列表,以允许用户选择一个而且只选择其中一个。功能类似于数据绑定组合框,但采用单选按钮格式。

傻傻的我,我以为这会被内置,但不是。你是怎么做到的?

7 个答案:

答案 0 :(得分:27)

基本上,在审核了谷歌搜索结果后,我开始使用an MSDN discussion thread where Dr. WPF provided an answer中的信息,该信息讨论了使ListBox样式化的问题。然而,当列表框被禁用时,背景是一种烦人的颜色,我无法摆脱我的生活,直到我读到the MSDN example of the ListBox ControlTemplate,它显示了正在踢我的背景屁股的秘密边框元素。

所以,最后的答案就是这种风格:

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

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

我花在WPF上的时间越多,我就越认为它使得琐碎的疯狂困难和疯狂的琐事。请享用。斯科特

答案 1 :(得分:14)

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

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

重要 GroupName =“radioList”

答案 2 :(得分:7)

我通过将ValueConverter转换为enum的{​​{1}}完成此操作。通过将单选按钮表示的枚举值传递为bool,转换器将返回是否应检查此单选按钮。

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" /> 定义如下:

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; } } 绑定更改为绑定到当前值和无线电枚举值的IsChecked,因为你不能在MultiBinding上使用绑定。

答案 3 :(得分:6)

Super Simple,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>

查看模型等:

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

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

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

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

当你知道如何时,WPF很简单。

答案 4 :(得分:4)

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

然而,当我尝试使用ListBoxItems中的控件时,我确实遇到了一个问题。当我使用这种风格时,由于这一行,我无法关注任何包含的控件:

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

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

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

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

希望这有帮助。

答案 5 :(得分:2)

我从Jon Benson's blog entry获取灵感,但修改了他的解决方案以使用具有description属性的枚举。因此,解决方案的关键部分变为:

包含说明的调查员

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

用于读取描述和返回绑定的键/值对的代码。

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

XAML中的对象数据提供程序

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

您在XAML中的ListBox

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

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

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

答案 6 :(得分:1)

我作弊:

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

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

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>

在我的事件中读取单选按钮的值,因为它被选中如下:

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

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

    }

希望能有所帮助。