WPF autocompletebox and the enter key - wpf

I am trying to get the WPF AutoCompleteBox to raise the KeyDown event when I press the enter key. I am using the normal KeyDown hook, which works for everything but the enter key it seems. Does anyone know how I can fix this?

You could inherit the AutoCompleteBox, adding an event for Enter.
public class MyAutoCompleteBox : AutoCompleteBox
{
public override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if(e.Key == Key.Enter) RaiseEnterKeyDownEvent();
}
public event Action<object> EnterKeyDown;
private void RaiseEnterKeyDownEvent()
{
var handler = EnterKeyDown;
if(handler != null) handler(this);
}
}
In your consuming class, you can subscribe:
public void Subscribe()
{
autoCompleteBox.EnterKeyDown += DoSomethingWhenEnterPressed;
}
public void DoSomethingWhenEnterPressed(object sender)
{
}

Very late answer, but I faced this same problem that brought me to this question and finally solved it using PreviewKeyDown
<wpftoolkit:AutoCompleteBox Name="AutoCompleteBoxCardName"
Populating="LoadAutocomplete"
PreviewKeyDown="AutoCompleteBoxName_PreviewKeyDown"/>
and
private void AutoCompleteBoxName_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
//...
}
}

There is a slightly easier (and in my opinion more MVVM) way:
// This still goes in your code behind (yuck!)
protected override void OnKeyDown(KeyEventArgs e)
{
if (!IsDropDownOpen && SelectedItem != null && (e.Key == Key.Enter || e.Key == Key.Return))
{
// Drop down is closed so the item in the textbox should be submitted with a press of the Enter key
base.OnKeyDown(e); // This has to happen before we mark Handled = false
e.Handled = false; // Set Handled = false so the event bubbles up to your inputbindings
return;
}
// Drop down is open so user must be selecting an AutoComplete list item
base.OnKeyDown(e);
}
This minimizes the blasphemous code-behind and allows your key event to continue to bubble up to something like an input binding:
<UserControl.InputBindings>
<KeyBinding Key="Tab" Command="{Binding NextCommand}"/>
<KeyBinding Key="Tab" Modifiers="Shift" Command="{Binding LastCommand}"/>
<KeyBinding Key="Escape" Command="{Binding ClearCommand}"/>
<KeyBinding Key="Enter" Command="{Binding EnterCommand}"/>
</UserControl.InputBindings>

