单选按钮

单选按钮(也称为选项按钮)允许用户从包含两个或更多互斥但相关的选项的集合中选择一个选项。 单选按钮始终成组使用,每个选项都表示为组中的一个单选按钮。

默认状态下,不会选择 RadioButtons 组中的单选按钮。 也就是说,所有单选按钮都处于清除状态。 但是,用户选择单选按钮后,就不能取消选择该按钮以将组还原到其初始清除状态。

RadioButtons 组的单一行为将其与复选框区分开来,后者支持多选、取消选择或清除。

RadioButtons 组的示例,其中选择了一个单选按钮

这是正确的控制吗?

使用单选按钮可以让用户从两个或更多互斥的选项中进行选择。

RadioButtons 组,其中选择了一个单选按钮

当用户需要在做出选择前查看所有选项时,可使用单选按钮。 单选按钮平等地强调所有选项,这意味着有些选项可能会引起超出必要或所需的关注。

除非所有选项都值得同等关注,否则请考虑使用其他控件。 例如,若要推荐大多数用户和大多数情况下的最佳选项,请使用 组合框 将最佳选项显示为默认选项。

组合框,显示默认选项

如果只有两个可能的选项可以明确表示为单个二进制选项,例如 on/off 或 yes/no,请将它们合并为单个 复选框,切换开关 控件。 例如,对“我同意”使用单个复选框,而不是“我同意”和“我不同意”的两个单选按钮。

请勿使用两个单选按钮来表示单个二元选项:

表示二元选项的两个单选按钮

改用复选框:

复选框是演示二进制选择 的一个不错的选择

当用户可以选择多个选项时,请使用 复选框

复选框支持多选

当用户的选项在某个值范围内时(例如,10,20,30...100),应使用 滑块控件

滑块控件,在一系列值中显示一个值

如果有超过八个选项,请使用组合框

列表框,显示多个选项

如果可用选项基于应用的当前上下文,或者它们可能动态变化,请使用列表控件。

Recommendations

  • 请确保一组单选按钮的目的和当前状态十分明确。
  • 将单选按钮的文本标签限制在一行以内。
  • 如果文本标签是动态的,请考虑按钮如何自动调整大小,以及它周围的任何视觉对象会发生什么情况。
  • 除非品牌准则告诉你,否则请使用默认字体。
  • 不要并排放置两个 RadioButtons 组。 当两个 RadioButtons 组彼此相邻时,用户很难确定哪些按钮属于哪个组。

RadioButtons 概述

RadioButtons 与 RadioButton

有两种方法可以创建单选按钮组:RadioButtons 和 RadioButton。

  • 建议使用 RadioButtons 控件。 此控件简化了布局、处理键盘导航和辅助功能,并支持绑定到数据源。
  • 你可以使用由单独的 RadioButton 控件组成的组。

键盘访问和导航行为已在 RadioButtons 控制项中进行了优化。 这些改进在辅助功能和键盘功能方面有所帮助,能够让用户更快、更轻松地浏览选项列表。

除这些改进外,RadioButtons 组中各个单选按钮的默认可视布局也通过自动化方向、间距和边距设置进行了优化。 此优化消除了指定这些属性的要求,因为使用更基本的分组控件(如 StackPanelGrid)时,可能需要这样做。

RadioButtons 控件具有特殊的导航行为,可帮助键盘用户更快、更轻松地导航列表。

键盘焦点

RadioButtons 控件支持两种状态:

  • 未选择单选按钮
  • 选择了一个单选按钮

后续部分介绍每个状态中控件的焦点行为。

未选择单选按钮

如果未选择任何单选按钮,则列表中的第一个单选按钮会获得焦点。

注释

不会选择从初始选项卡导航接收选项卡焦点的项。

没有选项卡焦点的列表,未选择任何项

无标签页焦点的列表,且无选中项目

具有初始选项卡焦点的列表,未选择任何项

具有初始选项卡焦点但未选择任何项的列表

选择了一个单选按钮

