20

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

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

4

7 回答 7

31

基本上,在查看了谷歌结果之后,我从MSDN 讨论线程中的信息开始,WPF 博士提供了一个答案,其中谈到了将 ListBox 设置为看起来正确的样式。但是,当列表框被禁用时,背景是一种烦人的颜色,我一生都无法摆脱,直到我阅读了 ListBox ControlTemplate 的 MSDN 示例,它显示了正在踢我的背景的秘密 Border 元素屁股。

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

<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 上的时间越多,我就越觉得它让琐碎的事情变得异常困难,变得异常困难。享受。-斯科特

于 2009-04-30T01:33:25.700 回答
17

使用具有属性 Name 的对象列表将列表框绑定到 ListBox 的 ItemsSource(这可以更改)

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

重要的GroupName="radioList"

于 2009-07-28T03:20:13.693 回答
16

超级简单,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 很简单。

于 2013-04-10T06:44:50.103 回答
7

我已经通过将 aValueConverter转换enum为 a 来完成此操作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当前值和单选框枚举值的绑定,因为您不能在ConverterParameter.

于 2009-04-30T13:58:56.487 回答
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 中包含的任何控件。

希望这可以帮助。

于 2012-12-21T03:09:55.877 回答
2

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

带描述的枚举器

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 
    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");
      }
    }
  }
}
于 2012-09-27T05:28:25.483 回答
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"];

    }

希望对某人有所帮助。

于 2009-08-06T23:26:08.857 回答