KeyBinding with Command Binding dont work with TextBox UpdateSourceTrigger LostFocus - wpf

I'm using MVVM and have the following problem. My TextBox.Text is bound with UpdateSourceTrigger=LostFocus (thats what the user want). I have a Button with a SaveCommand CommandBinding - this works. Now i have a KeyBinding with Strg+S wich also execute the SaveCommand. And here is the problem: when i m in the Textbox and press Strg+s, the changes are not in the viewmodel.
is there any way to get MVVM Commands with KeyBinding and TextBox UpdateSourceTrigger=LostFocus working together?
some code to check out the problem
<Window>
<Window.InputBindings>
<KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveCommand}"></KeyBinding>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding MyText1, UpdateSourceTrigger=LostFocus}" Width="100"></TextBox>
<Button Grid.Row="1" Content="_Save" Command="{Binding SaveCommand}" IsDefault="True"></Button>
</Grid>
</Window>
public partial class MainWindow : Window
{
private Viewmodel _data;
public MainWindow()
{
_data = new Viewmodel();
InitializeComponent();
this.DataContext = _data;
}
}
public class Viewmodel : INPCBase
{
private string _myText1;
private Lazy<DelegateCommand> _save;
public Viewmodel()
{
this._save = new Lazy<DelegateCommand>(()=> new DelegateCommand(this.SaveCommandExecute));
}
private void SaveCommandExecute()
{
MessageBox.Show(MyText1);
}
public string MyText1
{
get { return _myText1; }
set { _myText1 = value; this.NotifyPropertyChanged(()=>MyText1);}
}
public ICommand SaveCommand
{
get { return _save.Value; }
}
}

at the moment i came up with the following workaround. within the usercontrol/views where i define my KeyBindings, i also listen to the PreviewKeyDown event and set the focus to the next element when eg. Strg+S is pressed.
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.S && e.KeyboardDevice.Modifiers == ModifierKeys.Control)
{
var fe = Keyboard.FocusedElement as UIElement;
if (fe != null)
{
fe.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}

I have the same problem and end up with attached property for TextBox.
public static bool GetCommitOnSave(DependencyObject obj)
{
return (bool)obj.GetValue(CommitOnSaveProperty);
}
public static void SetCommitOnSave(DependencyObject obj, bool value)
{
obj.SetValue(CommitOnSaveProperty, value);
}
public static readonly DependencyProperty CommitOnSaveProperty =
DependencyProperty.RegisterAttached("CommitOnSave", typeof(bool), typeof(Helper), new PropertyMetadata(false, CommitOnSavePropertyChanged));
private static void CommitOnSavePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox textBox)
{
if ((bool)e.NewValue)
{
if ((bool)e.NewValue)
{
textBox.KeyDown += TextBox_KeyDown;
}
else
{
textBox.KeyDown -= TextBox_KeyDown;
}
}
}
}
private static void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var textBox = (TextBox)sender;
if (e.Key == Key.S && Keyboard.Modifiers == ModifierKeys.Control)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
}
Using <TextBox Text="{Binding Name}" local:Helper.CommitOnSave="True" />
Of course you can set attached property in style for all TextBoxes in a form.

I think I find the best solution for me. I mix solution #blindmeis and my previous one with using attached property.
I create command which update binding source of actual keyboard focused element:
public class CommitValueCommand : ICommand
{
private static CommitValueCommand _instance;
public static CommitValueCommand Command => _instance ?? (_instance = new CommitValueCommand());
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (Keyboard.FocusedElement is TextBox textBox)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
//for combobox etc.
else if (Keyboard.FocusedElement is Selector selector)
{
BindingOperations.GetBindingExpression(selector, Selector.SelectedValueProperty).UpdateSource();
}
}
}
In Execute method of command SaveCommand just at beginning invoke CommitValueCommand.Command.Execute().

Related

"Shift+enter" should move to new line in WPF application

