我如何解决在 GotFocus 事件中为 ToggleButton 设置 IsChecked 导致同时调用 Checked 和 Unchecked 事件?

How can I work around setting IsChecked for ToggleButton in GotFocus event causing both Checked and Unchecked events to be called?

要求

当用户切换到其中一个切换按钮时,我希望选中它并取消选中任何其他切换按钮。如果用户点击切换按钮,它的状态应该从选中变为未选中或从未选中变为选中。

问题

GotFocus 事件中为 ToggleButton 设置 IsChecked 属性 会导致调用 Checked 事件,然后神秘地取消选中 ToggleButton 并导致调用 Unchecked 事件。

代码

这是我的实验室项目中演示这一点的示例代码。

XAML:

<Grid>
    <StackPanel x:Name="ToggleButtonContainer" Orientation="Horizontal" VerticalAlignment="Top">
        <ToggleButton Content="Toggle 1" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
        <ToggleButton Content="Toggle 2" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
        <ToggleButton Content="Toggle 3" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
        <ToggleButton Content="Toggle 4" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
        <ToggleButton Content="Toggle 5" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
    </StackPanel>    
</Grid>

代码:

    private void ToggleButton_Checked(object sender, RoutedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("INFO:  ToggleButton_Checked called by {0}", sender);
        foreach (var toggleButton in ToggleButtonContainer.Children)
        {
            if (toggleButton != sender &&
                (toggleButton as ToggleButton).IsChecked.HasValue &&
                (toggleButton as ToggleButton).IsChecked.Value)
            {
                (toggleButton as ToggleButton).IsChecked = false;
            }
        }
    }

    private void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("INFO:  ToggleButton_Unchecked called by {0}", sender);
    }

    private void ToggleButton_GotFocus(object sender, RoutedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("INFO:  ToggleButton_GotFocus called by {0}", sender);
        (sender as ToggleButton).IsChecked = true;
    }

环境信息

观察结果

我观察到,如果我单击按钮并在释放鼠标左键之前将其拖离,则不会遇到此问题。当您调试并单步执行 Checked 事件时也会发生这种情况(因为同样,自从调试器获得焦点后,ToggleButton 从未接收到 MouseLeftButtonUp 事件)。

问题

让我知道这是否适合你。

XAML

<Grid>
        <StackPanel x:Name="ToggleButtonContainer" Orientation="Horizontal" VerticalAlignment="Top">
            <RadioButton Style="{StaticResource {x:Type ToggleButton}}" Content="Toggle 1" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
            <RadioButton Style="{StaticResource {x:Type ToggleButton}}" Content="Toggle 2" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
            <RadioButton Style="{StaticResource {x:Type ToggleButton}}" Content="Toggle 3" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
            <RadioButton Style="{StaticResource {x:Type ToggleButton}}" Content="Toggle 4" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
            <RadioButton Style="{StaticResource {x:Type ToggleButton}}" Content="Toggle 5" Margin="2" Padding="4" GotFocus="ToggleButton_GotFocus" Checked="ToggleButton_Checked" Unchecked="ToggleButton_Unchecked" />
        </StackPanel>
    </Grid>

代码:

 private void ToggleButton_Checked(object sender, RoutedEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("INFO:  ToggleButton_Checked called by {0}", sender);
        }

        private void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("INFO:  ToggleButton_Unchecked called by {0}", sender);
        }

        private void ToggleButton_GotFocus(object sender, RoutedEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("INFO:  ToggleButton_GotFocus called by {0}", sender);
            (sender as ToggleButton).IsChecked = true;
        }

使用 GroupName 属性实现切换控件之间的互斥。

<StackPanel>
    <RadioButton GroupName="Os" Content="Windows XP" IsChecked="True"/>
    <RadioButton GroupName="Os" Content="Windows Vista" />
    <RadioButton GroupName="Os" Content="Windows 7" />
    <RadioButton GroupName="Office" Content="Microsoft Office 2007" IsChecked="True"/>
    <RadioButton GroupName="Office" Content="Microsoft Office 2003"/>
    <RadioButton GroupName="Office" Content="Open Office"/>
</StackPanel>

简单的风格就可以做到。为了测试目的,如果切换按钮是 checked,我将 Margin 设置为 5;如果得到 focused 然后将控件设置为 checkedForegroundRed.

<Style TargetType="ToggleButton" >
    <Setter Property="IsChecked" Value="False" />
    <Style.Triggers>
       <Trigger Property="IsChecked" Value="True">
        <Setter Property="Margin" Value="5" />
      </Trigger>
      <Trigger Property="IsFocused" Value="True">
        <Setter Property="IsChecked" Value="True" />
        <Setter Property="Foreground" Value="Red" />
       </Trigger>
   </Style.Triggers>
   </Style>

Why does the button become unchecked?

按钮变为未选中状态,因为 IsChecked 属性 在 GotFocus 事件中发生了更改,但随后在 OnClick 方法触发时又改回原样。

Is this a bug in WPF or am I just doing something incorrectly?

看来,当前的 ToggleButton 的设计意图是 selecting/unselecting 它的唯一控件是鼠标左键单击或空格键。

What is a good work around to resolve this issue? (if there is a good, clean work around)

感谢 this post 以及与 Janne Matikainen 的对话,我得以提出以下解决方案:

public class MyToggleButton : ToggleButton
{
    protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
    {
        if (e.KeyboardDevice.IsKeyDown(Key.Tab))
        {
            IsChecked = true;
            base.OnGotKeyboardFocus(e);
        }
    }

    protected override void OnLostFocus(System.Windows.RoutedEventArgs e)
    {
        IsChecked = false;
        base.OnLostFocus(e);
    }
}

的所有实例替换为 会产生问题文本要求部分中指定的行为。