(I know this is a late answer, but I still think it's usefull for people who want to solve this issue, without code behind)
A good way to do this in MVVM
First add the reference:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and from the NuGet package (MVVMLight):
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
Than in your View:
<wpftoolkit:AutoCompleteBox Name="AutoCompleteBoxName">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<cmd:EventToCommand Command="{Binding AutoCompleteEnter}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</wpftoolkit:AutoCompleteBox>
and than in your ViewModel :
public ICommand AutoCompleteEnter { get { return new RelayCommand<System.Windows.Input.KeyEventArgs>(Auto_Complete_Enter); } }
public void Auto_Complete_Enter(System.Windows.Input.KeyEventArgs e)
{
//Detect if key is 'Enter/Return' key
if ((e.Key == Key.Enter) || (e.Key == Key.Return))
{
Console.WriteLine("U have pressed the enter key");
}
}
Hope this will still help some people out.

So, I've spend a lot of time trying to get this thing to work. Other responses are certainly helpful and will get you to destination, but i have following issues with implementation:
I needed MVVM solution with an ability to bind Enter to Command within ViewModel, which means that Event Handlers was undesirable option.
I was trying to avoid adding additional dependencies to fix single event in a single control, so no MVVMLight.
Solution:
Pull copy of DonNetProjects.Input.Toolkit from github
Navigate to AutoCompleteBox/System/Windows/Controls/AutoCompleteBox.cs
Change overload of OnKeyDown method to following:
if (IsDropDownOpen)
{
if (SelectionAdapter != null)
{
SelectionAdapter.HandleKeyDown(e);
if (e.Handled)
{
return;
}
}
if (e.Key == Key.Escape)
{
OnAdapterSelectionCanceled(this, new RoutedEventArgs());
//e.Handled = true;
}
}
else
{
// The drop down is not open, the Down key will toggle it open.
if (e.Key == Key.Down)
{
IsDropDownOpen = true;
//e.Handled = true;
}
}
// Standard drop down navigation
switch (e.Key)
{
case Key.F4:
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
break;
case Key.Enter:
if (IsDropDownOpen)
{
OnAdapterSelectionComplete(this, new RoutedEventArgs());
e.Handled = true;
}
break;
default:
break;
}
base.OnKeyDown(e);
Recompile to new DLL and reference it instead of original WPFToolkit.
Result:
If new version used as following:
xmlns:tk="clr-namespace:System.Windows.Controls;assembly=DotNetProjects.Input.Toolkit"
<tk:AutoCompleteBox ItemsSource="{Binding AvailableValues}" SelectedItem="{Binding SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<tk:AutoCompleteBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding AddValue}"/>
</tk:AutoCompleteBox.InputBindings>
</tk:AutoCompleteBox>
Resulting behavior is: if dropdown is open, Enter would be rerouted to it to complete selection, if dropdown is closed it will fire command in KeyBinding.

Alternatively, when using Caliburn Micro, you can simply use:
<Controls:AutoCompleteBox ItemsSource="{Binding Keywords}"
ValueMemberPath="Name"
Text="{Binding EnteredText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsTextCompletionEnabled="True"
cal:Message.Attach="[Event PreviewKeyDown] = [Action AddTagOnEnter($eventArgs)]" />
Specifically, note the last line to attach your event. For some other method parameter options, see here.
Finally, define a public method in your ViewModel:
public void AddTagOnEnter(KeyEventArgs e)
{
if (e.Key != Key.Enter) return;
// Do something useful
}

Related

WPF Calendar Control does not listen to Enter Key via InputBindings (MVVM)

I'm trying to listen to Enter key in calendar control via MVVM. I am able to listen to any other key but not Enter for some reason. Using code behind is not a solution as I want to do command binding with the ViewModel… so please handling PreviewKeyDown will not do.
This is in the view:
<Calendar SelectionMode="SingleRange">
<Calendar.InputBindings>
<KeyBinding Key="Return"
Command="{Binding CloseCalendarCommand}" />
<KeyBinding Key="Esc"
Command="{Binding CloseCalendarCommand}" />
</Calendar.InputBindings>
</Calendar>
The Escape key is working fine and triggers CloseCalendarCommand. Enter key does not trigger the command. Before MVVM I used PreviewKeyDown="FilterCalendar_PreviewKeyDown" to handle the Enter Key...
private void FilterCalendar_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
e.Handled = true;
CalendarPopup.IsPopupOpen = false;
}
}
but this is goes against MVVM concept and I want to do other things in the viewModel once the calendar popup is closed that I cannot from the code behind.
In the viewmodel I'm using Prism Commands:
CloseCalendarCommand = new DelegateCommand(OnCloseCalendarCommand);
private void OnCloseCalendarCommand()
{
IsCalendarVisible = false;
...
}
public ICommand CloseCalendarCommand { get; }

WPF MVVM passing event parameters to view model

I have grid control and I need to pass MouseWheel event to view model.
Now I'm doing this like that
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseWheel">
<i:InvokeCommandAction Command="{Binding MouseWheelCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
but I need to do different actions on mouse scroll up and mouse scroll down.
How to do that?
Can I do that without code in view and without extern libraries? Im using c#, wpf, visual studio 2010 express.
You can use input bindings with a custom mouse gesture, which is very easy to implement:
public class MouseWheelUp : MouseGesture
{
public MouseWheelUp(): base(MouseAction.WheelClick)
{
}
public MouseWheelUp(ModifierKeys modifiers) : base(MouseAction.WheelClick, modifiers)
{
}
public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
{
if (!base.Matches(targetElement, inputEventArgs)) return false;
if (!(inputEventArgs is MouseWheelEventArgs)) return false;
var args = (MouseWheelEventArgs)inputEventArgs;
return args.Delta > 0;
}
}
and then use it like this:
<TextBlock>
<TextBlock.InputBindings>
<MouseBinding Command="{Binding Command}">
<MouseBinding.Gesture>
<me:MouseWheelUp />
</MouseBinding.Gesture>
</MouseBinding>
</TextBlock.InputBindings>
ABCEFG
</TextBlock>
For this you need MouseWheelEventArgs in your MVVM. So Pass this EventArgs as commandParamter.
You can refer this link ---
Passing EventArgs as CommandParameter
Then in your View-Model Class you can use this event args as follow
void Scroll_MouseWheel(MouseWheelEventArgs e)
{
if (e.Delta > 0)
{
// Mouse Wheel Up Action
}
else
{
// Mouse Wheel Down Action
}
e.Handled = true;
}