I am trying to develop a messaging application in WPF..
Now,what I want is when a user clicks on "Enter" the message has to send and when the user clicks "Shift+enter" it should move to a new line.
I have tried something like this,But it doesn't seems to work
if (e.Key == Key.Enter && (e.KeyboardDevice.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift)
{
//insert newline
}
else if(e.Key==Key.Enter)
{
//Send Message
}
I am using Textbox here.
Set the AcceptsReturn property to true and handle PreviewKeyDown:
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && Keyboard.Modifiers != ModifierKeys.Shift)
{
//TODO: send message...
e.Handled = true;
}
}
XAML:
<TextBox AcceptsReturn="True" PreviewKeyDown="TextBox_PreviewKeyDown" />
Working on a similar concept. Here is what I did. The below solution also somewhat adheres to MVVM architectural pattern if that's your thing.
You'll need the following.
Step 1:
Add the following for you XAML.
<TextBox Text="{Binding Path=MessageText, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="False" AcceptsTab="True" TextWrapping="Wrap" SpellCheck.IsEnabled ="True">
<TextBox.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3"/>
</Style>
</TextBox.Resources>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SendMessageCommand, Mode=OneWay}" />
<KeyBinding Gesture="Shift+Return" Command="{Binding NewLineCommand, Mode=OneWay}" CommandParameter="{Binding Path=., Mode=OneWay, ElementName=chattext_field}" />
</TextBox.InputBindings>
Step 2 : Create your view model if you don't already have one. In my example, it called AppViewModel.
class AppViewModel : INotifyPropertyChanged
{
private string messageText = string.Empty;
public string MessageText
{
get { return messageText; }
set
{
messageText = value;
NotifyPropertyChanged();
}
}
public ICommand SendMessageCommand { get; private set; }
public ICommand NewLineCommand { get; private set; }
public void Load()
{
NewLineCommand = new CustomCommand(p =>
{
System.Windows.Controls.TextBox txtB = p as System.Windows.Controls.TextBox;
if (txtB == null)
return;
var caretIdx = txtB.CaretIndex;
if (string.IsNullOrEmpty(MessageText))
MessageText += "\r";
else
MessageText = MessageText.Insert(caretIdx, "\r");
txtB.CaretIndex = caretIdx + 1;
});
SendMessageCommand = new CustomCommand(p =>
{
try
{
// your send message code here
}
catch (LogException ex)
{
System.Windows.MessageBox.Show($"Message sending failure.\n{ex}", "Message Center", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Step 3 :
When you load your user control/View using the view model. Initialize/Load the view model when the view is ready.
public partial class MyChatControl : UserControl
{
public MyChatControl()
{
InitializeComponent();
this.Loaded += MyChatControl_Loaded;
}
private void MyChatControl_Loaded(object sender, RoutedEventArgs e)
{
try
{
AppViewModel model = new AppViewModel();
model.Load();
this.DataContext = model;
}
catch (LogException ex)
{
MessageBox.Show($"Failed control content load.\n{ex}", "Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
Almost forgot, here is my "CustomCommand" implementation if you don't have one yet. I have a Async version called "CustomAsyncCommand" as well if you need.
// Interface
public interface ICustomCommand : ICommand
{
event EventHandler<object> Executed;
}
// Command Class
public class CustomCommand : ICustomCommand
{
#region Private Fields
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
#endregion
#region Constructor
public CustomCommand(Action<object> execute) : this(execute, null)
{
}
public CustomCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute ?? (x => true);
}
#endregion
#region Public Methods
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter = null)
{
Refresh();
_execute(parameter);
Executed?.Invoke(this, parameter);
Refresh();
}
public void Refresh()
{
CommandManager.InvalidateRequerySuggested();
}
#endregion
#region Events
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public event EventHandler<object> Executed;
#endregion
}
Only Set the AcceptsReturn property to true
XMAL
<TextBox AcceptsReturn="True" />

Mouseover button binding in WPF

What technique could be used in WPF to show TextBlock Text based on the Button that has mouse over on it?
The UI is organized as follows:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<Button Content="Item1" Tag="This is the text for Item1"/>
<Button Content="Item2" Tag="This is the text for Item2"/>
<Button Content="Item3" Tag="This is the text for Item3"/>
</StackPanel>
<TextBlock Grid.Column="2" Margin="20,0,0,0" TextWrapping="Wrap" Text="This text should be shown based on the mouseovered button"/>
</Grid>
I was thinking to set the desired text to Tag of the Button but how to get that Tag to show on the mouse over event.
Notes:
I prefer to use individual Buttons here instead of ListBox or any other ItemsControl (the real application has more controls between these things etc.)
The application follows MVVM
I would prefer an approach other that setting the texts to UI directly (Tag of the Button)
For Mouse Move over,
Create an AttachedProperty for MouseMove and hook your list view with the property. The attached property can be used to any element in your application.
Attached Property
public class MouseBehaviour
{
public static readonly DependencyProperty MouseMoveCommandProperty =
DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(MouseBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseMoveCommandChanged)));
private static void MouseMoveCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.MouseMove += new MouseEventHandler(element_MouseMove);
}
static void element_MouseMove(object sender, MouseEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GetMouseMoveCommand(element);
command.Execute(e);
}
public static void SetMouseMoveCommand(UIElement element, ICommand value)
{
element.SetValue(MouseMoveCommandProperty, value);
}
public static ICommand GetMouseMoveCommand(UIElement element)
{
return (ICommand)element.GetValue(MouseMoveCommandProperty);
}
}
XAML
xmlns:mousebehav="clr-namespace:your namespace"
<Button mousebehav:MouseBehaviour.MouseMoveCommand="{Binding MouseMoveCommand}">
VM
private RelayCommand _MouseMoveCommand;
public RelayCommand MouseMoveCommand
{
get
{
if (_MouseMoveCommand== null) return _MouseMoveCommand= new RelayCommand(param => Execute_MouseMoveCommand((MouseEventArgs)param));
return _MouseMoveCommand;
}
set { _MouseMoveCommand= value; }
}
private void Execute_MouseMoveCommand(MouseEventArgs param)
{
//your logic to expand or ??
}
Attached property for Mouse Leave,
public static readonly DependencyProperty MouseLeaveCommandProperty =
DependencyProperty.RegisterAttached("MouseLeaveCommand", typeof(ICommand), typeof(MouseBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseLeaveCommandChanged)));
private static void MouseLeaveCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.MouseLeave += new MouseEventHandler(element_MouseLeave);
}
static void element_MouseLeave(object sender, MouseEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GetMouseLeaveCommand(element);
command.Execute(e);
}
public static void SetMouseLeaveCommand(UIElement element, ICommand value)
{
element.SetValue(MouseLeaveCommandProperty, value);
}
public static ICommand GetMouseLeaveCommand(UIElement element)
{
return (ICommand)element.GetValue(MouseLeaveCommandProperty);
}
VM
private RelayCommand _MouseLeaveCommand;
public RelayCommand MouseLeaveCommand
{
get
{
if (_MouseLeaveCommand== null) return _MouseLeaveCommand= new RelayCommand(param => Execute_MouseLeaveCommand((MouseEventArgs)param));
return _MouseLeaveCommand;
}
set { _MouseMoveCommand= value; }
}
private void Execute_MouseLeaveCommand(MouseEventArgs param)
{
//your logic to expand or ??
}
XAML
<Button mousebehav:MouseBehaviour.MouseLeaveCommand="{Binding MouseLeaveCommand}">

ListBox Map J and K Keys to Up/Down Arrow Keys

I have a ListBox and I simply want to bind the J and K keys to whatever commands the up and down arrow keys are bound to. The up and down arrow keys in a WPF listbox typically change the selected item to the previous/next item. I thought something like this should work:
<ListBox.InputBindings>
<KeyBinding Key="J" Command="ScrollBar.LineDownCommand" />
<KeyBinding Key="K" Command="ScrollBar.LineUpCommand" />
</ListBox.InputBindings>
I'm probably being too simplistic here.
You can use your DependencyClass on the commands. Define the commands in ListBox.InputBindings:
XAML
<ListBox Name="SampleListBox" Width="200" Height="200" KeyboardNavigation.DirectionalNavigation="Cycle" SelectedIndex="{Binding MySelectedIndex}">
<ListBox.InputBindings>
<KeyBinding Command="{Binding NextCommand}" Gesture="CTRL+J" />
<KeyBinding Command="{Binding PrevCommand}" Gesture="CTRL+K" />
</ListBox.InputBindings>
<ListBoxItem>Sample 1</ListBoxItem>
<ListBoxItem>Sample 2</ListBoxItem>
<ListBoxItem>Sample 3</ListBoxItem>
<ListBoxItem>Sample 4</ListBoxItem>
</ListBox>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Set your data
this.DataContext = new MainWindowViewModel();
// Set focus
SampleListBox.Focus();
}
}
/// <summary>
/// Class with commands
/// </summary>
public class MainWindowViewModel : DependencyObject
{
public ICommand NextCommand
{
get;
set;
}
public ICommand PrevCommand
{
get;
set;
}
public int MySelectedIndex
{
get
{
return (int)GetValue(MySelectedIndexProperty);
}
set
{
SetValue(MySelectedIndexProperty, value);
}
}
public static readonly DependencyProperty MySelectedIndexProperty =
DependencyProperty.Register("MySelectedIndex", typeof(int), typeof(MainWindowViewModel), new UIPropertyMetadata(0));
public MainWindowViewModel()
{
MySelectedIndex = 0;
NextCommand = new SimpleCommand(SetNext);
PrevCommand = new SimpleCommand(SetPrev);
}
private void SetNext()
{
MySelectedIndex += 1;
}
private void SetPrev()
{
if (MySelectedIndex > 0)
{
MySelectedIndex -= 1;
}
}
}
public class SimpleCommand : ICommand
{
private Action _action;
public SimpleCommand(Action p_action)
{
_action = p_action;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (_action != null)
{
_action();
}
}
}
In the class contains two ICommand's: NextCommand and PrevCommand. Also there is a DependencyProperty MySelectedIndex, which contains the current index of the item. In SimpleCommand always return true.
This is just an example that still need to check the total number of Items ListBox. Or instead of increasing the SelectedIndex, use ScrollViewer logic.
Extension
Example with ScrollViewer:
To scroll through the items in the ListBox, you must first have access to it. Below is the corresponding function:
public static DependencyObject GetScrollViewer(DependencyObject Object)
{
if (Object is ScrollViewer)
{
return Object;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(Object); i++)
{
var child = VisualTreeHelper.GetChild(Object, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
Simple function scrolling:
private void OnScrollDown(object sender, RoutedEventArgs e)
{
if (MyListBox.Items.Count > 0)
{
// Get ScrollViewer from ListBox
ScrollViewer scrollViewer = GetScrollViewer(MyListBox) as ScrollViewer;
if (scrollViewer != null)
{
// Increment offset - scrolling Down, sub - scrolling Up
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + ScrollListBoxOffset);
}
}
}

Invoke Command When "ENTER" Key Is Pressed In XAML

I want to invoke a command when ENTER is pressed in a TextBox. Consider the following XAML:
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
...>
...
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding MyCommand}"
CommandParameter="{Binding Text}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
...
</UserControl>
and that MyCommand is as follows:
public ICommand MyCommand {
get { return new DelegateCommand<string>(MyCommandExecute); }
}
private void MyCommandExecute(string s) { ... }
With the above, my command is invoked for every key press. How can I restrict the command to only invoke when the ENTER key is pressed?
I understand that with Expression Blend I can use Conditions but those seem to be restricted to elements and can't consider event arguments.
I have also come across SLEX which offers its own InvokeCommandAction implementation that is built on top of the Systems.Windows.Interactivity implementation and can do what I need. Another consideration is to write my own trigger, but I'm hoping there's a way to do it without using external toolkits.
There is KeyTrigger in expression blend.
<UserControl
xmlns:i="clr-namespace:System.Windows.Interactivity;
assembly=System.Windows.Interactivity"
xmlns:iex="clr-namespace:Microsoft.Expression.Interactivity.Input;
assembly=Microsoft.Expression.Interactions" ...>
<TextBox>
<i:Interaction.Triggers>
<iex:KeyTrigger Key="Enter">
<i:InvokeCommandAction Command="{Binding PasswordLoginCommand}" />
</iex:KeyTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl>
System.Windows.Interactivity and Microsoft.Expression.Interactions assemblies are available for WPF in the official Nuget package.
I like scottrudy's approach (to which I've given a +1) with the custom triggers approach as it stays true to my initial approach. I'm including a modified version of it below to use dependency properties instead of reflection info so that it's possible to bind directly to the ICommand. I'm also including an approach using attached properties to avoid using System.Windows.Interactivity if desired. The caveat to the latter approach is that you lose the feature of multiple invokations from an event, but you can apply it more generally.
Custom Triggers Approach
ExecuteCommandAction.cs
public class ExecuteCommandAction : TriggerAction<DependencyObject> {
#region Properties
public ICommand Command {
get { return (ICommand)base.GetValue(CommandProperty); }
set { base.SetValue(CommandProperty, value); }
}
public static ICommand GetCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CommandProperty, value);
}
// We use a DependencyProperty so we can bind commands directly rather
// than have to use reflection info to find them
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommandAction), null);
#endregion Properties
protected override void Invoke(object parameter) {
ICommand command = Command ?? GetCommand(AssociatedObject);
if (command != null && command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
}
TextBoxEnterKeyTrigger.cs
public class TextBoxEnterKeyTrigger : TriggerBase<UIElement> {
protected override void OnAttached() {
base.OnAttached();
TextBox textBox = this.AssociatedObject as TextBox;
if (textBox != null) {
this.AssociatedObject.KeyUp += new System.Windows.Input.KeyEventHandler(AssociatedObject_KeyUp);
}
else {
throw new InvalidOperationException("This behavior only works with TextBoxes");
}
}
protected override void OnDetaching() {
base.OnDetaching();
AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedObject_KeyUp);
}
private void AssociatedObject_KeyUp(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
TextBox textBox = AssociatedObject as TextBox;
//This checks for an mvvm style binding and updates the source before invoking the actions.
BindingExpression expression = textBox.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
expression.UpdateSource();
InvokeActions(textBox.Text);
}
}
}
MyUserControl.xaml
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:b="clr-namespace:MyNameSpace.Interactivity"
...
<TextBox>
<i:Interaction.Triggers>
<b:TextBoxEnterKeyTrigger>
<b:ExecuteCommandAction Command="{Binding MyCommand}" />
</b:TextBoxEnterKeyTrigger>
</i:Interaction.Triggers>
</TextBox>
...
</UserControl>
Attached Properties Approach
EnterKeyDown.cs
public sealed class EnterKeyDown {
#region Properties
#region Command
public static ICommand GetCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EnterKeyDown),
new PropertyMetadata(null, OnCommandChanged));
#endregion Command
#region CommandArgument
public static object GetCommandArgument(DependencyObject obj) {
return (object)obj.GetValue(CommandArgumentProperty);
}
public static void SetCommandArgument(DependencyObject obj, object value) {
obj.SetValue(CommandArgumentProperty, value);
}
public static readonly DependencyProperty CommandArgumentProperty =
DependencyProperty.RegisterAttached("CommandArgument", typeof(object), typeof(EnterKeyDown),
new PropertyMetadata(null, OnCommandArgumentChanged));
#endregion CommandArgument
#region HasCommandArgument
private static bool GetHasCommandArgument(DependencyObject obj) {
return (bool)obj.GetValue(HasCommandArgumentProperty);
}
private static void SetHasCommandArgument(DependencyObject obj, bool value) {
obj.SetValue(HasCommandArgumentProperty, value);
}
private static readonly DependencyProperty HasCommandArgumentProperty =
DependencyProperty.RegisterAttached("HasCommandArgument", typeof(bool), typeof(EnterKeyDown),
new PropertyMetadata(false));
#endregion HasCommandArgument
#endregion Propreties
#region Event Handling
private static void OnCommandArgumentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
SetHasCommandArgument(o, true);
}
private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
FrameworkElement element = o as FrameworkElement;
if (element != null) {
if (e.NewValue == null) {
element.KeyDown -= new KeyEventHandler(FrameworkElement_KeyDown);
}
else if (e.OldValue == null) {
element.KeyDown += new KeyEventHandler(FrameworkElement_KeyDown);
}
}
}
private static void FrameworkElement_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
DependencyObject o = sender as DependencyObject;
ICommand command = GetCommand(sender as DependencyObject);
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element != null) {
// If the command argument has been explicitly set (even to NULL)
if (GetHasCommandArgument(o)) {
object commandArgument = GetCommandArgument(o);
// Execute the command
if (command.CanExecute(commandArgument)) {
command.Execute(commandArgument);
}
}
else if (command.CanExecute(element.DataContext)) {
command.Execute(element.DataContext);
}
}
}
}
#endregion
}
MyUserControl.xaml
<UserControl
...
xmlns:b="clr-namespace:MyNameSpace.Interactivity"
...
<TextBox b:EnterKeyDown.Command="{Binding AddNewDetailCommand}"
b:EnterKeyDown.CommandArgument="{Binding Path=Text,RelativeSource={RelativeSource Self}}" />
...
</UserControl>
I ran into this same issue yesterday and solved it using custom triggers. It may seem a bit much at first, but I found this general pattern is usable for doing a lot of the things that I used to accomplish using event handlers directly in a view (like double click events). The first step is to create a trigger action that can accept a parameter since we will need it later.
public class ExecuteCommandAction : TriggerAction<FrameworkElement>
{
public string Command { get; set; }
protected override void Invoke(object o)
{
if (Command != null)
{
object ctx = AssociatedObject.DataContext;
if (ctx != null)
{
var cmd = ctx.GetType().GetProperty(Command)
.GetValue(ctx, null) as ICommand;
if (cmd != null && cmd.CanExecute(o))
{
cmd.Execute(o);
}
}
}
}
}
The next step is to create the trigger. You could do some interesting things with base classes to make it more generic for capturing different types of key presses, but we'll keep it simple.
public class TextBoxEnterKeyTrigger: TriggerBase<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.KeyUp += AssociatedObject_KeyUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
}
void AssociatedObject_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
TextBox textBox = AssociatedObject as TextBox;
object o = textBox == null ? null : textBox.Text;
if (o != null)
{
InvokeActions(o);
}
}
}
}
Keep in mind that even though you may have a data binding in place to your TextBox value, the property changed event won't fire because your textbox hasn't lost focus. For this reason I am passing the value of the TextBox.Text property to the command. The last step is to use this feature in your XAML. You need to be sure to include the Interactivity namespace as well as the namespace that contains your code from above.
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:common="clr-namespace:My.UI;assembly=My.UI">
<TextBox Text="{Binding Path=MyText, Mode=TwoWay}" IsEnabled="{Binding CanMyCommand}">
<i:Interaction.Triggers>
<common:TextBoxEnterKeyTrigger>
<common:ExecuteCommandAction Command=MyCommand" />
</common:TextBoxEnterKeyTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl>
I used scottrudy's code in my application however, my textbox text is bound to some property in viewmodel class and this property is not getting updated by the time command is invoked after pressiong ENTER key because my textbox hasn't lost focus yet. So, to resolved this, i added the following code snippets just above InvokeActions(o) in AssociatedObject_KeyUp method and updated text property is getting updated in viewmodel class.
BindingExpression bindingExpression = (textBox).GetBindingExpression(TextBox.TextProperty);
bindingExpression.UpdateSource();
On top of my mind.. You can pass event args to command and than in ViewModel check if e.KeyPress = Keys.Enter.. this is not really code :) i dont have my VS on this computer.. this is rather an idea :)

