Describe the bug
KeyboardNavigationEx.AlwaysShowFocusVisual may cause the program to enter an infinite loop.
When the user holds down the keyboard arrow key (Left or Right), the input focus rapidly switches between two buttons. After releasing the key, the focus continues to switch back and forth endlessly, and the application becomes unresponsive.
I investigated the cause and found that it's related to the use of Dispatcher.BeginInvoke(DispatcherPriority.Background), which causes the focus update actions to be queued and executed later, leading to an infinite recursion.
Steps to reproduce
https://github.com/myd7349/ControlzEx/tree/focus-bug/src/FocusDemo
Create a Window containing two buttons:
<Window x:Class="FocusDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FocusDemo"
xmlns:controlzEx="urn:controlzex"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="controlzEx:KeyboardNavigationEx.AlwaysShowFocusVisual"
Value="True" />
<Setter Property="Margin" Value="10" />
<Setter Property="Width" Value="200" />
<Setter Property="Height" Value="30" />
</Style>
</StackPanel.Resources>
<Button Content="123" />
<Button Content="456" />
</StackPanel>
</Window>
This window contains two buttons, both of which have controlzEx:KeyboardNavigationEx.AlwaysShowFocusVisual set to True.
When running this window, we can switch the input focus between the two buttons using the mouse or the keyboard arrow keys.
Now, press and hold either the Left or Right arrow key. The input focus will rapidly toggle between the two buttons.
After releasing the key, the focus keeps switching rapidly between the buttons and never stops, effectively freezing the application.
Expected behavior
The focus should toggle between buttons normally and stop immediately after the arrow key is released.
The application should remain responsive.
Actual behavior
After holding down the arrow key, focus switching becomes continuous and uncontrollable.
Even after releasing the key, the focus continues toggling rapidly between the two buttons indefinitely.
Environment
- ControlzEx: 7.0.1
- Windows 11 25H2
- .NET 8.0
Additional analysis
I looked into the source code and added some trace logs at three positions (position 1, position 2, and position 3) to analyze the timing of element?.Dispatcher.BeginInvoke.
I also added a Guid at position 2 and position 3 to help identify each invocation.
namespace ControlzEx
{
public sealed class KeyboardNavigationEx
{
public static void Focus(UIElement? element)
{
var guid = Guid.NewGuid().ToString();
System.Diagnostics.Trace.WriteLine($"=============== {guid} Focus {element}"); // position 2
element?.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
System.Diagnostics.Trace.WriteLine($"=============== {guid} BeginInvoke {element}"); // position 3
var keybHack = Instance;
var alwaysShowFocusVisual = keybHack.AlwaysShowFocusVisualInternal;
keybHack.AlwaysShowFocusVisualInternal = true;
try
{
Keyboard.Focus(element);
keybHack.ShowFocusVisualInternal();
}
finally
{
keybHack.AlwaysShowFocusVisualInternal = alwaysShowFocusVisual;
}
}));
}
public static readonly DependencyProperty AlwaysShowFocusVisualProperty
= DependencyProperty.RegisterAttached("AlwaysShowFocusVisual",
typeof(bool),
typeof(KeyboardNavigationEx),
new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, OnAlwaysShowFocusVisualChanged));
private static void OnAlwaysShowFocusVisualChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
if (dependencyObject is UIElement element && args.NewValue != args.OldValue)
{
element.GotFocus -= FrameworkElementGotFocus;
if ((bool)args.NewValue)
{
element.GotFocus += FrameworkElementGotFocus;
}
}
}
private static void FrameworkElementGotFocus(object? sender, RoutedEventArgs e)
{
System.Diagnostics.Trace.WriteLine($"=============== FrameworkElementGotFocus {sender}"); // position 1
Focus(sender as UIElement);
}
}
}
In normal situations, when switching focus between button 123 and button 456, the logs look like this:
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 123
=============== a78326d2-7fc5-44a5-8ff8-6b00eb7fc236 Focus System.Windows.Controls.Button: 123
=============== a78326d2-7fc5-44a5-8ff8-6b00eb7fc236 BeginInvoke System.Windows.Controls.Button: 123
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 456
=============== 3a97ebba-a543-463d-9b37-ab94ba4d78a5 Focus System.Windows.Controls.Button: 456
=============== 3a97ebba-a543-463d-9b37-ab94ba4d78a5 BeginInvoke System.Windows.Controls.Button: 456
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 123
=============== e9ac5e40-183b-4508-bfab-8c057c14bdf0 Focus System.Windows.Controls.Button: 123
=============== e9ac5e40-183b-4508-bfab-8c057c14bdf0 BeginInvoke System.Windows.Controls.Button: 123
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 456
=============== 23cbccf6-5510-46e2-b1a3-7033eaddfade Focus System.Windows.Controls.Button: 456
=============== 23cbccf6-5510-46e2-b1a3-7033eaddfade BeginInvoke System.Windows.Controls.Button: 456
However, when the issue occurs, the logs look like this:
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 456
=============== 2a79c275-7058-4017-a559-35eb6fa637b1 Focus System.Windows.Controls.Button: 456
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 123
=============== c7977748-914e-49ce-9964-06d1d0deedb8 Focus System.Windows.Controls.Button: 123
=============== 2a79c275-7058-4017-a559-35eb6fa637b1 BeginInvoke System.Windows.Controls.Button: 456
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 456
=============== acd639df-ef9e-4d81-b6fe-10d9b6edf16d Focus System.Windows.Controls.Button: 456
=============== c7977748-914e-49ce-9964-06d1d0deedb8 BeginInvoke System.Windows.Controls.Button: 123
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 123
=============== 5d8b475b-6c9f-4f08-a8bb-59aa3f052586 Focus System.Windows.Controls.Button: 123
=============== acd639df-ef9e-4d81-b6fe-10d9b6edf16d BeginInvoke System.Windows.Controls.Button: 456
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 456
=============== 38805b56-5e95-474b-85d3-c141bcd25146 Focus System.Windows.Controls.Button: 456
=============== 5d8b475b-6c9f-4f08-a8bb-59aa3f052586 BeginInvoke System.Windows.Controls.Button: 123
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 123
=============== ea627c77-bb12-4f7f-b95b-6be353ebec1c Focus System.Windows.Controls.Button: 123
=============== 38805b56-5e95-474b-85d3-c141bcd25146 BeginInvoke System.Windows.Controls.Button: 456
=============== FrameworkElementGotFocus System.Windows.Controls.Button: 456
=============== 10284abf-259a-4f90-a88c-740631a6c66f Focus System.Windows.Controls.Button: 456
As you can see, since BeginInvoke(DispatcherPriority.Background) executes asynchronously,
when the arrow key is held down, multiple invocations get queued before the previous ones finish.
As a result, focus events pile up and recursively trigger new focus changes, leading to an infinite loop.
Screenshots
N/A
Describe the bug
KeyboardNavigationEx.AlwaysShowFocusVisualmay cause the program to enter an infinite loop.When the user holds down the keyboard arrow key (Left or Right), the input focus rapidly switches between two buttons. After releasing the key, the focus continues to switch back and forth endlessly, and the application becomes unresponsive.
I investigated the cause and found that it's related to the use of
Dispatcher.BeginInvoke(DispatcherPriority.Background), which causes the focus update actions to be queued and executed later, leading to an infinite recursion.Steps to reproduce
https://github.com/myd7349/ControlzEx/tree/focus-bug/src/FocusDemo
Create a
Windowcontaining two buttons:This window contains two buttons, both of which have
controlzEx:KeyboardNavigationEx.AlwaysShowFocusVisualset toTrue.When running this window, we can switch the input focus between the two buttons using the mouse or the keyboard arrow keys.
Now, press and hold either the Left or Right arrow key. The input focus will rapidly toggle between the two buttons.
After releasing the key, the focus keeps switching rapidly between the buttons and never stops, effectively freezing the application.
Expected behavior
The focus should toggle between buttons normally and stop immediately after the arrow key is released.
The application should remain responsive.
Actual behavior
After holding down the arrow key, focus switching becomes continuous and uncontrollable.
Even after releasing the key, the focus continues toggling rapidly between the two buttons indefinitely.
Environment
Additional analysis
I looked into the source code and added some trace logs at three positions (
position 1,position 2, andposition 3) to analyze the timing ofelement?.Dispatcher.BeginInvoke.I also added a
Guidatposition 2andposition 3to help identify each invocation.In normal situations, when switching focus between button 123 and button 456, the logs look like this:
However, when the issue occurs, the logs look like this:
As you can see, since
BeginInvoke(DispatcherPriority.Background)executes asynchronously,when the arrow key is held down, multiple invocations get queued before the previous ones finish.
As a result, focus events pile up and recursively trigger new focus changes, leading to an infinite loop.
Screenshots
N/A