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.
Related
I am attempting to make a UserControl to create a CheckBoxList in WPF using MVVM. Also, Entity Framework is being used to deploy the data. Given the following:
WPF (UserControl)
<Grid>
<ListBox Name="ListBox" ItemsSource="{Binding TheList}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Sport}"
Tag="{Binding SportsId}"
IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Classes
public class Athlete
{
public int AthleteId { get; set; }
public string Name { get; set; }
public ICollection<Sports> Sports { get; set; }
}
public class Sports {
public int SportsId { get; set; }
public string Sport { get; set; }
}
How can I get the UserControl to load the entire list of the Sports class and then select the ones that the Athlete can play?
I found the solution to my issue. I was able to find it here. It goes like this:
WPF UserControl.xaml
<UserControl x:Class="YourNamespace.CheckBoxList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:YourNamespace"
mc:Ignorable="d"
x:Name="ThisCheckBoxList"
d:DesignHeight="450" d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel>
<ItemsControl x:Name="host"
ItemsSource="{Binding ElementName=ThisCheckBoxList, Path=ItemsSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MyCheckBox x:Name="theCheckbox"
DisplayMemberPath="{Binding ElementName=ThisCheckBoxList, Path=DisplayPropertyPath}"
Unchecked="MyCheckBox_Checked"
Checked="MyCheckBox_Checked"
Tag="{Binding Path=.}">
<local:MyCheckBox.IsChecked >
<MultiBinding Mode="OneWay" >
<MultiBinding.Converter>
<local:IsCheckedValueConverter />
</MultiBinding.Converter>
<Binding Path="."></Binding>
<Binding ElementName="ThisCheckBoxList" Path="SelectedItems"></Binding>
<Binding ElementName="ThisCheckBoxList" Path="DisplayPropertyPath"></Binding>
</MultiBinding>
</local:MyCheckBox.IsChecked>
</local:MyCheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</UserControl>
WPF UserControl.xaml.cs
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace Eden
{
/// <summary>
/// Interaction logic for CheckBoxList.xaml
/// </summary>
public partial class CheckBoxList : UserControl
{
public CheckBoxList()
{
InitializeComponent();
}
public object ItemsSource
{
get => GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
// Using a DependencyProperty as the backing store for ItemSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(CheckBoxList),
new UIPropertyMetadata(null, (sender, args) => Debug.WriteLine(args)));
public IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedItems. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(CheckBoxList),
new UIPropertyMetadata(null, SelectedChanged));
/// <summary>
/// This is called when selected property changed.
/// </summary>
/// <param name="obj"></param>
/// <param name="args"></param>
private static void SelectedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (args.NewValue is INotifyCollectionChanged ncc)
{
ncc.CollectionChanged += (sender, e) =>
{
CheckBoxList thiscontrol = (CheckBoxList)obj;
RebindAllCheckbox(thiscontrol.host);
};
}
}
private static void RebindAllCheckbox(DependencyObject de)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(de); i++)
{
DependencyObject dobj = VisualTreeHelper.GetChild(de, i);
if (dobj is CheckBox cb)
{
var bexpression = BindingOperations.GetMultiBindingExpression(cb, MyCheckBox.IsCheckedProperty);
if (bexpression != null) bexpression.UpdateTarget();
}
RebindAllCheckbox(dobj);
}
}
public string DisplayPropertyPath
{
get => (string)GetValue(DisplayPropertyPathProperty);
set => SetValue(DisplayPropertyPathProperty, value);
}
// Using a DependencyProperty as the backing store for DisplayPropertyPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayPropertyPathProperty =
DependencyProperty.Register("DisplayPropertyPath", typeof(string), typeof(CheckBoxList),
new UIPropertyMetadata("", (sender, args) => Debug.WriteLine(args)));
private PropertyInfo mDisplayPropertyPathPropertyInfo;
private void MyCheckBox_Checked(object sender, RoutedEventArgs e)
{
if (SelectedItems == null)
return;
MyCheckBox chb = (MyCheckBox)sender;
object related = chb.Tag;
if (mDisplayPropertyPathPropertyInfo == null)
{
mDisplayPropertyPathPropertyInfo =
related.GetType().GetProperty(
DisplayPropertyPath, BindingFlags.Instance | BindingFlags.Public);
}
object propertyValue;
if (DisplayPropertyPath == ".")
propertyValue = related;
else
propertyValue = mDisplayPropertyPathPropertyInfo.GetValue(related, null);
if (chb.IsChecked == true)
{
if (!SelectedItems.Cast<object>()
.Any(o => propertyValue.Equals(
DisplayPropertyPath == "." ? o : mDisplayPropertyPathPropertyInfo.GetValue(o, null))))
{
SelectedItems.Add(related);
}
}
else
{
object toDeselect = SelectedItems.Cast<object>()
.Where(o => propertyValue.Equals(DisplayPropertyPath == "." ? o : mDisplayPropertyPathPropertyInfo.GetValue(o, null)))
.FirstOrDefault();
if (toDeselect != null)
{
SelectedItems.Remove(toDeselect);
}
}
}
}
public class MyCheckBox : CheckBox
{
public string DisplayMemberPath
{
get => (string)GetValue(DisplayMemberPathProperty);
set => SetValue(DisplayMemberPathProperty, value);
}
// Using a DependencyProperty as the backing store for DisplayMemberPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register("DisplayMemberPath",
typeof(string),
typeof(MyCheckBox),
new UIPropertyMetadata(string.Empty, (sender, args) =>
{
MyCheckBox item = (MyCheckBox)sender;
Binding contentBinding = new Binding((string)args.NewValue);
item.SetBinding(ContentProperty, contentBinding);
}));
}
}
BaseMultiValueConverter
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace Eden
{
/// <summary>
/// A base value converter that allows direct XAML usage
/// </summary>
/// <typeparam name="T">The type of this value converter</typeparam>
public abstract class BaseMultiValueConverter<T> : MarkupExtension, IMultiValueConverter
where T : class, new()
{
#region Private Variables
/// <summary>
/// A single static instance of this value converter
/// </summary>
private static T Coverter = null;
#endregion
#region Markup Extension Methods
/// <summary>
/// Provides a static instance of the value converter
/// </summary>
/// <param name="serviceProvider">The service provider</param>
/// <returns></returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Coverter ?? (Coverter = new T());
}
#endregion
#region Value Converter Methods
/// <summary>
/// The method to convert on type to another
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public abstract object Convert(object[] value, Type targetType, object parameter, CultureInfo culture);
/// <summary>
/// The method to convert a value back to it's source type
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public abstract object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture);
#endregion
}
}
IMultiValueConverter
using EcoDev.Data;
using System;
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Windows.Data;
namespace Eden
{
/// <summary>
///
/// </summary>
public class IsCheckedValueConverter : BaseMultiValueConverter<IsCheckedValueConverter>
{
private PropertyInfo PropertyInfo { get; set; }
private Type ObjectType { get; set; }
public override object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[1] == null) return false; // IF I do not have no value for selected simply return false
if (!(values[2] is string PropertyName)) return false;
if (string.IsNullOrEmpty(PropertyName)) return false;
if (!targetType.IsAssignableFrom(typeof(bool))) throw new NotSupportedException("Can convert only to boolean");
IEnumerable collection = values[1] as IEnumerable;
object value = values[0];
if (value.GetType() != ObjectType)
{
PropertyInfo = value.GetType().GetProperty(PropertyName, BindingFlags.Instance | BindingFlags.Public);
ObjectType = value.GetType();
}
foreach (var obj in collection)
{
if (PropertyName == ".")
{
if (value.Equals(obj)) return true;
}
else
{
if (PropertyInfo.GetValue(value, null).Equals(PropertyInfo.GetValue(obj, null))) return true;
}
}
return false;
}
public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
And then all you have to do in whatever window/page you want to use it in is use this code:
<local:CheckBoxList Height="Auto"
SelectedItems="{Binding SelectedItems}"
ItemsSource="{Binding ItemsSource}"
DisplayPropertyPath="Text"/>
The question is very broad and vague but I try to explain the best I can. You may need to read the whole thing at least twice. And also read the external link to the end of it or at least carefully read the codes in it.
First look at the final solution:
public class AthleteVM : DependencyObject
{
public int AthleteId { get; set; }
public string Name { get; set; }
private ObservableCollection<SportSelectionVM> _sports = new ObservableCollection<SportSelectionVM>();
public ObservableCollection<SportSelectionVM> Sports { get { return _sports; } }
}
public class SportSelectionVM : DependencyObject
{
public int SportsId { get; set; }
public string Name { get; set; }
private Model.Sport _model;
public SportSelectionVM(Model.Sport model, bool isSelected)
{
_model = model;
SportsId = model.Id;
Name = model.Name;
IsSelected = isSelected;
}
/// <summary>
/// Gets or Sets IsSelected Dependency Property
/// </summary>
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(AthleteVM), new PropertyMetadata(false, (d, e) =>
{
// PropertyChangedCallback
var vm = d as SportSelectionVM;
var val = (bool)e.NewValue;
AthleteDataService.UpdateModel(vm._model, val);//database changes here
}));
}
XAML:
<ListBox Name="ListBox" ItemsSource="{Binding Sports}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"
Tag="{Binding SportsId}"
IsChecked="{Binding IsSelected}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
DataContext of this view is an instance of AthleteVM. add all sports to Sports in AthleteVM and set IsSelected on those necessary.
see the constructor: public SportSelectionVM(Model.Sport model, bool isSelected)
similar strategy should be used to create an AthleteVM or to populate AthleteVM list in its parent.
EF and UOW:
As we know here's the idea behind MVVM:
[Model] <--- [VM] <--TwoWay Binding--> [View]
When EF is added to this pattern, it is usually recommended to follow UOW pattern as well.
Typically the UOW (UnitOfWork) is an object which is in charge of one database transaction (I don't mean SQLTransaction) and it is recommended to always create a UOW inside a using statement so that it would be disposed afterwards. Using this approach, you should expect to run into this question: how different UOWs interact with each others. which answer is: they don't.
Each UOW creates a lazy copy of the database and starts to modify it until you tell it to discard or save. If another UOW is created in the middle of this process, it doesn't contain any of the changes made to previous UOWs, unless the previous UOW is saved.
So you don't have to worry about Model and instead, you'll focus on the DataService to have something like this.
Model<->VM
Considering all this information, ViewModel simply uses an instance of a DataService to fetch data from database and puts them in bindable properties and observable collections to maintain a TwoWay Binding.
But VM and Model do not have a TwoWay relationship, meaning that any change to ViewModel should be reflected on Model and then be saved in database manually.
My favourite solution is to take full advantage of PropertyChangedCallback feature of DependencyProperty to tell the DataService to reflect the change:
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(MyViewModel),
new PropertyMetadata(0, (d,e)=>
{
var vm = d as MyViewModel;
var val = (int)e.NewValue;//check conditions here
vm._model.MyProperty = val;//update model
vm._dataService.Update(vm._model);//update database
}));
in above sample, class MyViewModel has an instance of _model and _dataService.
in general TextBox control TextChanged event is working but in ItemsControl, the TextBox TextChanged Event is not fired how can i do this. I trying to do by using following code which I have implemented but not getting result which I want.
So, what am I doing wrong?
View
<Window x:Class="SoniSoft.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ff="clr-namespace:SoniSoft"
Title="Window1" Height="300" Width="300">
<Window.DataContext>
<ff:ViewModels/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="38"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" ff:TextBoxBehaviour.TextChangedCommand="{Binding TextChanged}" />
<ItemsControl Margin="7,0,0,0" Grid.Row="3" ItemsSource="{Binding Path=ViewModelSearchResults}" x:Name="list">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Grid>
<TextBox ff:TextBoxBehaviour.TextChangedCommand="{Binding TextChanged}" Text="{Binding Path=CategoryName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="14" FontWeight="Normal" x:Name=" TextBoxCategoryName" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
View Models
class ViewModels :ViewModelBase
{
public ObservableCollection<Category> AllCategorys = new ObservableCollection<Category>();
DatabaseDataContext db = new DatabaseDataContext();
private ListCollectionView _objViewModelSearchResults;
public ListCollectionView ViewModelSearchResults
{
get { return _objViewModelSearchResults; }
set
{
_objViewModelSearchResults = value;
OnPropertyChanged("ViewModelSearchResults");
}
}
public ViewModels()
{
AllCategorys.Clear();
foreach (var item in db.Categories.OrderBy(c => c.CategoryName))
{
AllCategorys.Add(item);
}
ViewModelSearchResults = new ListCollectionView(AllCategorys);
}
public ICommand TextChanged
{
get
{
// this is very lazy: I should cache the command!
return new TextChangedCommand();
}
}
private class TextChangedCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Text Changed");
}
public bool CanExecute(object parameter)
{
return true;
}
}
}
DependencyProperty
class EventBehaviourFactory
{
public static DependencyProperty CreateCommandExecutionEventBehaviour(RoutedEvent routedEvent, string propertyName, Type ownerType)
{
DependencyProperty property = DependencyProperty.RegisterAttached(propertyName, typeof(ICommand), ownerType,
new PropertyMetadata(null,
new ExecuteCommandOnRoutedEventBehaviour(routedEvent).PropertyChangedHandler));
return property;
}
private class ExecuteCommandOnRoutedEventBehaviour : ExecuteCommandBehaviour
{
private readonly RoutedEvent _routedEvent;
public ExecuteCommandOnRoutedEventBehaviour(RoutedEvent routedEvent)
{
_routedEvent = routedEvent;
}
/// <summary>
/// Handles attaching or Detaching Event handlers when a Command is assigned or unassigned
/// </summary>
/// <param name="sender"></param>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
protected override void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue)
{
UIElement element = sender as UIElement;
if (element == null) { return; }
if (oldValue != null)
{
element.RemoveHandler(_routedEvent, new RoutedEventHandler(EventHandler));
}
if (newValue != null)
{
element.AddHandler(_routedEvent, new RoutedEventHandler(EventHandler));
}
}
protected void EventHandler(object sender, RoutedEventArgs e)
{
HandleEvent(sender, e);
}
}
internal abstract class ExecuteCommandBehaviour
{
protected DependencyProperty _property;
protected abstract void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue);
protected void HandleEvent(object sender, EventArgs e)
{
DependencyObject dp = sender as DependencyObject;
if (dp == null)
{
return;
}
ICommand command = dp.GetValue(_property) as ICommand;
if (command == null)
{
return;
}
if (command.CanExecute(e))
{
command.Execute(e);
}
}
/// <summary>
/// Listens for a change in the DependencyProperty that we are assigned to, and
/// adjusts the EventHandlers accordingly
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void PropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// the first time the property changes,
// make a note of which property we are supposed
// to be watching
if (_property == null)
{
_property = e.Property;
}
object oldValue = e.OldValue;
object newValue = e.NewValue;
AdjustEventHandlers(sender, oldValue, newValue);
}
}
}
class TextBoxBehaviour
{
public static readonly DependencyProperty TextChangedCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(TextBox.TextChangedEvent, "TextChangedCommand", typeof(TextBoxBehaviour));
public static void SetTextChangedCommand(DependencyObject o, ICommand value)
{
o.SetValue(TextChangedCommand, value);
}
public static ICommand GetTextChangedCommand(DependencyObject o)
{
return o.GetValue(TextChangedCommand) as ICommand;
}
}
Here is the problem. You are setting the command in an ItemTemplate. Thus it is binding to the Category object you have in the ListCollectionView. Now this is the object that doesnt contain any command for your text changed. What does contain the command for your TextChanged is the DataContext of the UserControl and you need to bind it to that.
Now there are is a way to work around and its called Ancestor RelativeSource. As I work with silverlight it might work different but this line of code should do.
Edit:
The actual line should be. this because it is ofcourse a window and you need to have the DataContext (the viewmodel) and then the property TextChanged:
<TextBox ff:TextBoxBehaviour.TextChangedCommand="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}, Path=DataContext.TextChanged}" />
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
}
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.