Focus is not recovered from control hosted in WindowsFormsHost inside a Popup
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
### Description
When using a WindowsFormsHost to host a Windows Forms control in a WPF Popup, focus gets trapped in the Windows forms control.
### Reproduction Steps
Create a WPF windows with the following XAML definition
```xml
```
When opened, focus the first (WPF TextBox), then focus the second (Windows Forms) textbox and then focus the WPF textbox again.
### Expected behavior
The focus should be moved to the WPF textbox
### Actual behavior
The input caret is moved to the WPF textbox, so it looks as if it has focus, but any input goes into the Windows Forms textbox
### Regression?
No. The same problem exists in .NET 4.8 and .NET 8
### Known Workarounds
Listening to the `Keyboard.GotKeyboardFocusEvent` and, after each such event verify that the Win32 focus is on a WPF owned HWND, and if not force the focus back to the WPF window that WPF though had got the keyboard focus.
```cs
using System;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace WinFormsIteropTest
{
///
/// Interaction logic for App.xaml
///
public partial class App : Application
{
private bool ignoreFocusChange;
public App()
{
InitializeComponent();
EventManager.RegisterClassHandler(typeof(Popup), Keyboard.GotKeyboardFocusEvent, new RoutedEventHandler(OnWindowGotFocus));
}
private void OnWindowGotFocus(object sender, RoutedEventArgs e)
{
var popup = (UIElement)sender;
var windowHandle = ((HwndSource)PresentationSource.FromVisual(popup))?.Handle;
var focusedNativeWindow = NativeMethods.GetFocus();
if (ignoreFocusChange)
{
return;
}
if (focusedNativeWindow == windowHandle)
{
return;
}
popup.Dispatcher.InvokeAsync(() => VerifyPopupFocus(popup, e), System.Windows.Threading.DispatcherPriority.Input);
}
private void VerifyPopupFocus(UIElement popup, RoutedEventArgs e)
{
var focusedElement = Keyboard.FocusedElement;
var focusedNativeWindow = NativeMethods.GetFocus();
if (focusedElement is not Visual focusedVisual
|| PresentationSource.FromVisual(focusedVisual) is not HwndSource nativeWindow)
{
// The element with keyboard focus is not a WPF element
return;
}
var focusScope = FocusManager.GetFocusScope((DependencyObject)e.Source);
var focusScopeWindowHandle = ((HwndSource)PresentationSource.FromVisual((Visual)focusScope))?.Handle;
if (focusedNativeWindow == focusScopeWindowHandle)
{
// Focused native window is the focus scope window no change needed
return;
}
var nativeWindowHandle = nativeWindow.Handle;
if (nativeWindowHandle == focusedNativeWindow)
{
// The focus is within WPF. Nothing needs to be done.
return;
}
var activeWindow = System.Windows.Application.Current.Windows.Cast()
.FirstOrDefault(x => x.IsActive);
var windowToFocus = nativeWindowHandle;
var focusScopeToFocus = focusScope;
if (activeWindow != null)
{
var activeWindowHandle = new WindowInteropHelper(activeWindow).Handle;
if (activeWindowHandle == focusedNativeWindow)
{
// The focus is within WPF. Nothing needs to be done.
return;
}
windowToFocus = activeWindowHandle;
focusScopeToFocus = activeWindow;
}
ForceFocusToWpfElement(windowToFocus, focusScopeToFocus, focusedElement);
}
private void ForceFocusToWpfElement(IntPtr windowHandle, DependencyObject focusScope, IInputElement focusedElement)
{
// The focused HWND is not a WPF window, but WPF think it has Keyboard Focus.
// This means that the focus is out of sync. (The HWND that has focused will receive keyboard input,
// but WPF will render the UI as if the focused element has focus.
// To fox this state, we need to focus the WPF window so that it receives the input.
// We then have to restore the focus to the previously focused element in WPF.
ignoreFocusChange = true;
try
{
NativeMethods.SetFocus(windowHandle);
var elementFocusedByMovingFocusToWpfWindow = FocusManager.GetFocusedElement(focusScope);
if (elementFocusedByMovingFocusToWpfWindow != null
&& elementFocusedByMovingFocusToWpfWindow != focusedElement)
{
// Focus them element from WPF as well, since WPF seems to not fully update its internal state
// when just restoring focus when the main window receives focus by a native call.
elementFocusedByMovingFocusToWpfWindow.Focus();
}
focusedElement.Focus();
}
finally
{
ignoreFocusChange = false;
}
}
private static class NativeMethods
{
[DllImport("user32.dll")]
internal static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
internal static extern IntPtr GetFocus();
}
}
}
```
### Impact
_No response_
### Configuration
.NET 8 and .NET 4.8 on Windows 10
### Other information
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.