Updated Answer - 05/11
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
Previous Answer - 05/05
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
IsKeyboardFocusWithin=true
? It's a getter property, no setter – Coop