Suppose I have usercontrol with textbox, combobox, button,... inside this control.
Button1 is bound to a ICommand in view model.
My request is: when user hit Enter key in any field, like any textbox, combobox, it will fire Button1 Click event, so that ICommand will be called.
How to implement this?
I created simple behaviour for this kind of situation
<TextBox Grid.Row="2" x:Name="Tags">
<i:Interaction.Behaviors>
<this:KeyEnterCommand Command="{Binding AddTagsCommand}" CommandParameter="{Binding ElementName=Tags}" />
</i:Interaction.Behaviors>
</TextBox>
behaviour code:
public class KeyEnterCommand : Behavior<Control>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.KeyDown += KeyDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.KeyDown -= KeyDown;
}
void KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Enter && Command != null)
{
Command.Execute(CommandParameter);
}
}
#region Command (DependencyProperty)
/// <summary>
/// Command
/// </summary>
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(KeyEnterCommand),
new PropertyMetadata(null));
#endregion
#region CommandParameter (DependencyProperty)
/// <summary>
/// CommandParameter
/// </summary>
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(KeyEnterCommand),
new PropertyMetadata(null));
#endregion
}
Add a dependency property to the UserControl:-
public ICommand EnterKeyCommand
{
get { return GetValue(EnterKeyCommandProperty) as ICommand; }
set { SetValue(EnterKeyCommandProperty, value); }
}
public static readonly DependencyProperty EnterKeyCommandProperty =
DependencyProperty.Register(
"EnterKeyCommand",
typeof(ICommand),
typeof(MyControl),
null);
Attach a handler for the Keyup event on the UserControl using the AddHandler method:-
void MyControl()
{
InitializeComponent();
this.AddHandler(UIElement.KeyUpEvent, new KeyEventHandler(UserControl_KeyUp), true); //Note that last parameter important
}
void UserControl_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && EnterKeyCommand != null && EnterKeyCommand.CanExecute(null))
{
EnterKeyCommand.Execute(null);
}
}
Note the point here is that the use of AddHandler allows you to intercept an event that has already been handled.
Also note that this is simplified for clarity. In reality you would also want to implement another dependency property for the Command parameter and pass that to CanExecute and Execute instead of null. You would also need to detect whether the OriginalSource is a TextBox that has AcceptsReturn set to true.
Related
I have a problem with binding in KeyBinding in WPF. I'm developing .net 3.5 project using WPF with MVVM pattern. I have to fire command whenever some letter will be typed. Unfortunately Command and CommandParameter aren't Dependency Properties in this .net version and i can't bind to them. So I've written attached properties to assign command and command parameter from my view model. But binding to them isn't working, when I change binding to text (in command parameter) CommandBindingParameterChanged will rise but it doesn't when there is binding to parameter. I tired to set window's name and pass that to binding but it also didn't work. But when I'll assign the same command to button it works fine. Here is my code snippet:
Attached properties:
public class Helper
{
public static readonly DependencyProperty CommandBindingProperty = DependencyProperty.RegisterAttached("CommandBinding", typeof(ICommand), typeof(Helper), new FrameworkPropertyMetadata(default(ICommand), FrameworkPropertyMetadataOptions.None, CommandChanged));
public static ICommand GetCommandBinding(DependencyObject o)
{
return (ICommand)o.GetValue(CommandBindingProperty);
}
public static void SetCommandBinding(DependencyObject o, ICommand value)
{
o.SetValue(CommandBindingProperty, value);
}
private static void CommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var input = d as InputBinding;
input.Command = (ICommand)e.NewValue;
}
public static readonly DependencyProperty CommandBindingParameterProperty = DependencyProperty.RegisterAttached("CommandBindingParameter", typeof(object), typeof(Helper), new PropertyMetadata(CommandParameterChanged));
private static void CommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var input = d as InputBinding;
if (input != null)
input.CommandParameter = e.NewValue;
}
public static object GetCommandBindingParameter(DependencyObject o)
{
return o.GetValue(CommandBindingParameterProperty);
}
public static void SetCommandBindingParameter(DependencyObject o, object value)
{
o.SetValue(CommandBindingParameterProperty, value);
}
}
ViewModel
public class MainWindowViewModel : ViewModelBase
{
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
RaisePropertyChanged("Text");
}
}
private bool _parameter;
public bool Parameter
{
get { return _parameter; }
set
{
_parameter = value;
RaisePropertyChanged("Parameter");
}
}
public MainWindowViewModel()
{
Parameter = true;
}
private RelayCommand<bool> _someCommand;
public ICommand SomeCommand
{
get { return _someCommand ?? (_someCommand = new RelayCommand<bool>(Execute, CanExecute)); }
}
private bool CanExecute(bool arg)
{
return arg;
}
private void Execute(bool obj)
{
//do something
}
}
XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:Test"
Name="Window"
DataContext="{Binding Main, Source={StaticResource Locator}}"
>
<Grid>
<StackPanel>
<TextBox Text="{Binding Text}">
<TextBox.InputBindings>
<KeyBinding Key="A" local:Helper.CommandBinding="{Binding DataContext.SomeCommand, ElementName=Window}" local:Helper.CommandBindingParameter="{Binding DataContext.Parameter, ElementName=Window}"/>
</TextBox.InputBindings>
</TextBox>
<Button Content="SomeButton" Command="{Binding SomeCommand}" CommandParameter="{Binding Parameter}"/>
</StackPanel>
</Grid>
you may want to try this solution.
Use Blend 3 Interactions, i.e. Add System.Windows.Interactivity & Microsoft.Expression.Interactions.dll as reference into your project. I have tested the changes below. Execute method (defined in ViewModel) is called the movement textbox is keyed in.
Modified XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:Test"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Name="Window">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<local:CommandAction Command="{Binding Path=SomeCommand}" CommandParameter="{Binding Path=Parameter}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</StackPanel>
</Grid>
</Window>
CommandAction.CS: Instead of Helper, use CommandAction. CommandAction is found at this location
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using Microsoft.Expression.Interactivity;
using System.Windows.Interactivity;
namespace Test
{
/// <summary>
/// The CommandAction allows the user to route a FrameworkElement's routed event to a Command.
/// For instance this makes it possible to specify--in Xaml--that right-clicking on a Border
/// element should execute the Application.Close command (this example may not make much sense,
/// but it does illustrate what's possible).
///
/// CommandParameter and CommandTarget properties are provided for consistency with the Wpf
/// Command pattern.
///
/// The action's IsEnabled property will be updated according to the Command's CanExecute value.
///
/// In addition a SyncOwnerIsEnabled property allows the user to specify that the owner element
/// should be enabled/disabled whenever the action is enabled/disabled.
/// </summary>
public class CommandAction : TargetedTriggerAction<FrameworkElement>, ICommandSource
{
#region Properties to Expose
[Category("Command Properties")]
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(CommandAction), new PropertyMetadata(
(ICommand)null, OnCommandChanged));
[Category("Command Properties")]
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
"CommandParameter", typeof(object), typeof(CommandAction), new PropertyMetadata());
[Category("Command Properties")]
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
"CommandTarget", typeof(IInputElement), typeof(CommandAction), new PropertyMetadata());
[Category("Command Properties")]
public bool SyncOwnerIsEnabled
{
get { return (bool)GetValue(SyncOwnerIsEnabledProperty); }
set { SetValue(SyncOwnerIsEnabledProperty, value); }
}
/// <summary>
/// When SyncOwnerIsEnabled is true then changing CommandAction.IsEnabled will automatically
/// update the owner (Target) IsEnabled property.
/// </summary>
public static readonly DependencyProperty SyncOwnerIsEnabledProperty = DependencyProperty.Register(
"SyncOwnerIsEnabled", typeof(bool), typeof(CommandAction), new PropertyMetadata());
#endregion
#region Command implementation
/// <summary>
/// This is a strong reference to the Command.CanExecuteChanged event handler. The commanding
/// system uses a weak reference and if we don't enforce a strong reference then the event
/// handler will be gc'ed.
/// </summary>
private EventHandler CanExecuteChangedHandler;
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var action = (CommandAction)d;
action.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
}
private void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
UnhookCommand(oldCommand);
if (newCommand != null)
HookCommand(newCommand);
}
private void UnhookCommand(ICommand command)
{
command.CanExecuteChanged -= CanExecuteChangedHandler;
UpdateCanExecute();
}
private void HookCommand(ICommand command)
{
// Save a strong reference to the Command.CanExecuteChanged event handler. The commanding
// system uses a weak reference and if we don't save a strong reference then the event
// handler will be gc'ed.
CanExecuteChangedHandler = new EventHandler(OnCanExecuteChanged);
command.CanExecuteChanged += CanExecuteChangedHandler;
UpdateCanExecute();
}
private void OnCanExecuteChanged(object sender, EventArgs e)
{
UpdateCanExecute();
}
private void UpdateCanExecute()
{
if (Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
IsEnabled = command.CanExecute(CommandParameter, CommandTarget);
else
IsEnabled = Command.CanExecute(CommandParameter);
if (Target != null && SyncOwnerIsEnabled)
Target.IsEnabled = IsEnabled;
}
}
#endregion
protected override void Invoke(object o)
{
if (Command != null)
{
var command = Command as RoutedCommand;
if (command != null)
command.Execute(CommandParameter, CommandTarget);
else
Command.Execute(CommandParameter);
}
}
}
}
Screenshot: if System.Windows.Interactivity & Microsoft.Expression.Interactions.dll are missing in your environment, please install blend. Blend is very easy to isntall and Installation doesn't take much time.
Does anyone know how to make a custom ItemsSource?
What I want to do is to make an itemsSource to my own UserControl so that it could be bound by ObservableCollection<>.
Also, I could know Whenever the number of items in the itemsSource updated, so as to do further procedures.
Thank you so much.
You may need to do something like this in your control
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as UserControl1;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
// Remove handler for oldValue.CollectionChanged
var oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged;
if (null != oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
// Add handler for newValue.CollectionChanged (if possible)
var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;
if (null != newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
}
void newValueINotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Do your stuff here.
}
Use a DependencyProperty ItemsSource in your CustomControl and then bind to this DependencyProperty
This is the XAML-Code (Recognize the DataContext of the ListBox):
<UserControl
x:Name="MyControl">
<ListBox
DataContext="{Binding ElementName=MyControl}"
ItemsSource="{Binding ItemsSource}">
</ListBox>
</UserControl>
This is the CodeBehind:
public partial class MyCustomControl
{
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable),
typeof(ToolboxElementView), new PropertyMetadata(null));
}
This is the Code, where you use your "MyCustomControl":
<Window>
<local:MyCustomControl
ItemsSource="{Binding MyItemsIWantToBind}">
</local:MyCustomControl>
</Window>
Simplified answer.
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(null, (s, e) =>
{
if (s is UserControl1 uc)
{
if (e.OldValue is INotifyCollectionChanged oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= uc.ItemsSource_CollectionChanged;
}
if (e.NewValue is INotifyCollectionChanged newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += uc.ItemsSource_CollectionChanged;
}
}
}));
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Logic Here
}
// Do Not Forget To Remove Event On UserControl Unloaded
private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
{
if (ItemsSource is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= ItemsSource_CollectionChanged;
}
}
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 :)
I am pretty sure I am doing something dreadfully wrong, but can't figure it out.
I created a simple wrapper around a class and added a dependency property so I could bind to it. However, the binding gives no errors, but does nothing.
In order to simplify things I changed the class to TextBox, and got the same results.
public class TextEditor : TextBox
{
#region Public Properties
#region EditorText
/// <summary>
/// Gets or sets the text of the editor
/// </summary>
public string EditorText
{
get
{
return (string)GetValue(EditorTextProperty);
}
set
{
//if (ValidateEditorText(value) == false) return;
if (EditorText != value)
{
SetValue(EditorTextProperty, value);
base.Text = value;
//if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("EditorText"));
}
}
}
public static readonly DependencyProperty EditorTextProperty =
DependencyProperty.Register("EditorText", typeof(string), typeof(TextEditor));
#endregion
#endregion
#region Constructors
public TextEditor()
{
//Attach to the text changed event
//TextChanged += new EventHandler(TextEditor_TextChanged);
}
#endregion
#region Event Handlers
private void TextEditor_TextChanged(object sender, EventArgs e)
{
EditorText = base.Text;
}
#endregion
}
When I run the following XAML the first gives results, but the second one (EditorText) doesn't even hit the EditorText property.
<local:TextEditor IsReadOnly="True" Text="{Binding Path=RuleValue, Mode=TwoWay}" WordWrap="True" />
<local:TextEditor IsReadOnly="True" EditorText="{Binding Path=RuleValue, Mode=TwoWay}" WordWrap="True" />
You're doing extra work in your CLR property. There is no guarantee that your CLR property will be used by WPF so you shouldn't be doing this. Instead, use metadata on your DP to achieve the same effect.
public string EditorText
{
get { return (string)GetValue(EditorTextProperty); }
set { SetValue(EditorTextProperty, value); }
}
public static readonly DependencyProperty EditorTextProperty =
DependencyProperty.Register(
"EditorText",
typeof(string),
typeof(TextEditor),
new FrameworkPropertyMetadata(OnEditorTextChanged));
private static void OnEditorTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var textEditor = dependencyObject as TextEditor;
// do your extraneous work here
}
trying my first attached behavior: I want to bind the TextSelection of the RichTextBox to my ViewModel`s property:
public TextSelection SelectedRichText {get;set;}
That way I bind it:
<RichTextBox behavior:RichTextBoxSelectionBehavior.RichTextBoxSelection="{Binding SelectedRichText}" />
Thats my code and I have 2 questions:
1) Why is the OnRichTextBoxSelectionPropertyChanged never called?
2) see the question is this method at bottom: OnRichTextBoxGotSelectedText
public static class RichTextBoxSelectionBehavior
{
public static TextSelection GetRichTextBoxSelection(DependencyObject obj)
{
return (TextSelection)obj.GetValue(RichTextBoxSelection);
}
public static void SetRichTextBoxSelection(DependencyObject obj, TextSelection value)
{
obj.SetValue(RichTextBoxSelection, value);
}
// Using a DependencyProperty as the backing store for MyProperty.
public static readonly DependencyProperty RichTextBoxSelection =
DependencyProperty.RegisterAttached
(
"RichTextBoxSelection",
typeof(TextSelection),
typeof(RichTextBoxSelectionBehavior),
new UIPropertyMetadata(OnRichTextBoxSelectionPropertyChanged)
);
private static void OnRichTextBoxSelectionPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
RichTextBox rtb = dpo as RichTextBox;
if (rtb != null)
{
if ( !((TextSelection)args.NewValue).IsEmpty)
{
// if the TextSelected has selected text hook up the RichTextBox intenal SelectedChanged event with my own
rtb.SelectionChanged += OnRichTextBoxGotSelectedText;
}
else
{
rtb.SelectionChanged -= OnRichTextBoxGotSelectedText;
}
}
}
private static void OnRichTextBoxGotSelectedText(object sender, RoutedEventArgs e)
{
RichTextBox rtb = (RichTextBox) sender;
// How can I pass now my rtb.Selection to the property the behavior is bound to? e.g. my SelectedRichText property in the ViewModel
//Action action = () => { rtb.Selection; };
//rtb.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
}
}
I mixed up an attached behavior with a dependency property, here is the solution:
Put a RichTExtBox in a UserControl and this code too:
// SelectedText property.
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.Register("SelectedText", typeof(TextSelection),
typeof(MyRichTextBox ));
/// <summary>
/// Default constructor.
/// </summary>
public MyRichTextBox ()
{
InitializeComponent();
this.TextBox.SelectionChanged += new RoutedEventHandler(TextBox_SelectionChanged);
}
void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
var sT = (e.OriginalSource as RichTextBox).Selection;
SelectedText = sT;
}
/// <summary>
/// The WPF Selected Text of the FlowDocument in the control
/// </summary>
public TextSelection SelectedText
{
get { return (TextSelection)GetValue(SelectedTextProperty); }
set { SetValue(SelectedTextProperty, value); }
}
//UserControl embedded in the MainWindow.xaml
<My:MyRichTextBox SelectedText="{Binding SelectedDocument,Mode=TwoWay}" x:Name="EditBox" />
Now you have access to the TextSelection of the richtextbox in your ViewModel!