当用户通过按下Tab键进入已经选中单选按钮的列表时,所选的单选按钮会获得焦点。

不带选项卡焦点的列表

没有选项卡焦点但选择了某个项的列表

具有初始选项卡焦点的列表

具有初始选项卡焦点且选择了某个项的列表

键盘导航

有关常规键盘导航行为的详细信息,请参阅 键盘交互 - 导航

RadioButtons 组中的项已具有焦点时,用户可以在组中的项之间使用箭头键进行“内部导航”。 上下箭头键会移动到 XAML 标记中定义的“下一个”或“上一个”逻辑项。 使用向左键和向右键可进行空间移动。

在单列或单行布局中,键盘导航会导致以下行为:

单列

单列 RadioButtons 组中键盘导航的示例

向上键和向下键在项之间移动。
向左键和向右键不执行任何操作。

单行

单行 RadioButtons 组中键盘导航的示例

向左键和向上键移动到上一项,向右键和向下键移动到下一项。

在多列多行网格布局中,键盘导航会导致此行为:

左/右方向键

多列/多行 RadioButtons 组中水平键盘导航的示例

使用向左键和向右键可让焦点在同一行中的项之间水平移动。

水平键盘导航示例,焦点位于列 的最后一项

当焦点位于列中的最后一个项目上并且按下向右键或向左键时,焦点将移动到下一列或上一列中的最后一项(如果有)。

向上/向下键

多列/多行 RadioButtons 组中垂直键盘导航的示例

上下箭头键可在列中的项目间垂直移动焦点。

焦点位于某列中最后一项的垂直键盘导航示例

当焦点位于列中的最后一个项目并且按下向下键时,焦点将移动到下一列中的第一个项目(如果有)。 当焦点位于列中的第一个项目并按下向上键时,焦点将移动到上一列中的最后一项(如果有)

有关详细信息,请参阅 键盘交互

换行

RadioButtons 组不会将焦点从第一行或第一列换行到最后一行或最后一列,也不会从最后一行或最后一列换行到第一行或最后一列。 这是因为,当用户使用屏幕阅读器时,边界感和开始和结束的明确指示会丢失,这使得视觉障碍的用户难以导航列表。

RadioButtons 控件也不支持枚举,因为控件旨在包含合理数量的项(请参阅 这是正确的控件吗?)。

选择跟随焦点

使用键盘在 RadioButtons 组中的项目之间导航时,焦点从一个项目移动到下一个项目时,将选中新聚焦的项目并清除以前聚焦的项目。

键盘导航之前

键盘导航前的焦点和选择示例

键盘导航之前的焦点和选择。

键盘导航之后

键盘导航后的焦点和选择示例

键盘导航之后的焦点和选择,其中通过向下键将焦点移到单选按钮 3,选择它,并清除单选按钮 2。

可以使用 Ctrl+箭头键进行导航,无需更改所选内容即可移动焦点。 移动焦点后,可以使用空格键选择当前具有焦点的项目。

如果使用游戏手柄或遥控器在单选按钮之间移动,“选择跟随焦点”行为将被禁用,用户必须按“A”按钮来选择当前具有焦点的单选按钮。

辅助功能行为

下表说明讲述人如何处理 RadioButtons 组和播放的内容。 此行为取决于用户如何设置讲述人详细信息首选项。

Action 解说员公告
焦点移动到所选项目 “名称、RadioButton、已选中、第 x 项,共 N 项”
焦点移动到未选择的项目
(如果使用 Ctrl 箭头键或Xbox游戏板进行导航,
这表明所选内容不跟随焦点。)
“名称、RadioButton、未选中、第 x 项,共 N 项”

注释

讲述人为每个项目宣布的 名称AutomationProperties.Name 附加属性的值(如果该项有此属性可用);否则,则是该项目的 ToString 方法所返回的值。

x 是当前项的数目。 N 是组中的项总数。

创建 WinUI 3 RadioButtons 组

WinUI 3 示例集图标 WinUI 3 示例库应用包含 WinUI 控件和功能的交互式示例。 从 Microsoft Store 或浏览 GitHub 上的源代码获取应用。

