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开发者_如何学JAVA
(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.
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.
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.
精彩评论