I discover a very strange behavior of WindowsFormsHost
in WPF. I find that if a WPF control doesn't have WindowsFormsHost
as a child control, then IsKeyboardFocusWithinChanged
fires properly-- it is fired whenever the WPF control gains or loses focuses, and the variable IsKeyboardFocusWithin
is toggled as expected ( true
when the control gains focus, false
when loses focus).
But, if I host a WindowsFormHost
in WPF, then after a short while, the IsKeyboardFocusWithinChanged
event is no longer fired for both the WPF mother control and the WindowsFormHost
child control.
I can't find in MSDN documentation or SO why so, any reason?
This is my code:
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" IsKeyboardFocusWithinChanged="Window_IsKeyboardFocusWithinChanged">
<Grid Name="grid1">
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
host = new System.Windows.Forms.Integration.WindowsFormsHost();
var mtbDate = new System.Windows.Forms.MaskedTextBox("00/00/0000");
host.Child = mtbDate;
host.IsKeyboardFocusWithinChanged += Host_IsKeyboardFocusWithinChanged;
grid1.Children.Add(host);
}
private void Host_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(host.IsKeyboardFocusWithin.ToString()+" blah");
}
private System.Windows.Forms.Integration.WindowsFormsHost host;
private void Window_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(IsKeyboardFocusWithin.ToString());
}
}
When the lines involving WindowsFormHost
are commented out, then IsKeyboardFocusWithin
is true
whenever the control gains focus, and false
when the control loses focus.
When the lines involving WindowsFormHost
are there, then IsKeyboardFocusWithin
is true
, until I click on the control, and then host.IsKeyboardFocusWithin
becomes false
, and IsKeyboardFocusWithin
also becomes false
, and then, no matter what I do, IsKeyboardFocusWithinChanged
event will never be fired again.
Optimized previous solution to support multiple WindowsFormsHost
elements in a Window
. Also, updated style to highlight focused control with green border if IsKeyboardFocusWithin
property is true
.
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="250" Width="325">
<Window.Resources>
<ResourceDictionary>
<Style TargetType="Border">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Child.IsKeyboardFocusWithin}" Value="True">
<Setter Property="BorderBrush" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button x:Name="hiddenBtn" Height="1" Width="1" />
<StackPanel Grid.Column="0" x:Name="leftPanel" Margin="5">
<Label HorizontalContentAlignment="Right">Start Date</Label>
<Label HorizontalContentAlignment="Right">End Date</Label>
<Label HorizontalContentAlignment="Right">Phone Number</Label>
<Label HorizontalContentAlignment="Right">Zip Code</Label>
</StackPanel>
<StackPanel Grid.Column="1" x:Name="rightPanel" Margin="5">
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
GenerateControls();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private const int WM_KILLFOCUS = 0x0008;
private const int WM_ACTIVATEAPP = 0x001c;
private const int WM_PARAM_FALSE = 0x00000000;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Handle messages...
if (msg == WM_KILLFOCUS)
{
Console.WriteLine(wParam + " " + lParam);
//suppress kill focus message if host has keyboardfocus, else don't
var hosts = FindVisualChildren<WindowsFormsHost>(this);
var focusedControlHwnd = wParam.ToInt32();
if(focusedControlHwnd != 0)
{
handled = hosts.Any(x => x.Child.Handle.ToInt32() == focusedControlHwnd);
}
}
else if (msg == WM_ACTIVATEAPP && wParam.ToInt32() == WM_PARAM_FALSE)
{
//now the kill focus could be suppressed event during window switch, which we want to avoid
//so we make sure that the host control property is updated
var hosts = FindVisualChildren<WindowsFormsHost>(this);
if (hosts.Any(x => x.IsKeyboardFocusWithin))
hiddenBtn.Focus();
}
return IntPtr.Zero;
}
private void GenerateControls()
{
System.Windows.Forms.MaskedTextBox maskedTextBox;
System.Windows.Forms.Integration.WindowsFormsHost host;
host = new System.Windows.Forms.Integration.WindowsFormsHost();
maskedTextBox = new System.Windows.Forms.MaskedTextBox("00/00/0000");
host.Child = maskedTextBox;
rightPanel.Children.Add(new Border() { Child = host });
host = new System.Windows.Forms.Integration.WindowsFormsHost();
maskedTextBox = new System.Windows.Forms.MaskedTextBox("00/00/0000");
host.Child = maskedTextBox;
rightPanel.Children.Add(new Border() { Child = host });
host = new System.Windows.Forms.Integration.WindowsFormsHost();
maskedTextBox = new System.Windows.Forms.MaskedTextBox("(000)-000-0000");
host.Child = maskedTextBox;
rightPanel.Children.Add(new Border() { Child = host });
host = new System.Windows.Forms.Integration.WindowsFormsHost();
maskedTextBox = new System.Windows.Forms.MaskedTextBox("00000");
host.Child = maskedTextBox;
rightPanel.Children.Add(new Border() { Child = host });
}
public static IEnumerable<T> FindVisualChildren<T>(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 FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
}
Screenshot
As Hans Passant mentioned in the comment, this behavior is caused due to the fact the WindowsFormsHost
and the MaskedTextBox
have different Hwnd(s).
The first time you click on host-control, the child control will get focus, and the IsKeyboardFocusedWithin is set properly. But as soon as the child control gets the focus, the OS notices the difference in Hwnd and sends the kill-focus message to WPF window - which in turn sets the IsKeyboardFocusedWithin as false.
What you can do is add a WndProc
hook to your WPF main window, and suppress the kill-focus message - only when the host-control's IsKeyboardFocusedWithin
value is true.
However, there is a side effect - when you do switch away from WPF window, the host-control's IsKeyboardFocusedWithin
value might stay true. In order to resolve this, you can use a simple traversal trick to shift the focus when window-diactivated message is sent and hence have the property IsKeyboardFocusedWithin updated according to current state.
Source code sample: I used a StackPanel instead of a Grid, in order to display the TextBox(s)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
host = new System.Windows.Forms.Integration.WindowsFormsHost();
var mtbDate = new System.Windows.Forms.MaskedTextBox("00/00/0000");
host.Child = mtbDate;
host.IsKeyboardFocusWithinChanged += Host_IsKeyboardFocusWithinChanged;
stackPanel1.Children.Add(host);
textBox1 = new TextBox();
stackPanel1.Children.Add(textBox1);
textBox2 = new TextBox();
stackPanel1.Children.Add(textBox2);
}
private void Host_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(host.IsKeyboardFocusWithin.ToString() + " blah");
textBox1.Text = $"Host.IsKeyboardFocusedWithin = {host.IsKeyboardFocusWithin}";
}
private System.Windows.Forms.Integration.WindowsFormsHost host;
private TextBox textBox1;
private TextBox textBox2;
private void Window_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(IsKeyboardFocusWithin.ToString());
textBox2.Text = $"Window.IsKeyboardFocusedWithin = {IsKeyboardFocusWithin}";
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private const int WM_KILLFOCUS = 0x0008;
private const int WM_ACTIVATEAPP = 0x001c;
private const int WM_PARAM_FALSE = 0x00000000;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Handle messages...
if (msg == WM_KILLFOCUS)
{
//suppress kill focus message if host has keyboardfocus, else don't
handled = host.IsKeyboardFocusWithin;
}
else if (msg == WM_ACTIVATEAPP && wParam.ToInt32() == WM_PARAM_FALSE)
{
//now the kill focus could be suppressed event during window switch, which we want to avoid
//so we make sure that the host control property is updated by traversal (or any other method)
if (host.IsKeyboardFocusWithin)
{
host.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
return IntPtr.Zero;
}
}
And, the result will look like this:
With Focus
Without Focus
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With