dotnet / dotnet/wpf

Focus is not recovered from control hosted in WindowsFormsHost inside a Popup

Open
#9,257 3 comments 0 reactions 0 assignees View on GitHub
Investigate
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.