互斥式复选框,如Grouped RadioButtons

时间:2015-04-19 23:16:43

标签: c# wpf checkbox radio-button

如何对CheckBoxes进行分组(可以对RadioButtons进行分组),以便在选中其中一个时取消选中其他选项?

修改

使用CheckBoxes的动机是,默认情况下,一旦选中,它们允许用户取消选择它们,而RadioButtons则不允许(没有添加额外的逻辑)。此外,这种类型的UI元素分组更自然地映射到可以为空的bool值,其中有三个有效选项:selected,not selected,null(或根本没有选择)。这是某些场景的有效用例。 RadioButtonCheckBox都不能完全符合这个范例。

从用户的角度来看,完成此操作的控件实际上是任意的,因为一个只是一个填充的圆,而另一个是一个填充的正方形,你可以很容易地渲染为另一个,所以我不认为混淆任何人是一个很大的问题。我想说一个用户不太可能被一个预期有一个圆圈的方格所困扰,而且被迫在两个RadioButton选项之间做出决定更加沮丧,他们不希望在两个选项之间做出选择。 。实际上,可以创建支持分组的CheckBoxes或支持取消选择的RadioButtons来实现这一点,这个问题是关于前者的。

2 个答案:

答案 0 :(得分:0)

我在CodeBehind中不得不一直这样做很沮丧,因为我推出了自己继承自CheckBox的类,它使用RadioButton提供的相同API添加了功能。它缺少一些东西,但这是为了后人的缘故。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace GroupedCheckBoxTest
{
    public class GroupedCheckBox : CheckBox
    {
        /// <summary>
        /// All the members of the current group
        /// </summary>
        private IEnumerable<GroupedCheckBox> currentGroup;

        /// <summary>
        /// Gets or sets the name that specifies which GroupedCheckBox controls are mutually exclusive.
        /// </summary>
        public string GroupName { get; set; }

        /// <summary>
        /// Indicates when the checked state of the GroupedCheckBox changes
        /// </summary>
        public event EventHandler CheckChanged;

        public GroupedCheckBox()
        {
            // Add only the current checkbox to the group
            // (for now, but potentially always)
            currentGroup = this.SingleItemAsEnumerable();

            // Attach empty delegate so we don't have to keep checking for null
            this.CheckChanged += delegate { };

            // Aggregate all checked changed events
            this.Checked += GroupedCheckBox_Checked;

            // Associate all GroupedCheckBoxs once they are loaded
            this.Loaded += GroupedCheckBox_Loaded;
        }

        // False when any one of the group checked
        void GroupedCheckBox_Checked(object sender, RoutedEventArgs e)
        {
            // Uncheck everyone else in the group
            foreach (GroupedCheckBox otherCB in currentGroup.Except(this.SingleItemAsEnumerable()))
            {
                // Uncheck the other checkbox
                otherCB.IsChecked = false;
            }

            this.CheckChanged(this, e);
        }

        void GroupedCheckBox_Loaded(object sender, RoutedEventArgs e)
        {
            // If this GroupedCheckBox isn't even part of a group
            if (string.IsNullOrEmpty(this.GroupName))
            {
                // We don't need to do anything special
                return;
            }

            // The highest parent node we can get to
            DependencyObject parentNode = this;

            // Walk the control tree until we get to the top
            while (VisualTreeHelper.GetParent(parentNode) != null)
            {
                // Get the parent of the current node
                parentNode = VisualTreeHelper.GetParent(parentNode);
            }

            // Get all GroupedCheckBox's with the same GroupName as this one
            currentGroup = parentNode.GetControlsOfType<GroupedCheckBox>().Where(cb => this.GroupName == cb.GroupName);
        }
    }

    internal static class GroupedCheckBoxHelperExtensions
    {
        public static IEnumerable<T> GetControlsOfType<T>(this DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null && child is T)
                    {
                        yield return (T)child;
                    }

                    foreach (T childOfChild in GetControlsOfType<T>(child))
                    {
                        yield return childOfChild;
                    }
                }
            }
        }

        // usage: someObject.SingleItemAsEnumerable();
        public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
        {
            yield return item;
        }
    }
}

只需将其复制到GroupedCheckBox.cs并添加适当的xaml引用并按如下方式使用:

<UserControl x:Class="UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:controls="clr-namespace:YourProject.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>        
        <controls:GroupedCheckBox Grid.Row="0" Grid.Column="0" IsChecked="{Binding Path=Stop}" GroupName="1" />
        <controls:GroupedCheckBox Grid.Row="0" Grid.Column="1" IsChecked="{Binding Path=Go}"   GroupName="1" />

        <controls:GroupedCheckBox Grid.Row="1" Grid.Column="0" IsChecked="{Binding Path=Yes}"  GroupName="2" />
        <controls:GroupedCheckBox Grid.Row="1" Grid.Column="1" IsChecked="{Binding Path=No}"   GroupName="2" />
    </Grid>
</UserControl>

答案 1 :(得分:0)

previouse answer中,您可以使用伪装成CheckBox的RadioButton。或者您在ViewModel的代码中执行逻辑。

但是有一个原因,没有CheckBox分组:RadioButton的语义是只能选择其中一个。 CheckBox向用户发出信号,表示这些框彼此独立。

所以我建议坚持使用RadioButtons,除非有任何其他原因。