How to call Event trigger on each text box focus event in mvvm application

I am working on a Touch screen application in which I need to open touch screen key board when cursor focuses on any textbox. I am using the following code to call my command which is in view model..
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding openKeyboard}" />
</i:EventTrigger>
</i:Interaction.Triggers>
It is working fine when I wrote on each textbox... Let I have multiple text boxes in single form, Is there any way to write it commonly and should be apply for all my textboxes of my form (or) application?
Thanks in Advance
I like to use attached behaviors. Here is an example of one I have for selecting the value in a text box when it gains focus. This way you can apply this behavior to any textbox. One of the great things about attached behaviors is many properties/events are at the UIElement level so you can reuse some behaviors accross multiple controls. Anyhow, here is my example:
Behavior
public class SelectAllOnFocusedBehavior
{
private static bool GetSelectAllOnFocused(TextBox textBox)
{
return (bool) textBox.GetValue(SelectAllOnFocusedProperty);
}
public static void SetSelectAllOnFocused(
TextBox textBox, bool value)
{
textBox.SetValue(SelectAllOnFocusedProperty, value);
}
public static readonly DependencyProperty SelectAllOnFocusedProperty =
DependencyProperty.RegisterAttached(
"SelectAllOnFocused",
typeof (bool),
typeof (SelectAllOnFocusedBehavior),
new UIPropertyMetadata(false, OnSelectAllOnFocusedChanged));
private static void OnSelectAllOnFocusedChanged(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TextBox item = depObj as TextBox;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool) e.NewValue)
{
item.PreviewMouseLeftButtonDown += item_IgnoreLeftMouseDown;
item.GotFocus+=item_GotFocus;
}
else
{
//remove EventsHere
item.PreviewMouseLeftButtonDown -= item_IgnoreLeftMouseDown;
item.GotFocus -= item_GotFocus;
}
}
static void item_IgnoreLeftMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// Find the TextBox
DependencyObject parent = e.OriginalSource as UIElement;
while (parent != null && !(parent is TextBox))
parent = VisualTreeHelper.GetParent(parent);
if (parent != null)
{
var textBox = (TextBox)parent;
if (!textBox.IsKeyboardFocusWithin)
{
// If the text box is not yet focussed, give it the focus and
// stop further processing of this click event.
textBox.Focus();
e.Handled = true;
}
}
}
static void item_GotFocus(object sender, RoutedEventArgs e)
{
var item = e.OriginalSource as TextBox;
if (item != null)
item.SelectAll();
}
//EventHandler Here
}
Corresponding wpf
<TextBox x:Name="blahblah"
cmds:SelectAllOnFocusedBehavior.SelectAllOnFocused="True"
cmds:NextTabItemOnEnterBehavior.NextTabItemOnEnter="True"
Height="20" Width="75"
If you are using Caliburn you can do the following to trigger something on your ViewModel:
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<cal:ActionMessage MethodName="openKeyboard" />
</i:EventTrigger>
</i:Interaction.Triggers>
And then create a method on your ViewModel with same name as your MethodName:
public void openKeyboard()
{
// Do your stuff
}
I hope it helps.

How to make the binding happen from the ViewModel

