Search code examples
wpftextboxcommandbindingwindowsformshost

WPF Commands Firing despite having Focus in WinForms TextBox


Given:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>

    <Grid.CommandBindings>
        <CommandBinding Command="Cut" 
                        Executed="CommandBinding_Executed"/>
    </Grid.CommandBindings>

    <TextBox x:Name="WpfTextBox" 
             VerticalAlignment="Center" 
             Text="Hello there" />

    <WindowsFormsHost Grid.Column="1" 
                      VerticalAlignment="Center">
        <wf:TextBox x:Name="WinFormsTextBox" 
                    Text="Hello there" />
    </WindowsFormsHost>
</Grid>

Pressing Ctrl+X in WinFormsTextBox causes CommandBinding_Executed to fire, but not when you are in WpfTextBox.

I wish to have the behaviour of WpfTextBox for WinFormsTextBox. I.e. The command should only fire when nothing has focus - it should work like a global view command or something.

Note: Adding a handler to the command's CanExecute event only aids in either preventing anything from happening in the WinFormsTextBox (Ctrl+X is completely swallowed when e.CanExecute is set to true - meaning no text is cut), or performs as normal.

Note 2: Cut is only an example, I would like a solution that would work for any command binding.

Note 3: The command should be able to fire from another control, if it had focus - like a ListView or something. Unless it had a TextBox that had focus inside of it (think edit mode).

I am not sure anything can really be done, I don't want to accept having to add specific handling in the CommandBinding_Executed method. But, C'est la vie.


Solution

  • A slightly silly solution for a slightly silly problem. This is a simple version of my final solution:

    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.ContinueRouting = IsFocusInWinFormsInputControl();
    }
    
    private static bool IsFocusInWinFormsInputControl()
    {
        // Try get focus control
        WinForms.Control focusedWinFormsControl = GetFocusedWinFormsControl();
    
        // Is there anything and is it a textbox etc?
        return focusedWinFormsControl != null &&
            (focusedWinFormsControl is WinForms.TextBox ||
             focusedWinFormsControl is WinForms.RichTextBox);
    }
    
    private static WinForms.Control GetFocusedWinFormsControl()
    {
        // Try get focused WinForms control
        IntPtr focusedControlHandle = GetFocus();
    
        WinForms.Control focusedControl = null;
        if (focusedControlHandle != IntPtr.Zero)
        {
            // Note: If focused Control is not a WinForms control, this will return null
            focusedControl = WinForms.Control.FromHandle(focusedControlHandle);
        }
    
        return focusedControl;
    }
    
    [DllImport("user32.dll")]
    private static extern IntPtr GetFocus();
    

    Basically, add in command validation logic to only execute the command if we are outside a WinForms TextBox.