WPF KeyBinding swallowing keys, preventing TextBox use - c#

Problem Overview:
Any KeyBinding's defined at a level higher than a TextBox (with no modifier keys assigned), prevents the user from typing those keys inside the TextBox.
Minimal XAML Hierarchy:
<Window>
<UserControl>
<Border>
<UserControl>
<TextBox>
Minimal Command/KeyBinding:
<UserControl.Resources>
<RoutedUICommand x:Key="Commands.SomeCommand" />
</UserControl.Resources>
<UserControl.InputBindings>
<KeyBinding Key="A" Command="{StaticResource Commands.SomeCommand}" />
</UserControl.InputBindings>
<UserControl.CommandBindings>
<CommandBinding Command="{StaticResource Commands.SomeCommand}" Executed="..." />
</UserControl.CommandBindings>
The Command and KeyBinding, are defined at the first UserControl level. So in this example, in the textbox, the user can type freely until they press the A key, and then it just does not insert the letter into the textbox. I can clearly see that the TextBox.KeyDown and TextBox.PreviewKeyDown are firing when you press the A key (and Handled = false) , but the letter will not get added to the text of the textbox and TextBox.PreviewTextInput does not fire.
I'm looking for any suggestions that may indicate what is swallowing the keypress and stopping it from getting processed by the TextBox, or anything related to how I can debug this issue.
EDIT: Thanks to Snoop, I was able to clearly see the problem.
TextBox.PreviewKeyDown tunnels down and fires through the visual tree, starting at the Window, and ending at the TextBox
TextBox.KeyDown bubbles back up starting at the TextBox and heading towards the window
TextBox.KeyDown gets Handled set to true by the first UserControl that has the KeyBinding set.
TextBox.PreviewTextInput never fires, nor does the textbox process the input, because the KeyDown event was set as handled.
This still leaves the problem, how do you prevent the UserControl from handling the input if a textbox has focus? Within the Command execution, I can check if a textbox has keyboard focus, but by this time it's too late.

TextInput and PreviewTextInput only fires when the Text actually changes / might change.
As you updated your question to reflect, the Command intercepts the event and the (Preview)TextInput events are never raised.
The nicest solution would be to add a modifier key to your KeyBinding, but I suspect that is not your preferred way to go.
Another option would be to e.Handle the PreviewKeyDown event on the TextBox and raise the TextComposition events yourself, using something like:
target.RaiseEvent(new TextCompositionEventArgs(InputManager.Current.PrimaryKeyboardDevice,
new TextComposition(InputManager.Current, target, "A"))
{
RoutedEvent = TextCompositionManager.TextInputEvent
});
(Alternatively, insert into textBox.Text at the correct CaretIndex)
Truth be told, it would still be a hack.

I have the same problem.
I took a look to documentation for key bindind, and there is described, that the key on which you bind shouldn't be just key, but key gesture, so it shall be
Modifier key + normal key
Numeric keypad key
Functional key.
Of course, it works with just A, but it's bad practice overall. You should consider to implement some of the posibilities mentioned behind. More at https://msdn.microsoft.com/cs-cz/library/system.windows.input.keybinding(v=vs.110).aspx

I had used the TextComposition RaiseEvent approach for years, however this seems to break typing for non-latin keyboard layouts (eg. cyrillic).
The proper way to do this is to derive from InputBinding and return false in the Matches? check if the event originated from a text-box.
/// <summary>
/// This gesture doesn't handle keys originating in a text control. This allows key bindings without modifier keys
/// that don't break normal typing. A standard KeyGesture doesn't have such logic; this allows the parent of a
/// text box to handle such bare keypresses before the textbox gets to see it as normal text input, thus breaking
/// normal typing.
/// </summary>
public class BareKeyGesture : InputGesture
{
public Key Key { get; set; }
public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
{
var keyEventArgs = inputEventArgs as KeyEventArgs;
if (keyEventArgs == null)
return false;
if (inputEventArgs.OriginalSource is TextBoxBase)
return false;
return (int)Key == (int)keyEventArgs.Key && Keyboard.Modifiers == ModifierKeys.None;
}
}
/// <summary>
/// This only exists because the InputBinding constructor is protected, but since we have to have it anyway
/// we also use this opportunity to simplify adding a BareKeyGesture to it.
/// </summary>
public class BareKeyBinding : InputBinding
{
private BareKeyGesture _gesture = new();
public BareKeyBinding()
{
Gesture = _gesture;
}
public Key Key
{
get => _gesture.Key;
set { _gesture.Key = value; }
}
}
And now that you have an InputGesture which will ignore events originating from textboxes, you can use it in XAML like normal:
<UserControl.InputBindings>
<nsp:BareKeyBinding
Key="D"
Command="{StaticResource Commands.YourCommand}"
CommandParameter="None" />
</UserControl.InputBindings>