RadioButtons 控件使用类似于 ItemsControl的内容模型。 这意味着你可以:

在这里,你将使用三个选项声明一个简单的 RadioButtons 控件。 Header 属性设置为为组提供标签,SelectedIndex 属性设置为提供默认选项。

<RadioButtons Header="Background color"
              SelectedIndex="0"
              SelectionChanged="BackgroundColor_SelectionChanged">
    <x:String>Red</x:String>
    <x:String>Green</x:String>
    <x:String>Blue</x:String>
</RadioButtons>

结果如下所示:

一个包含三个单选按钮的组

若要在用户选择某个选项时执行操作,请处理 SelectionChanged 事件。 在这里,将更改名为“ExampleBorder”()的 <Border x:Name="ExampleBorder" Width="100" Height="100"/> 元素的背景色。

private void BackgroundColor_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (ExampleBorder != null && sender is RadioButtons rb)
    {
        string colorName = rb.SelectedItem as string;
        switch (colorName)
        {
            case "Red":
                ExampleBorder.Background = new SolidColorBrush(Colors.Red);
                break;
            case "Green":
                ExampleBorder.Background = new SolidColorBrush(Colors.Green);
                break;
            case "Blue":
                ExampleBorder.Background = new SolidColorBrush(Colors.Blue);
                break;
        }
    }
}

小窍门

还可以从 SelectionChangedEventArgs.AddedItems 属性获取所选项。 索引 0 处只有一个选定项,因此你可以获取如下所示的选定项:string colorName = e.AddedItems[0] as string;

选择状态

单选按钮有两个状态:已选择或已清除。 在 RadioButtons 组中选择某个选项时,可以从 SelectedItem 属性获取其值,并可以从 选定索引 属性中获取其在集合中的位置。 如果用户在同一组中选择另一个单选按钮,那么该单选按钮可以被清除;但如果用户再次选择同一个单选按钮,则无法清除。 但是,可以通过设置 SelectedItem = nullSelectedIndex = -1,以编程方式清除单选按钮组。 (如果将 SelectedIndex 设置为 Items 集合范围之外的任何值,则不会选择任何内容。)

RadioButtons 内容

在前面的示例中,使用简单字符串填充了 RadioButtons 控件。 该控件提供了单选按钮,并使用字符串作为每个单选按钮的标签。

但是,可以使用任何对象填充 RadioButtons 控件。 通常,你希望对象提供可用作文本标签的字符串表示形式。 在某些情况下,图像可能适合代替文本。

在这里,SymbolIcon 元素用于填充控件。

<RadioButtons Header="Select an icon option:">
    <SymbolIcon Symbol="Back"/>
    <SymbolIcon Symbol="Attach"/>
    <SymbolIcon Symbol="HangUp"/>
    <SymbolIcon Symbol="FullScreen"/>
</RadioButtons>

一组带有符号图标的单选按钮

还可以使用单独的 RadioButton 控件来填充 RadioButtons 项。 这是我们稍后讨论的一个特殊情况。 请参阅 RadioButtons 组中的 RadioButton 控件

能够使用任何对象的好处是可以将 RadioButtons 控件绑定到数据模型中的自定义类型。 下一部分演示了这一点。

数据绑定

RadioButtons 控件支持将数据绑定到其 ItemsSource 属性。 此示例演示如何将控件绑定到自定义数据源。 此示例的外观和功能与前面的背景色示例相同,但在这里,颜色画笔存储在数据模型中,而不是在 SelectionChanged 事件处理程序中创建。

<RadioButtons Header="Background color"
              SelectedIndex="0"
              SelectionChanged="BackgroundColor_SelectionChanged"
              ItemsSource="{x:Bind colorOptionItems}"/>
public sealed partial class MainPage : Page
{
    // Custom data item.
    public class ColorOptionDataModel
    {
        public string Label { get; set; }
        public SolidColorBrush ColorBrush { get; set; }

