我如何解决在 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;
}
环境信息
- Visual Studio 2013 更新 4
- Windows 8 / Windows 10
- .NET Framework 4.5
观察结果
我观察到,如果我单击按钮并在释放鼠标左键之前将其拖离,则不会遇到此问题。当您调试并单步执行 Checked
事件时也会发生这种情况(因为同样,自从调试器获得焦点后,ToggleButton
从未接收到 MouseLeftButtonUp
事件)。
问题
- 为什么按钮变成未选中状态?
- 这是 WPF 中的错误还是我只是做错了什么?
- 解决此问题的好方法是什么? (如果有好的、干净的解决方法)
让我知道这是否适合你。
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
然后将控件设置为 checked
和 Foreground
到 Red
.
<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);
}
}
将 的所有实例替换为 会产生问题文本要求部分中指定的行为。
要求
当用户切换到其中一个切换按钮时,我希望选中它并取消选中任何其他切换按钮。如果用户点击切换按钮,它的状态应该从选中变为未选中或从未选中变为选中。
问题
在 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;
}
环境信息
- Visual Studio 2013 更新 4
- Windows 8 / Windows 10
- .NET Framework 4.5
观察结果
我观察到,如果我单击按钮并在释放鼠标左键之前将其拖离,则不会遇到此问题。当您调试并单步执行 Checked
事件时也会发生这种情况(因为同样,自从调试器获得焦点后,ToggleButton
从未接收到 MouseLeftButtonUp
事件)。
问题
- 为什么按钮变成未选中状态?
- 这是 WPF 中的错误还是我只是做错了什么?
- 解决此问题的好方法是什么? (如果有好的、干净的解决方法)
让我知道这是否适合你。
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
然后将控件设置为 checked
和 Foreground
到 Red
.
<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);
}
}
将