Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Keyboard.FocusedElement null when focus is inside WindowsFormsHost? It breaks WPF command routing

I have a custom RoutedUICommand MyCommand which gets executed via ICommand.Execute. The top window has a binding to handle it:

<Window.CommandBindings>
    <CommandBinding Command="local:MainWindow.MyCommand" CanExecute="CanExecuteCommmand" Executed="CommandExecuted"/>
</Window.CommandBindings>

This is the only handler for this command. I also have a WindowsFormsHost with WinForms' TextBox control inside (for the demo purpose). When the focus is inside this TextBox, MyCommand doesn't reach the top window. When the focus is inside a WPF's native TextBox, the command handler gets invoked as expected.

I've figured out this is happening because Keyboard.FocusedElement is null when focus is inside WindowsFormsHost. Why is it null in this case, is it a WPF bug or design feature? Am I missing something?

I believe the command should be reaching the top window, regardless of where the focus is (when it's the only handler in the visual tree and FocusManager.IsFocusScope is set up correctly). I have a related question about that.

The project sources are available here.

XAML:

<Window x:Class="WpfCommandTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfCommandTest" 
        xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"  
        Title="MainWindow" Height="480" Width="640" Background="Gray">

    <Window.CommandBindings>
        <CommandBinding Command="local:MainWindow.MyCommand" CanExecute="CanExecuteCommmand" Executed="CommandExecuted"/>
    </Window.CommandBindings>

    <StackPanel Margin="20,20,20,20">
        <TextBox Name="textBoxOutput" Focusable="True" IsTabStop="True" Height="150" Text="WPF TextBox&#x0a;"/>
        <WindowsFormsHost Focusable="True" KeyboardNavigation.IsTabStop="True"  Height="150">
            <wf:TextBox x:Name="textBoxWf" Text="WinForms TextBox" />
        </WindowsFormsHost>
        <Button FocusManager.IsFocusScope="True" Name="btnTest" Focusable="False" IsTabStop="False" Content="Test (ICommand.Execute)" Click="btnTest_Click" Width="200"/>
        <Button FocusManager.IsFocusScope="True" Focusable="False" IsTabStop="False" Content="Test (Command property)" Command="local:MainWindow.MyCommand" Width="200"/>
        <Button FocusManager.IsFocusScope="True" Name="btnClearFocus" Focusable="False" IsTabStop="False" Content="Clear Focus" Click="btnClearFocus_Click" Width="200"/>
    </StackPanel>

</Window>

C#:

using System;
using System.Windows;
using System.Windows.Input;

namespace WpfCommandTest
{
    public partial class MainWindow : Window
    {
        public static readonly RoutedUICommand MyCommand = new RoutedUICommand("MyCommand", "MyCommand", typeof(MainWindow));
        const string Null = "null";

        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += (s, e) => textBoxOutput.Focus(); // set focus on the TextBox
        }

        void CanExecuteCommmand(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        void CommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            var routedCommand = e.Command as RoutedCommand;
            var commandName = routedCommand != null ? routedCommand.Name : Null;
            Log("*** Executed: {0} ***, {1}", commandName, FormatFocus());
        }

        void btnTest_Click(object sender, RoutedEventArgs e)
        {
            Log("btnTest_Click, {0}", FormatFocus());
            ICommand command = MyCommand;
            if (command.CanExecute(null))
                command.Execute(null);
        }

        void btnClearFocus_Click(object sender, RoutedEventArgs e)
        {
            FocusManager.SetFocusedElement(this, this);
            Keyboard.ClearFocus();
            Log("btnClearFocus_Click, {0}", FormatFocus());
        }

        void Log(string format, params object[] args)
        {
            textBoxOutput.AppendText(String.Format(format, args) + Environment.NewLine);
            textBoxOutput.CaretIndex = textBoxOutput.Text.Length;
            textBoxOutput.ScrollToEnd();
        }

        string FormatType(object obj)
        {
            return obj != null ? obj.GetType().Name : Null;
        }

        string FormatFocus()
        {
            return String.Format("focus: {0}, keyboard focus: {1}",
                FormatType(FocusManager.GetFocusedElement(this)),
                FormatType(Keyboard.FocusedElement));
        }
    }
}
like image 767
noseratio Avatar asked Aug 26 '13 06:08

noseratio


1 Answers

To complete the interop Form-WPF you need to do this:

App.xaml.cs:

    public partial class App : Application
      {
      protected override void OnStartup(StartupEventArgs e)
        {
        WindowsFormsHost.EnableWindowsFormsInterop();
        base.OnStartup(e);
        }
      }
like image 85
Ricky Avatar answered Nov 15 '22 14:11

Ricky