如何在WPF中使一组切换按钮表现得像单选按钮?

131

我有一组按钮,应该像切换按钮一样工作,但也像单选按钮一样,当前只有一个按钮可以被选择/按下。还需要一种状态,其中没有任何按钮被选择/按下。

行为类似于 Photoshop 工具栏,在任何时候都只有零个或一个工具被选择!

有任何想法如何在 WPF 中实现这个功能吗?

10个回答

325

在我看来,这是最简单的方法。

<RadioButton Style="{StaticResource {x:Type ToggleButton}}" />

享受! -- Pricksaw


32
这个应该是目前为止最好的选择答案。它可以给你所有单选按钮的好处,而又不必将其绑定到某个控制器上产生的问题。我最初尝试将其绑定到一个独立的不可见单选按钮集合上,但这会产生不必要的额外开销,并且最终出现了一个错误,即所有点击的按钮看起来都被突出显示,但不一定被选中。 - Lee Louviere
20
如果您需要将此应用于元素内的所有单选按钮(例如 Grid),请使用 <Grid.Resources> <Style TargetType="RadioButton" BasedOn="{StaticResource {x:Type ToggleButton}}" /> </Grid.Resources> - Roman Starkov
18
这种方法的一个缺点是,您无法通过单击已选中的单选按钮来取消选中。 - Julien
2
@wondra,你只能为FrameworkElement分配单个样式。但是,你可以采用romkyns的解决方案并从ToggleButton样式继承: <Style BasedOn="{StaticResource {x:Type ToggleButton}}" x:Key="Blubb" TargetType="RadioButton"><Setter Property="Background" Value="Yellow" /></Style> - Suigi
2
@Julien 如果你想通过点击一个已选中的按钮来 "取消选中另一个(按钮)",则不应该使用 RadioButton。这是整个问题的关键: 单选按钮 "代表用户可以选择但不能清除的按钮"。如果你想允许用户取消选中,则应该使用 ToggleButton - Disillusioned
显示剩余5条评论

37

最简单的方法是为ListBox设置ToggleButtons的ItemTemplate样式

<Style TargetType="{x:Type ListBox}">
    <Setter Property="ListBox.ItemTemplate">
        <Setter.Value>
            <DataTemplate>
                <ToggleButton Content="{Binding}" 
                              IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
                />
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

然后您可以使用ListBox的SelectionMode属性来处理SingleSelect与MultiSelect。


谢谢分享!我一直在想这是否是一个好的做法...但看起来还不错。 - GorillaApe
1
@LeeLouviere:您能详细说明为什么不行吗?这难道不是如何使用项目模板的最佳示例吗? - O. R. Mapper
4
这个例子有问题,因为它混淆了显示和行为。你通过数据模板改变了显示,但仍然保留了列表框的行为,而不是单选按钮或切换按钮的行为。键盘交互很奇怪。更好的解决方案是使用RadioButtons的ItemsControl,样式类似于ToggleButtons(详见其他被投票最高的答案)。 - Zarat
为什么要重复造轮子呢? - Wobbles

33
<RadioButton Content="Point" >
    <RadioButton.Template>
        <ControlTemplate>
            <ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                          Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
        </ControlTemplate>
    </RadioButton.Template>
</RadioButton>

对我来说可行,尽情享受吧!


我知道这不是问题所在。但如果按钮的行为应该像单选按钮一样,其中必须始终选择一个,该怎么办? - Karsten
为了回答自己的问题:请阅读Uday Kiran的答案 :-) - Karsten
2
这是一个不错的解决方案。但是,当我们有一个事件时,它会被调用两次。 - Khiem-Kim Ho Xuan
如果我添加的内容不仅仅是一个简单的字符串,我会收到“元素已经是另一个元素的子元素”的错误。有没有解决这个问题的方法? - Tobias Hoefer
这绝对是根据问题的提出方式(以及我所寻找的内容)最好、最简单的答案(感谢!)。第一个解决方案将显示和行为问题混合在一起,与WPF原则不一致,而排名最高的答案则不允许您取消选择当前选定的项目。 - Mike Marynowski
显示剩余3条评论

5
你可以在ToggleButton的Click事件上使用一个通用事件,通过VisualTreeHelper将组合控件(Grid、WrapPanel等)中所有ToggleButton.IsChecked设置为false,然后重新检查发送者。或者类似于这样的内容。
private void ToggleButton_Click(object sender, RoutedEventArgs e)
    {
        int childAmount = VisualTreeHelper.GetChildrenCount((sender as ToggleButton).Parent);

        ToggleButton tb;
        for (int i = 0; i < childAmount; i++)
        {
            tb = null;
            tb = VisualTreeHelper.GetChild((sender as ToggleButton).Parent, i) as ToggleButton;

            if (tb != null)
                tb.IsChecked = false;
        }

        (sender as ToggleButton).IsChecked = true;
    }