        public override string ToString()
        {
            return Label;
        }
    }

    List<ColorOptionDataModel> colorOptionItems;

    public MainPage()
    {
        this.InitializeComponent();

        colorOptionItems = new List<ColorOptionDataModel>();
        colorOptionItems.Add(new ColorOptionDataModel()
            { Label = "Red", ColorBrush = new SolidColorBrush(Colors.Red) });
        colorOptionItems.Add(new ColorOptionDataModel()
            { Label = "Green", ColorBrush = new SolidColorBrush(Colors.Green) });
        colorOptionItems.Add(new ColorOptionDataModel()
            { Label = "Blue", ColorBrush = new SolidColorBrush(Colors.Blue) });
    }

    private void BackgroundColor_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var option = e.AddedItems[0] as ColorOptionDataModel;
        ExampleBorder.Background = option?.ColorBrush;
    }
}

RadioButtons 组中的 RadioButton 控件

可以使用单独的 RadioButton 控件来填充 RadioButtons 项。 为此,您可以访问某些属性(如 AutomationProperties.Name),或者您可能已经具备现有的 RadioButton 代码,但希望利用 RadioButtons 的布局和导航。

<RadioButtons Header="Background color">
    <RadioButton Content="Red" Tag="red" AutomationProperties.Name="red"/>
    <RadioButton Content="Green" Tag="green" AutomationProperties.Name="green"/>
    <RadioButton Content="Blue" Tag="blue" AutomationProperties.Name="blue"/>
</RadioButtons>

RadioButton 组中使用 RadioButtons 控件时,RadioButtons 控件知道如何呈现 RadioButton,因此不会出现两个选择环的情况。

但是,应注意某些行为。 建议在单个控件上或在 RadioButtons上处理状态和事件,但不要同时处理两者,以避免冲突。

此表显示这两个控件上的相关事件和属性。

RadioButton RadioButtons
CheckedUncheckedClick SelectionChanged
IsChecked SelectedItemSelectedIndex

如果在单个 RadioButton 上处理事件(例如 CheckedUnchecked),并且同时处理 RadioButtons.SelectionChanged 事件,则两个事件都将触发。 首先发生 RadioButton 事件,然后发生 RadioButtons.SelectionChanged 事件,这可能会导致冲突。

IsCheckedSelectedItemSelectedIndex 属性保持同步。 对一个属性的更改会更新另一个属性。

忽略 RadioButton.GroupName 属性。 组是由 RadioButtons 控件创建的。

定义多列

默认情况下,RadioButtons 控件在单个列中垂直排列其单选按钮。 可以设置 MaxColumns 属性,使控件在多个列中排列单选按钮。 (执行此操作时,单选按钮按列主序顺序排列,即各个项按照从上到下、然后从左到右的顺序填充。)

<RadioButtons Header="RadioButtons in columns" MaxColumns="3">
    <x:String>Item 1</x:String>
    <x:String>Item 2</x:String>
    <x:String>Item 3</x:String>
    <x:String>Item 4</x:String>
    <x:String>Item 5</x:String>
    <x:String>Item 6</x:String>
</RadioButtons>

在两个三列组中包含的 两个三列组中的单选按钮

小窍门

若要将项目排列在单个水平行中,请将 MaxColumns 设置为等于组中的项数。

创建自己的 RadioButton 组

重要

建议使用 RadioButtons 控件对 RadioButton 元素进行分组。

单选按钮以组形式工作。 可以通过以下两种方式之一对单个 RadioButton 控件进行分组:

  • 将它们放在同一个父容器中。
  • 将每个单选按钮上的 GroupName 属性设置为相同的值。

在此示例中,第一个单选按钮组依据位于相同的堆栈面板中来进行隐式分组。 第二个组分为两个堆栈面板,因此 GroupName 用于将它们显式分组为单个组。