MVVM- How can I bind to a property, which is not a DependancyProperty?

I have found this question MVVM and the TextBox's SelectedText property. However, I am having trouble getting the solution given to work. This is my non-working code, in which I am trying to display the first textbox's selected text in the second textbox.
View:
SelectedText and Text are just string properties from my ViewModel.
<TextBox Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}" Height="155" HorizontalAlignment="Left" Margin="68,31,0,0" Name="textBox1" VerticalAlignment="Top" Width="264" AcceptsReturn="True" AcceptsTab="True" local:TextBoxHelper.SelectedText="{Binding SelectedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" />
<TextBox Text="{Binding SelectedText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Height="154" HorizontalAlignment="Left" Margin="82,287,0,0" Name="textBox2" VerticalAlignment="Top" Width="239" />
TextBoxHelper
public static class TextBoxHelper
{
#region "Selected Text"
public static string GetSelectedText(DependencyObject obj)
{
return (string)obj.GetValue(SelectedTextProperty);
}
public static void SetSelectedText(DependencyObject obj, string value)
{
obj.SetValue(SelectedTextProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.RegisterAttached(
"SelectedText",
typeof(string),
typeof(TextBoxHelper),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged));
private static void SelectedTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
if (e.OldValue == null && e.NewValue != null)
{
tb.SelectionChanged += tb_SelectionChanged;
}
else if (e.OldValue != null && e.NewValue == null)
{
tb.SelectionChanged -= tb_SelectionChanged;
}
string newValue = e.NewValue as string;
if (newValue != null && newValue != tb.SelectedText)
{
tb.SelectedText = newValue as string;
}
}
}
static void tb_SelectionChanged(object sender, RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null)
{
SetSelectedText(tb, tb.SelectedText);
}
}
#endregion
}
What am I doing wrong?
The reason this is not working is that the property change callback isn't being raised (as the bound value from your VM is the same as the default value specified in the metadata for the property). More fundamentally though, your behavior will detach when the selected text is set to null. In cases like this, I tend to have another attached property that is simply used to enable the monitoring of the selected text, and then the SelectedText property can be bound. So, something like so:
#region IsSelectionMonitored
public static readonly DependencyProperty IsSelectionMonitoredProperty = DependencyProperty.RegisterAttached(
"IsSelectionMonitored",
typeof(bool),
typeof(PinnedInstrumentsViewModel),
new FrameworkPropertyMetadata(OnIsSelectionMonitoredChanged));
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetIsSelectionMonitored(TextBox d)
{
return (bool)d.GetValue(IsSelectionMonitoredProperty);
}
public static void SetIsSelectionMonitored(TextBox d, bool value)
{
d.SetValue(IsSelectionMonitoredProperty, value);
}
private static void OnIsSelectionMonitoredChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
if ((bool)e.NewValue)
{
tb.SelectionChanged += tb_SelectionChanged;
}
else
{
tb.SelectionChanged -= tb_SelectionChanged;
}
SetSelectedText(tb, tb.SelectedText);
}
}
#endregion
#region "Selected Text"
public static string GetSelectedText(DependencyObject obj)
{
return (string)obj.GetValue(SelectedTextProperty);
}
public static void SetSelectedText(DependencyObject obj, string value)
{
obj.SetValue(SelectedTextProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.RegisterAttached(
"SelectedText",
typeof(string),
typeof(TextBoxHelper),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged));
private static void SelectedTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
tb.SelectedText = e.NewValue as string;
}
}
static void tb_SelectionChanged(object sender, RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null)
{
SetSelectedText(tb, tb.SelectedText);
}
}
#endregion
And then in your XAML, you'd have to add that property to your first TextBox:
<TextBox ... local:TextBoxHelper.IsSelectionMonitored="True" local:TextBoxHelper.SelectedText="{Binding SelectedText, Mode=OneWayToSource}" />
In order for the SelectedTextChanged handler to fire the SelectedText property must have an initial value. If you don't initialize this to some value (string.Empty as a bare minimum) then this handler will never fire and in turn you'll never register the tb_SelectionChanged handler.
This works for me using the class TextBoxHelper. As other mentioned, you need to initialize the SelectedText property of TextBoxHelper with a non null value. Instead of data binding to a string property (SelText) on the view you should bind to a string property of your VM which should implement INotifyPropertyChanged.
XAML:
<Window x:Class="TextSelectDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextSelectDemo"
Height="300" Width="300">
<StackPanel>
<TextBox local:TextBoxHelper.SelectedText="{Binding Path=SelText, Mode=TwoWay}" />
<TextBox Text="{Binding Path=SelText}" />
</StackPanel>
</Window>
Code behind:
using System.ComponentModel;
using System.Windows;
namespace TextSelectDemo
{
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
SelText = string.Empty;
DataContext = this;
}
private string _selText;
public string SelText
{
get { return _selText; }
set
{
_selText = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelText"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Your binding attempts to bind the Text property of your TextBox to a SelectedText property of the TextBox's current data context. Since you're working with an attached property, not with a property hanging off of your data context, you will need to give more information in your binding:
<TextBox Text="{Binding local:TextBoxHelper.SelectedText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" ... />
Where local has been associated with the CLR namespace containing the TextBoxHelper class.
You need a normal .net property wrapper for the dependencyproperty, some like:
public string SelectedText
{
set {SetSelectedText(this, value);}
...
It is not required by runtime (runtime use set/get) but it is required by designer and compiler.

Resources