As long as you use KeyBinding this not going to work without major hacks. A solution I implemented for this is:
Use the KeyDown event to capture those keys being pressed (instead of KeyBindings). This will be on your code-behind and from there you'll need to switch on the pressed Key to call the required command (SomeCommand in your case).
Now you have a different problem. The TextBox is getting the input but your key-bound commands are also firing. On the code behind, check the type of keyEventArgs.InputSource and ignore the key stroke if it's a TextBox.
It should look like this:
private void OnKeyDown(object sender, KeyEventArgs e)
{
ICommand command = null;
switch (e.Key)
{
case Key.A:
command = Commands.SomeCommand;
break;
case Key.B:
command = Commands.SomeOtherCommand;
break;
}
bool isSourceATextBox = e.InputSource.GetType() == typeof(TextBox);
if (command != null && !isSourceATextBox)
{
command.Execute(parameter:null);
}
}

Related

Programmatically sending keys to a textbox does not update its binding

I am using a ThirdParty keyboard control (DotNetBar from DevComponents, link here) within a WPF desktop application to enter text within a TextBox control. The application is developped on Windows 8 and .NET 4.5.
NOTE: The Windows Tablet Tip was tried, but it has many limitations which makes it difficult to use.
The keyboard being a WindowsForms control, it is put within a WindowsFormsHost.
Since the WindowsForms SendKeys method used by this keyboard will not work well in WPF (as mentionned in many articles on SO), I am programmatically sending the keys to the textbox using the InputManager, like so:
private void _keyboardControl_SendingKey(object sender, KeyboardKeyCancelEventArgs e)
{
// to prevent SendKeys to happen.
e.Cancel = true;
if (string.IsNullOrEmpty(e.Key))
{
return;
}
// A special key is a key like "Enter", "Backspace", "Left arrow", ...
if (IsSpecialKey(e.Key))
{
var keyEventArgs = new KeyEventArgs(
System.Windows.Input.Keyboard.PrimaryDevice,
System.Windows.Input.Keyboard.PrimaryDevice.ActiveSource,
0,
GetKeyValueFromStringCode(e.Key)) { RoutedEvent = Keyboard.KeyDownEvent };
InputManager.Current.ProcessInput(keyEventArgs);
}
// "Normal" keys, like a, b, C, (, 1, ....
else
{
var textCompositionEventArgs = new TextCompositionEventArgs(
System.Windows.Input.Keyboard.PrimaryDevice,
new TextComposition(InputManager.Current,
System.Windows.Input.Keyboard.FocusedElement,
e.Key)) { RoutedEvent = Keyboard.TextInputEvent };
InputManager.Current.ProcessInput(textCompositionEventArgs);
}
}
This effectively puts the right keys into the target WPF textbox which has the focus prior to showing the ThirdParty keyboard.
The textbox Text property has binding to a ViewModel property. The issue is that the updates made to the textbox are not being propagated through the binding when keys are entered programmatically.
If I use my own physical keyboard to type within the same textbox instead, the binding is correctly updated.
Any guidance on this would be greatly appreciated. It took a while to get here, it would be too bad if what I'm trying is not possible. Thanks!
Binding.UpdateSourceTrigger value for Text DP of TextBox by default is LostFocus. So source of binding won't be updated until lost focus fired on textBox.
You need to set it to PropertyChanged, so that whenever Text property changes, it gets updated to source binding.
<TextBox Text="{Binding PropertyName, UpdateSourceTrigger=PropertyChanged}"/>

How to set focus on an element of a user control?