4

您可以在网格中放置单选按钮,并为单选按钮创建类似模板的按钮。然后,如果您不希望按钮被切换,只需以编程方式移除选中状态即可。


但是通过使用单选按钮,有没有一种方法可以取消选择所有按钮?一旦选择了,我看不到一个简单的方法来取消选择它! - code-zoop
RadioButton有一个IsChecked属性,当你需要时可以在代码中将其设置为false。 - Arsen Mkrtchyan

2
您可以尝试使用System.Windows.Controls.Primitives.ToggleButton
 <ToggleButton Name="btnTest" VerticalAlignment="Top">Test</ToggleButton>

然后针对 IsChecked 属性编写代码,以模仿单选按钮的效果。

 private void btnTest_Checked(object sender, RoutedEventArgs e)
 {
     btn2.IsChecked = false;
     btn3.IsChecked = false;
 }

这不是非常通用和可重用的...例如,它不能在ListBoxItem内使用。 - ANeves

0

我为RibbonToggleButtons做了这个,但也许对于普通的ToggleButtons也是一样的。

我使用了来自如何将RadioButtons绑定到枚举? 的EnumToBooleanConverter,将每个按钮的IsChecked绑定到一个“模式”枚举值上(使用ConverterParameter为此按钮指定枚举值。每个按钮应该有一个枚举值)

然后,为了防止取消选中已选中的按钮,请在每个RibbonToggleButtons的Click事件的代码后面放置以下代码:

    private void PreventUncheckRibbonToggleButtonOnClick ( object sender, RoutedEventArgs e ) {

        // Prevent unchecking a checked toggle button - so that one always remains checked
        // Cancel the click if you hit an already-checked button

        var button = (RibbonToggleButton)sender;
        if( button.IsChecked != null ) { // Not sure why checked can be null but that's fine, ignore it
            bool notChecked = ( ! (bool)button.IsChecked );
            if( notChecked ){ // I guess this means the click would uncheck it
                button.IsChecked = true; 
            }
        }
    }

0

为了帮助像Julian和我这样的人(两分钟前...)。你可以像这样从RadioButton派生。

class RadioToggleButton : RadioButton
{
    protected override void OnToggle()
    {
        if (IsChecked == true) IsChecked = IsThreeState ? (bool?)null : (bool?)false;
        else IsChecked = IsChecked.HasValue;
    }
}

然后,您可以像使用Uday Kiran 建议...一样使用它。

<Window x:Class="Sample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Sample"
    Title="MainWindow" Height="600" Width="600">
    <StackPanel>
        <local:RadioToggleButton Content="Button" Style="{StaticResource {x:Type ToggleButton}}" />
    </StackPanel>
</Window>

这种方法只允许一个ToggleButton被选中,同时也允许取消选中。


0
我拿了一些答案并添加了一些额外的代码。现在,您可以拥有不同组的切换按钮,它们就像一个切换按钮一样运作:
<UserControl.Resources>
    <Style x:Key="GroupToggleStyle" TargetType="ToggleButton">
        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding GroupName, RelativeSource={RelativeSource Self}}" Value="Group1"/>
                    <Condition Binding="{Binding BooleanProperty}" Value="true"/>
                </MultiDataTrigger.Conditions>
                <MultiDataTrigger.Setters>
                    <Setter Property="IsChecked" Value="true"/>
                </MultiDataTrigger.Setters>
            </MultiDataTrigger>
        </Style.Triggers>
    </Style>
</UserControl.Resources>

还有不同组的单选按钮,看起来像切换按钮:

<Radio Button GroupName="Group1" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group1" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group2" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group3" Style="{StaticResource {x:Type ToggleButton}}">

0
一个简单的实现方式是在代码后台中维护一个标志,例如:
ToggleButton _CurrentlyCheckedButton;

然后将一个单一的Checked事件处理程序分配给所有上下文ToggleButton:

private void ToggleButton_Checked(object sender, RoutedEventArgs e)
{
   if (_CurrentlyCheckedButton != null)
      _CurrentlyCheckedButton.IsChecked = false;

   _CurrentlyCheckedButton = (sender as ToggleButton);
}

还有,一个单独的未经检查的事件处理程序:

private void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
    if (_CurrentlyCheckedButton == (sender as ToggleButton))
       _CurrentlyCheckedButton = null;
}

通过这种方式,您可以拥有所需的“零或一”选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接