I'm trying to capture the Enter key being pressed on a text box, so that I can kick off an update to the server. It's not working, and so I've reduced the problem to it's simplist elemetns.
In this example, it seems that the binding is not happening per keystroke, but at sometime later. I need to the binding to be completed by the time the enter key is pressed. Consider the following XAML and function from the VM.
Here's the XAML of the textbox
<TextBox Text="{Binding TextValue, Mode=TwoWay}" Height="23" Width="300">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<cmd:EventToCommand Command="{Binding KeyDownCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
The KeyDownCommand fire as expected, howerver the value is not yet in the TextValue property. If I hit enter a second time then the value is in the property? Here's the KeyDownCommand. The constructor of the ViewModel sets the keyDownCommand correctly.
public RelayCommand<RoutedEventArgs> KeyDownCommand { get; private set; }
private void KeyDownAction(RoutedEventArgs eventArg)
{
var source = eventArg.OriginalSource as FrameworkElement;
var e = eventArg as KeyEventArgs;
if (source != null && e != null && e.Key== Key.Enter)
{
e.Handled = true;
MessageBox.Show(TextValue);
}
}
It seems that what I need is a way to "post" the Text of the TextBox back to the TextValue property of the VM when the Enter key is pressed. Or is there something else I'm missing.
Try setting UpdateSourceTrigger to PropertyChanged on binding, like this:
<TextBox Text="{Binding TextValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="23" Width="300">
Now the view model property will be update every time the text is changed.
Update:
For Silverlight, as an alternative to UpdateSourceTrigger, you can use the following simple behavior that updates binding source whenever text changes:
public class TextChangedUpdateSourceBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += OnTextChanged;
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
var bindingExpression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null)
{
bindingExpression.UpdateSource();
}
}
}
Use it like this:
<TextBox Text="{Binding TextValue, Mode=TwoWay}" Height="23" Width="300">
<i:Interaction.Behaviors>
<b:TextChangedUpdateSourceBehavior />
</i:Interaction.Behaviors>
</TextBox>
No sooner did I post the question, than I hit upon the answer.
Here's the corrected KeyDownAction
private void KeyDownAction(RoutedEventArgs eventArg)
{
var source = eventArg.OriginalSource as FrameworkElement;
source.GetBindingExpression(TextBox.TextProperty).UpdateSource();
var e = eventArg as KeyEventArgs;
if (source != null && e != null && e.Key== Key.Enter)
{
e.Handled = true;
MessageBox.Show(TextValue);
}
}
Of now as I type this I realize that I'm "breaking" the pattern, in as much as now my ViewModel knows more about the View that it should.

MVVM - WPF DataGrid - AutoGeneratingColumn Event