I have a user control that has a TextBox on it. If I set focus on the TextBox in the constructor, then the TextBox works as expected. Sometimes though, I don't want the TextBox to have focus when the user control is first shown, and so I added a property to the user control which sets focus to the TextBox. This works, although I get the problem that I can't then reset focus on the TextBox after it has lost focus.
Doesn't anyone have any ideas why this might be happening?
public ucQueryBox()
{
InitializeComponent();
// Set default values for properties
CodePrompt = "Barcode";
TextBoxFontSize = 20;
TextBoxMaxWidth = 0;
Label = "";
LabelFontSize = 20;
LabelForeground = Colors.White.ToString();
KeyboardButtonVisibility = Visibility.Visible;
txtSelection.Focus();
}
/// <summary>
/// Allows user to decide whether or the user control should have focus when it loads
/// Focus puts the green boarder around the textbox
/// </summary>
[Browsable(true)]
public Boolean SetFocusOnLoad
{
get { return _bSetFocusOnLoad; }
set
{
_bSetFocusOnLoad = value;
if (_bSetFocusOnLoad)
txtSelection.Focus();
}
}
Focus in WPF is a complex topic. I think you'll find that the proper way to do what you want is to use FocusManager in your XAML:
<UserControl ... FocusManager.FocusedElement="{Binding ElementName=myTextBox}">
<TextBox x:Name="myTextBox" />
</UserControl>
If you use FocusManager like this to establish all focus requirements (that is, you use FocusManager on all Windows and UserControls that have any sort of focus requirements), then you'll probably find that all the focusing works exactly like you expect.

LeftAlt Keybinding in WPF

I'm trying to bind Left ALT key with a command to toggle visibility of a menu in WPF.
But it doesn't work.. Command is not firing..
<Window.InputBindings>
<KeyBinding
Key="LeftAlt"
Command="{Binding Path=MenuVisibilitySetCommand}"/>
</Window.InputBindings>
I've noticed that other special Keys ( such as Alt, Ctrl etc..) also not working here..
How to do KeyBinding for Special Key in WPF ?
For LeftALt to work like this, you also need to set Modifiers property to Alt.
<KeyBinding Key="LeftAlt" Modifiers="Alt" Command="{Binding Path=MenuVisibilitySetCommand}"/>
These special Keys are called Modifier keys and this should make it clear why it is not working. A modifier Key is to "modify" the behavior of a given key, Like Shift + L makes an uppercase "L" where only the L key makes a lowercase "l". Using Modifierkeys for actual logic can be problematic and irritating, because the user is not accustomed to see real actions happening when pressing these kind of buttons. But i agree there are places where this makes sense e.g. highlighting MenuItems when hitting ALT key.
But to your actual problem: You could use codebehind and the OnKeyDown/OnKeyUp or the Preview events to implement this behavior.
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.SystemKey == Key.LeftAlt)
{
myMenu.Visibility = Visibility.Visible;
// e.Handled = true; You need to evaluate if you really want to mark this key as handled!
}
base.OnKeyDown(e);
}
Of course cou could also fire your command in this code.

How can be combo box value change process interrupted?

