-
sfernandez
Site Admin
- Posts: 2991
- Joined:
Re: Disable keyboard shortscuts when in gui elements
Correct me if I'm wrong, but doing this has the same problem as using the KeyTrigger, because if you have the focus in a TextBox, and press the 'R' key, IView::KeyDown() will return false (TextBox only handles specific keys it needs like backspace or delete, arrow keys...), so if there is a shortcut associated with the 'R' key it will be executed. Then the 'R' key is translated into the corresponding char and sent to the IView::Char(), and TextInput event is generated, so TextBox writes the 'r' letter too.The current state is that we are handling half of the shortcuts only after IView::KeyDown() returned explicit "false", outside Noesis. Which is working perfectly fine for actions which are entirely independent from the UI, with no unwanted side effects.
Totally agree, that is why we are trying to offer the best approach with the tools we have available in Noesis.And maintaining keybindings in two different systems is not desirable either.
As I said before that is not the case, because KeyDown will always occur before TextInput. Which leads me to another possible solution, that could be more in line with what you are explaining. Implement a ShortcutTrigger that listens to TextInput event and fires the action on the KeyUp event, if no TextBox handled the TextInput event, something like this:Trying to move the shortcut definitions to Noesis scope has led up to using KeyTrigger (as the only available tool), but with the result of evaluation order of shortcuts and text input getting flipped
Code: Select all
void ShortcutTrigger::OnAttached()
{
ParentClass::OnAttached();
Noesis::UIElement* source = GetAssociatedObject();
if (source != 0)
{
source->TextInput() += MakeDelegate(this, &ShortcutTrigger::OnTextInput);
source->KeyUp() += MakeDelegate(this, &ShortcutTrigger::OnKeyUp);
}
}
void ShortcutTrigger::OnDetaching()
{
Noesis::UIElement* source = GetAssociatedObject();
if (source != 0)
{
source->TextInput() -= MakeDelegate(this, &ShortcutTrigger::OnTextInput);
source->KeyUp() -= MakeDelegate(this, &ShortcutTrigger::OnKeyUp);
}
ParentClass::OnDetaching();
}
bool ShortcutTrigger::CheckModifiers() const
{
Noesis::Keyboard* keyboard = GetAssociatedObject()->GetKeyboard();
return GetModifiers() == keyboard->GetModifiers();
}
void ShortcutTrigger::OnTextInput(Noesis::BaseComponent*, const Noesis::TextCompositionEventArgs&)
{
mTextInputIgnored = true;
}
void ShortcutTrigger::OnKeyUp(Noesis::BaseComponent*, const Noesis::KeyEventArgs& e)
{
if (e.key == GetKey() && CheckModifiers() && mTextInputIgnored)
{
mTextInputIgnored = false;
InvokeActions(0);
}
}
Re: Disable keyboard shortscuts when in gui elements
You are correct, it wasn't handled by IView::KeyDown(). But we are checking for return values both of IView::KeyDown() or IView::Char() before continuing to process the input, so it did actually do something plausible for us.Correct me if I'm wrong, but doing this has the same problem as using the KeyTrigger, because if you have the focus in a TextBox, and press the 'R' key, IView::KeyDown() will return false
The Idea of waiting on TextCompositionEvent in "ShortcutTrigger" is effectively doing the same. Doesn't exactly need to wait for KeyUp, but may already enter half-triggered state in KeyDown and trigger on TextCompositionEvent already.
But it does fall for another trap. Any logic trying to argue over TextCompositionEvents is then incapable of handling dead keys (e.g. ^), as TextCompositionEvent isn't triggered until UTF8 composite is passed to IView::Char(). (Win32Display from NoesisApp has skipped mapping any potentially dead keys (VK_OEM_*), so that doesn't show. And none are dead on US keyboard layout.)
So at least for dead key support, it's back to explicitly ignoring key presses if the source (or in fact anything in the event route) is potentially a handler of a followup TextCompositionEvent. There is no way around probing whether the scene is currently in text input mode or not, prior to deciding how to handle the key down event.
-
sfernandez
Site Admin
- Posts: 2991
- Joined:
Re: Disable keyboard shortscuts when in gui elements
I understand what you are saying, you're right using TextInput won't work when a shortcut references a dead key.
Taking into account your comments...
I think it is better to try to solve the specific problem here: don't trigger shortcuts if a TextBox (or PasswordBox) has the focus (as I exposed before).
Do you have any suggestion on how to improve that approach?
Taking into account your comments...
I've been doing more tests and I verified that WPF's TextBox only sets Handled to true for KeyDown event on some specific keys, and TextInput event. The KeyUp event simply bubbles up for all keys. So without breaking that behavior, implementing a shortcut trigger that works on all the scenarios you have exposed can't rely on those events being handled by inner controls.it's that the TextBox should (be able to) consume the input it can handle before shortcuts are triggered
I think it is better to try to solve the specific problem here: don't trigger shortcuts if a TextBox (or PasswordBox) has the focus (as I exposed before).
Do you have any suggestion on how to improve that approach?
Re: Disable keyboard shortscuts when in gui elements
Was there ever a better resolution to this? I have a very similar problem. In our game we have a background window that defines a bunch of "default" shortcuts, but new windows being opened should be able to redefine those shortcuts.
We also have the issue that textboxes don't get the letters associated with the background window shortcuts.
This is how we define those shortcuts:
And here, for example, our PauseMenu which redefines "Esc" to close itself. Which doesn't work, because Esc is used by the background window to open the PauseMenu:
So is there a way to either deactivate the background shortcuts when another window has the focus, or a way to set a priority to windows for shortcut, with the topmost window being highest priority, etc?
We also have the issue that textboxes don't get the letters associated with the background window shortcuts.
This is how we define those shortcuts:
Code: Select all
<jomini:WindowView x:Class="Game.IngameScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:jomini="clr-namespace:JominiNoesisGui;assembly=JominiNoesisGui.Extension"
xmlns:game="clr-namespace:Game"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=game:IngameScreenModel, IsDesignTimeCreatable=True}"
d:DesignWidth="1920" d:DesignHeight="1080"
Focusable="True"
Name="MainScreen">
<UserControl.InputBindings>
<KeyBinding Key="Esc" Command="{Binding OpenMenuCommand}" />
<KeyBinding Key="Add" Command="{Binding GameTimeControlModel.IncreaseSpeedCommand}" />
<KeyBinding Key="Subtract" Command="{Binding GameTimeControlModel.DecreaseSpeedCommand}" />
<KeyBinding Key="Space" Command="{Binding GameTimeControlModel.PauseCommand}" />
<KeyBinding Key="R" Command="{Binding MapIconsManager.FakeCameraShakeCommand}" />
<KeyBinding Key="F" Command="{Binding OpenSystemSearchWindowCommand}"/>
</UserControl.InputBindings>
</jomini:WindowView>
Code: Select all
<jomini:WindowView x:Class="Game.PauseMenuWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:jomini="clr-namespace:JominiNoesisGui;assembly=JominiNoesisGui.Extension"
xmlns:game="clr-namespace:Game"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
d:DataContext="{d:DesignInstance Type=game:PauseMenuWindowModel, IsDesignTimeCreatable=True}"
d:DesignHeight="320" d:DesignWidth="300"
mc:Ignorable="d">
<game:UIScalingDecorator>
<Grid Background="#FFE4E4E4" Height="320" Width="300">
<TextBlock TextWrapping="Wrap" Text="Game Paused" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Top" FontSize="24" Foreground="Blue" Margin="0,34.076,0,234"/>
<Button Content="Resume Game" Command="{Binding CloseCommand}" Width="219.75" Height="44.5" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,97,0,0" />
<Button Content="Exit to Main Menu" Command="{Binding ExitToMainMenuCommand}" Width="219.75" Height="44.5" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="40.125,167.5,40.125,0" />
<Button Content="Exit to Desktop" Command="{Binding ExitToDesktopCommand}" Width="219.75" Height="44.5" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="40.125,233.5,40.125,0" />
<b:Interaction.Behaviors>
<b:MouseDragElementBehavior />
</b:Interaction.Behaviors>
</Grid>
</game:UIScalingDecorator>
<UserControl.InputBindings>
<KeyBinding Key="Esc" Command="{Binding CloseCommand}" />
</UserControl.InputBindings>
</jomini:WindowView>
-
sfernandez
Site Admin
- Posts: 2991
- Joined:
Re: Disable keyboard shortscuts when in gui elements
Hi,
Defining nested KeyBindings like you did is permited and works the same as in WPF. But you have to take into account that the key bindings are based on the routing of KeyDown event. So if you have a hierarchy with 2 nested WindowView, each one with its own key bindings, the inner view can only execute its bindings if the focus is in any of its inner controls. I just tried a similar scenario and it works as expected in WPF and Noesis: a first Window is opened containing focus and when ESC key is pressed its command is executed to show the PauseMenu which sets the focus to one of its controls. Now when ESC is pressed, the CloseCommand of the pause menu is executed (and nothing else because it sets the event Handled to true).
Anyway, given the problems that KeyBinding has when dealing with TextBoxes (see https://stackoverflow.com/questions/331 ... extbox-use), and also the problems described in this post when trying to use the KeyTrigger alternative, we decided to create a new trigger (ShortcutTrigger) to try to solve this in a cleaner way: #2340
Do you think that approach could fit in your scenario?
Defining nested KeyBindings like you did is permited and works the same as in WPF. But you have to take into account that the key bindings are based on the routing of KeyDown event. So if you have a hierarchy with 2 nested WindowView, each one with its own key bindings, the inner view can only execute its bindings if the focus is in any of its inner controls. I just tried a similar scenario and it works as expected in WPF and Noesis: a first Window is opened containing focus and when ESC key is pressed its command is executed to show the PauseMenu which sets the focus to one of its controls. Now when ESC is pressed, the CloseCommand of the pause menu is executed (and nothing else because it sets the event Handled to true).
Anyway, given the problems that KeyBinding has when dealing with TextBoxes (see https://stackoverflow.com/questions/331 ... extbox-use), and also the problems described in this post when trying to use the KeyTrigger alternative, we decided to create a new trigger (ShortcutTrigger) to try to solve this in a cleaner way: #2340
Do you think that approach could fit in your scenario?
Re: Disable keyboard shortscuts when in gui elements
Hello,
sorry for the delay, I had to disappear from work for a while, but I'm now back and hopefully we can resume this discussion.
Unfortunately I'm not very familiar with WPF and am not quite sure what "it works the same as in WPF." entail. I'm also not quite sure how the routing of KeyDown events functions. I thought just sending the key events from our engine to Noesis would have noesis route it appropriately? To the focused widget, then "bubbling up" through its parents until some widget handles or there are no more parents to handle it?
Is that something we have to implement ourself?
Now, I know that the text widget has the focus, since i give it the focus immediately, and yet it doesn't receive the letters associated with a shortcut in the "main view" (ie the parent's parent).
This new method will definitely help with text boxes, btw.
sorry for the delay, I had to disappear from work for a while, but I'm now back and hopefully we can resume this discussion.
Unfortunately I'm not very familiar with WPF and am not quite sure what "it works the same as in WPF." entail. I'm also not quite sure how the routing of KeyDown events functions. I thought just sending the key events from our engine to Noesis would have noesis route it appropriately? To the focused widget, then "bubbling up" through its parents until some widget handles or there are no more parents to handle it?
Is that something we have to implement ourself?
Now, I know that the text widget has the focus, since i give it the focus immediately, and yet it doesn't receive the letters associated with a shortcut in the "main view" (ie the parent's parent).
Code: Select all
CSystemSearchWindow::CSystemSearchWindow()
{
Loaded() += Noesis::MakeDelegate( this, &CSystemSearchWindow::OnLoadedOrReloaded );
Reloaded() += Noesis::MakeDelegate( this, &CSystemSearchWindow::OnLoadedOrReloaded );
}
CSystemSearchWindow::~CSystemSearchWindow()
{
Loaded() -= Noesis::MakeDelegate( this, &CSystemSearchWindow::OnLoadedOrReloaded );
Reloaded() -= Noesis::MakeDelegate( this, &CSystemSearchWindow::OnLoadedOrReloaded );
}
void CSystemSearchWindow::OnLoadedOrReloaded( Noesis::BaseComponent* pComponent, const Noesis::RoutedEventArgs& Args )
{
Noesis::TextBox* pTextBox = GetTemplateChild<Noesis::TextBox>( "Input" );
if ( pTextBox )
{
pTextBox->Focus( true );
}
}
-
sfernandez
Site Admin
- Posts: 2991
- Joined:
Re: Disable keyboard shortscuts when in gui elements
You got it right, the routing of events is automatically handled by Noesis, and for key events it starts from the focused element and then bubble up to the root until some widget handles it.
Then I think implementing a ShortcutTrigger as I proposed will solve your problems.
Then I think implementing a ShortcutTrigger as I proposed will solve your problems.