<StackPanel>
    <StackPanel>
        <TextBlock Text="Background" Style="{ThemeResource BaseTextBlockStyle}"/>
        <!-- Group 1 - implicit grouping -->
        <StackPanel Orientation="Horizontal">
            <RadioButton Content="Green" Tag="green" Checked="BGRadioButton_Checked"/>
            <RadioButton Content="Yellow" Tag="yellow" Checked="BGRadioButton_Checked"/>
            <RadioButton Content="White" Tag="white" Checked="BGRadioButton_Checked"
                         IsChecked="True"/>
        </StackPanel>
    </StackPanel>

    <StackPanel>
        <TextBlock Text="BorderBrush" Style="{ThemeResource BaseTextBlockStyle}"/>
        <!-- Group 2 - grouped by GroupName -->
        <StackPanel Orientation="Horizontal">
            <StackPanel>
                <RadioButton Content="Green" Tag="green" GroupName="BorderBrush"
                             Checked="BorderRadioButton_Checked"/>
                <RadioButton Content="Yellow" Tag="yellow" GroupName="BorderBrush"
                             Checked="BorderRadioButton_Checked" IsChecked="True"/>
                <RadioButton Content="White" Tag="white"  GroupName="BorderBrush"
                             Checked="BorderRadioButton_Checked"/>
            </StackPanel>
        </StackPanel>
    </StackPanel>
    <Border x:Name="ExampleBorder"
            BorderBrush="#FFFFD700" Background="#FFFFFFFF"
            BorderThickness="10" Height="50" Margin="0,10"/>
</StackPanel>
private void BGRadioButton_Checked(object sender, RoutedEventArgs e)
{
    RadioButton rb = sender as RadioButton;

    if (rb != null && ExampleBorder != null)
    {
        string colorName = rb.Tag.ToString();
        switch (colorName)
        {
            case "yellow":
                ExampleBorder.Background = new SolidColorBrush(Colors.Yellow);
                break;
            case "green":
                ExampleBorder.Background = new SolidColorBrush(Colors.Green);
                break;
            case "white":
                ExampleBorder.Background = new SolidColorBrush(Colors.White);
                break;
        }
    }
}

private void BorderRadioButton_Checked(object sender, RoutedEventArgs e)
{
    RadioButton rb = sender as RadioButton;

    if (rb != null && ExampleBorder != null)
    {
        string colorName = rb.Tag.ToString();
        switch (colorName)
        {
            case "yellow":
                ExampleBorder.BorderBrush = new SolidColorBrush(Colors.Gold);
                break;
            case "green":
                ExampleBorder.BorderBrush = new SolidColorBrush(Colors.DarkGreen);
                break;
            case "white":
                ExampleBorder.BorderBrush = new SolidColorBrush(Colors.White);
                break;
        }
    }
}

这两组 RadioButton 控件如下所示:

两个组中的单选按钮

单选按钮状态

单选按钮有两个状态:已选择或已清除。 选择单选按钮时,其 IsChecked 属性为 true。 清除单选按钮时,其 IsChecked 属性为 false。 如果用户在同一组中选择另一个单选按钮,那么该单选按钮可以被清除;但如果用户再次选择同一个单选按钮,则无法清除。 但是,你可以通过将单选按钮的 IsChecked 属性设置为 false,以编程方式清除它。

要考虑的视觉对象

单个 RadioButton 控件的默认间距不同于 RadioButtons 组提供的间距。 若要将 RadioButtons 间距应用于单个 RadioButton 控件,请将 Margin 值设为 0,0,7,3,如下所示。

<StackPanel>
    <StackPanel.Resources>
        <Style TargetType="RadioButton">
            <Setter Property="Margin" Value="0,0,7,3"/>
        </Style>
    </StackPanel.Resources>
    <TextBlock Text="Background"/>
    <RadioButton Content="Item 1"/>
    <RadioButton Content="Item 2"/>
    <RadioButton Content="Item 3"/>
</StackPanel>

下图显示了组中单选按钮的首选间距。

显示一组垂直排列的单选按钮的图像

图像显示单选按钮的间距准则 显示单选按钮间距指南的图像

注释

如果使用 WinUI RadioButtons 控件,则间距、边距和方向已得到优化。