I am writing on c# with visual interface. Part of the logic consists of a set of values changing depended on which one of them (sets) is selected in the combo box.
Changes in the value sets can be saved or not. There is a need to offer an opportunity for user to save unsaved changes or reject them when he chooses a different item (set) in the combo box. It is imperative that when the message box with yes/no is presented combo box still displayed the old value, and only after that, depending on user's choice displayed new or old.
The sequence should be:
user uses keys or drop-down to select new item -> event is fired and form stops all of its processing -> my code cancels the change or lets it go through -> (if not cancelled) combo box is redrawn with new value.
N.B. Following events were tried and proved not to be adequate:
SelectedIndexChanged
SelectedValueChanged
SelectionChangeCommitted
Validating
DropDownClosed
I think this is what you are after - whenever the value is changed the user is prompted OK/Cancel. The only limitation with this is that the combobox shows the new value while the message box is displayed and until the user clicks Cancel. Maybe by intercepting the paint messages you can prevent this.
class MyCombo : ComboBox
{
// Keep track of the previous value
int previousIndex = 0;
// Determines whether the OnSelectedIndexChanged is ignored
bool ignoreChangedEvent = false;
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.ComboBox.SelectedIndexChanged"/> event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (!ignoreChangedEvent)
{
// Prompt the user to see if they really want to change.
if (MessageBox.Show("Change value?", Application.ProductName, MessageBoxButtons.OKCancel) == DialogResult.Cancel)
{
ignoreChangedEvent = true;
base.SelectedIndex = previousIndex;
}
else
{
previousIndex = base.SelectedIndex;
}
}
else
{
ignoreChangedEvent = false;
}
base.OnSelectedIndexChanged(e);
}
}
I have found somewhat acceptable solution, although not directly corresponding to the verbatium of the question asked.
One possible solution, though, sadly, for some reason deleted by his suggestor, was to use message filters. That, however, led to the path of manually calculating where the mouse click went and essentially substituting the winforms capabilities of translating the mouse events to the process of changing selected item in dropped list of the combo box with some crude crutches on my own. This is the path i shied from.
In the end i settled on "cosmetic" solution, with the idea being substitution of displayed text in combo box for the duration of user's decision-making on the subject of whether or not cancel the change.
So, in the SelectedIndexChanged event i've put the follofing code:
try
{
if (MyDataSets.Current.HasChanges() && !MyDataSets.Current.Name.Equals(cbChosenDataSet.Value))
{
cbChosenDataSet.DropDownStyle = ComboBoxStyle.DropDown;
cbChosenDataSet.Text = MyDataSets.Current.Name + ' ';
Application.DoEvents();
}
else return;
/*
* UserChoseToCancel is set according to user's choice
*/
if (UserChoseToCancel)
cbChosenDataSet.Value = MyDataSets.Current.Name;
else
MyDataSets.SetCurrent(cbChosenDataSet.Value);
/*
* other things
*/
}
catch(Exception e) {/* handling */}
finally
{
cbChosenDataSet.DropDownStyle = ComboBoxStyle.DropDownList;
}
The gist of the idea is this: in DropDown style ComboBox' text can be changed as needed. However, when set to one of the items in the list, a change of selection will occur. To avoid that unneededly, a space is added to the temporary text.
In case cancelling does not occurs, restoring of the style to DropDownList forces the text to be changed to the actual chosen value (which has remained the same).
In case user cancels the change, value of the combo box is set back to the old one. A check in the beginning of the handler stops the event generated by that from being processed further.

How to pass value from wpf form to wpf user control

Hai
am having a WPF user control in my WPF Form, when i click my button in my form , i just want to pass some value to the textbox which is in the usercontrol, tell me how to do this.
There are several ways you can do this. The easiest way is to use a String property and implement INotifyPropertyChanged in your UserControl.
To illustrate, you will have your UserControl like so:
/// <summary>
/// Interaction logic for TextBoxUsercontrol.xaml
/// </summary>
public partial class TextBoxUsercontrol : UserControl, INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
public TextBoxUsercontrol()
{
DataContext = this;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
}
Now the TextBox in your UserControl must bind itself to your Text property like so:
<TextBox Text="{Binding Text}" />
Then in your WPF form, you will have your UserControl declared and a button to handle the click like so:
<local:TextBoxUsercontrol x:Name="textBox" />
<Button Click="ButtonBase_OnClick" >Add Text</Button>
And finally, in your Click handler:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
textBox.Text = "Hello!";
}
Having shown you the solution, I give your question asking skill a 1 out of 5. You can be a lot more specific with your question and give sample codes snippets as I have done without asking us to download your whole solution from a site that we must wait download it (not to mention most of us are security conscious about downloading unknown files).
Good luck.
Standard WPF? NO WAY.
Why? You dont pass values around. You get a click event on the item that is clicked (the button) with elements defined on the button (only), then in the code you access the other elements, which thus also have to be either defined in code (the standard way) and expose their values through something called "properties", OR you get the control by name and extract the value. But you dont pass any additional data around.
Look at the tutorials ;)
If you want to PASS values around to METHODS on a click, you need to use something like caliburn (http://www.codeplex.com/caliburn) which allows you to map the click to a method and grab the values passed into the method from other controls.
Just Create a Dependency property and Bind the Porperty to the UserControl's TextBox. While creating the object itself assign the value to the Usercontrol's dependency property.

Categories

Resources