I'm currently taking a good look at the excellent toolkit from Laurent and I have the following question.
From Blend 4, I have added an EventTrigger for the Loaded event, in my ViewModel I have the following:
public RelayCommand rcAutoGeneratingColumn { get; private set; }
In the constructor I have:
rcAutoGeneratingColumn =
new RelayCommand(o => DataGridAutoGeneratingColumn(o));
Also in the ViewModel, I have the method which I wish to be invoked by the RelayCommand:
private void DataGridAutoGeneratingColumn(Object o)
{
DataGrid grid = (DataGrid)o;
foreach (DataGridTextColumn col in grid.Columns)
{
if (col.Header.ToString().ToLower() == "id")
{
col.Visibility = System.Windows.Visibility.Hidden;
}
}
}
My XAML contains the following (for the DataGrid):
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<GalaSoft_MvvmLight_Command:EventToCommand
Command="{Binding rcAutoGeneratingColumn, Mode=OneWay}"
CommandParameter="{Binding ElementName=dataGrid1, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
There is NO PROBLEM here the code works just fine, but obviously the event used to hide certain columns should be the AutoGeneratingColumn event and not Loaded.
I have used to Loaded event as a getaround.
I was hoping that I could relay any event offered by the control so that, in this case, the following would work instead:
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn">
<GalaSoft_MvvmLight_Command:EventToCommand
Command="{Binding rcAutoGeneratingColumn, Mode=OneWay}"
CommandParameter="{Binding ElementName=dataGrid1, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I am unable to get the AutoGeneratingColumn event to trigger, and I'm hoping that I've overlooked something and appreciate any advice given!
This behaviour is the same with the GridControl from DevExpress, in that the Loaded event is triggered whereas the ColumnsPopulated event (this being the equivalent of the AutoGeneratingColumn event) is not.
DevExpress offered the following information with regard to my question:
"We have reviewed this question, and come to an interesting conclusion. It looks like the visual tree is not being built at the moment when the Interaction.Triggers are being processed"
If this is true, and there is no other way in which to invoke the events within the ViewModel, then one would have to go ahead and - by using trial and error - note which of the DataGrid events (of which there are over 100) can be invoked in this way and which cannot!
One would like to think that every event which is available in the code-behind, can also be reached when applying the MVVM pattern.
I have searched for an answer but I cannot rule out that I have overlooked something, so if this is to be the case, then please accept my apologies!
You don't have to use evil code behind ;-) You can do this using an attached behaviour...
public class AutoGeneratingColumnEventToCommandBehaviour
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(AutoGeneratingColumnEventToCommandBehaviour),
new PropertyMetadata(
null,
CommandPropertyChanged));
public static void SetCommand(DependencyObject o, ICommand value)
{
o.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject o)
{
return o.GetValue(CommandProperty) as ICommand;
}
private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dataGrid = d as DataGrid;
if (dataGrid != null)
{
if (e.OldValue != null)
{
dataGrid.AutoGeneratingColumn -= OnAutoGeneratingColumn;
}
if (e.NewValue != null)
{
dataGrid.AutoGeneratingColumn += OnAutoGeneratingColumn;
}
}
}
private static void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dependencyObject = sender as DependencyObject;
if (dependencyObject != null)
{
var command = dependencyObject.GetValue(CommandProperty) as ICommand;
if (command != null && command.CanExecute(e))
{
command.Execute(e);
}
}
}
}
Then use it in XAML like this...
<DataGrid ItemsSource="{Binding MyGridSource}"
AttachedCommand:AutoGeneratingColumnEventToCommandBehaviour.Command="{Binding CreateColumnsCommand}">
</DataGrid>
Just set EventTrigger.SourceObject property.
<DataGrid
x:Name="DataGrid"
AutoGenerateColumns="True"
IsReadOnly="True"
ItemsSource="{Binding Data}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn" SourceObject="{Binding ElementName=DataGrid}">
<local:EventToCommand
Command="{Binding ColumnGeneratingCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
As MVVMLight from Galasoft is deprecated now, we can use CommunityToolkit.Mvvm package and use it like this:
<DataGrid AutoGenerateColumns="True"
Name="DataGrid"
ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn" SourceObject="{Binding ElementName=DataGrid}">
<i:InvokeCommandAction Command="{Binding AutoGeneratingColumnCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Note that Items property is a simple List, It could be an ObservableCollection or whatever.
The trick to get the fired event is to load your data after the window is loaded, or raise OnpropertyChanged on Items property after loaded.
<Window ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Window>
In your View Model:
private RelayCommand<DataGridAutoGeneratingColumnEventArgs> myAutoGeneratingColumnCommand;
public RelayCommand<DataGridAutoGeneratingColumnEventArgs> AutoGeneratingColumnCommand
{
get
{
if (myAutoGeneratingColumnCommand == null)
myAutoGeneratingColumnCommand = new RelayCommand<DataGridAutoGeneratingColumnEventArgs>(AutoGeneratingColumnCommandAction);
return myAutoGeneratingColumnCommand;
}
}
private void AutoGeneratingColumnCommandAction(DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyName == "Id")
{
e.Column.Width = 60;
}
else if (e.PropertyName == "Name")
{
e.Column.Header = "myName";
e.Column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
}
else
e.Cancel = true; // ignore all other properties and remove their column
}
RelayCommand myLoadedCommand;
public RelayCommand LoadedCommand
{
get
{
if (myLoadedCommand == null)
myLoadedCommand = new RelayCommand(LoadedCommandAction);
return myLoadedCommand;
}
}
private void LoadedCommandAction()
{
Load(); // Populate the Items List
}
During the course of developing a project with MVVM you're going to have circumstances where you must handle events in your view's code-behind and EventToCommand just plain doesn't work. You especially find this with Silverlight, but I assume from your question that you're using WPF. It's okay to do some event handling in your view's code-behind, just don't put any business logic there. You can even leave the command in your view model, just call it directly from your event handler.
((YourViewModel)this.DataContext).rcAutoGeneratingColumn.Execute(sender);

Resources