Pergunta

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.

Foi útil?

Solução 2

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.

Outras dicas

WPF commands are routed and you defined the CommandBinding for the Ctrl+X command in the parent control of WindowsFormsHost. So if you want it to be handled only in the WPF TextBox remove your CommandBinding from the Grid and put it there:

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

As commands are routed, the Ctrl+X command will be handled by the first parent having a binding for this command. As long as your focus is in the scope of the Grid and you execute Ctrl+X command, the Grid command bindings will handle it.


Here is an excellent article about routed events and commands in WPF : Understanding Routed Events and Commands In WPF


EDIT:

If you don't want the command to be handled when in a TextBox then you have to define the CommandBindings only where Ctrl+X makes sense for you. I do not think you have another solution. Anyway, typically the ApplicationCommands like Cut are contextual to a specific scope, for example a RichTextBox or a ListBox.


EDIT:

You cannot block WindowsFormsHost firing underlying routed commands. But what you can do is just to remove the host from the CommandBindings scope:

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

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

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

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

Of course if you have much more objects to layout it can be a bit tricky but it will work. Just remove the objects you do not want to handle commands from the scope of the CommandBindings.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top