What's a good method to bind Commands to Events? In my WPF app, there are events that I'd like to capture and process by my ViewModel but I'm not sure how. Things like losing focus, mouseover, mousemove, etc. Since I'm trying to adhere to the MVVM pattern, I'm wondering if there's a pure XAML solution.
Thanks!
Use System.Windows.Interactivity
…xmlns:i=http://schemas.microsoft.com/expression/2010/interactivity…
<Slider
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<i:InvokeCommandAction
Command="{Binding MyCommand}"
CommandParameter="{Binding Text, ElementName=textBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
Make sure your project references the assembly System.Windows.Interactivity.
Source: MSDN Blog Executing a command from an event of your choice
[Update]
Have a look to to Microsoft.Xaml.Behaviors.Wpf (available since 03.12.2018) Official package by Microsoft.
Have a look at Marlon Grech's Attached Command Behaviour, it could be exactly what you're looking for
In order to handle events, you must have some code that attaches itself to the event and executes your command in response. The final goal is to have in XAML:
MouseMoveCommand="{Binding MyCommand}"
In order to achieve this you need to define an attached property for each event that you want to handle. See this for an example and a framework for doing this.
I implemented it using Attached Properties and Reflection. I cannot say it is the best implementation, but I will maybe improve it and it may be a good start for you.
public class EventBinding : DependencyObject
{
public static string GetEventName(DependencyObject obj)
{
return (string)obj.GetValue(EventNameProperty);
}
public static void SetEventName(DependencyObject obj, string value)
{
obj.SetValue(EventNameProperty, value);
var eventInfo = obj.GetType().GetEvent(value);
var eventHandlerType = eventInfo.EventHandlerType;
var eventHandlerMethod = typeof(EventBinding).
GetMethod("EventHandlerMethod", BindingFlags.Static | BindingFlags.NonPublic);
var eventHandlerParameters = eventHandlerType.GetMethod("Invoke").GetParameters();
var eventArgsParameterType = eventHandlerParameters.
Where(p => typeof(EventArgs).IsAssignableFrom(p.ParameterType)).
Single().ParameterType;
eventHandlerMethod = eventHandlerMethod.MakeGenericMethod(eventArgsParameterType);
eventInfo.AddEventHandler(obj, Delegate.CreateDelegate(eventHandlerType, eventHandlerMethod));
}
private static void EventHandlerMethod<TEventArgs>(object sender, TEventArgs e)
where TEventArgs : EventArgs
{
var command = GetCommand(sender as DependencyObject);
command.Execute(new EventInfo<TEventArgs>(sender, e));
}
public static readonly DependencyProperty EventNameProperty =
DependencyProperty.RegisterAttached("EventName", typeof(string), typeof(EventHandler));
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(EventBinding));
}
public class EventInfo<TEventArgs>
{
public object Sender { get; set; }
public TEventArgs EventArgs { get; set; }
public EventInfo(object sender, TEventArgs e)
{
Sender = sender;
EventArgs = e;
}
}
public class EventInfo : EventInfo<EventArgs>
{
public EventInfo(object sender, EventArgs e)
: base(sender, e) { }
}
public class EventBindingCommand<TEventArgs> : RelayCommand<EventInfo<TEventArgs>>
where TEventArgs : EventArgs
{
public EventBindingCommand(EventHandler<TEventArgs> handler)
: base(info => handler(info.Sender, info.EventArgs)) { }
}
Examples of usage:
View
<DataGrid local:EventBinding.EventName="CellEditEnding"
local:EventBinding.Command="{Binding CellEditEndingCommand}" />
Model
private EventBindingCommand<DataGridCellEditEndingEventArgs> _cellEditEndingCommand;
public EventBindingCommand<DataGridCellEditEndingEventArgs> CellEditEndingCommand
{
get
{
return _cellEditEndingCommand ?? (
_cellEditEndingCommand = new EventBindingCommand<DataGridCellEditEndingEventArgs>(CellEditEndingHandler));
}
}
public void CellEditEndingHandler(object sender, DataGridCellEditEndingEventArgs e)
{
MessageBox.Show("Test");
}
I don't think you can use it in pure XAML, but take a look at the Delegate Command.
Execute Command, Navigate Frame, and Delegating Command behaviour is a pretty good pattern. It is also can be used in the Expression Blend.
On the "best practices" side, you should think twice before converting an event to a command. Normally, command is something user does intentionaly, an event most often is just an interaction trail, and should not leave the view boundaries.
Related
I'm new to WPF. Currently, I want to allow my Add button to add item by using either single click or double click. However, when I try to double click, it ends up fire single click event twice. Code in XAML as below:
<Button.InputBindings>
<MouseBinding Command="{Binding Path=AddCommand}" CommandParameter="{Binding}" MouseAction="LeftClick" />
<MouseBinding Command="{Binding Path=AddCommand}" CommandParameter="{Binding}" MouseAction="LeftDoubleClick" />
I found solution online which is to use DispatcherTimer in order to solve the problem. I have inserted these in code behind:
private static DispatcherTimer myClickWaitTimer =
new DispatcherTimer(
new TimeSpan(0, 0, 0, 1),
DispatcherPriority.Background,
mouseWaitTimer_Tick,
Dispatcher.CurrentDispatcher);
private void btnAdd_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Stop the timer from ticking.
myClickWaitTimer.Stop();
// Handle Double Click Actions
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
myClickWaitTimer.Start();
}
private static void mouseWaitTimer_Tick(object sender, EventArgs e)
{
myClickWaitTimer.Stop();
// Handle Single Click Actions
}
So here comes my question. I've removed the MouseBinding in XAML and want to call for AddCommand in code behind but I'm having problem to do so due to the PrismEventAggregator. The AddCommand in .cs as below:
private void AddCommandExecute(Object commandArg)
{
// Broadcast Prism event for adding item
this.PrismEventAggregator.GetEvent<AddItemEvent>().Publish(
new AddItemPayload()
{
BlockType = this.BlockType
}
);
}
Hence would like to know how to call for the AddCommand (which is a Prism Event in .cs) in Code behind?
Note: The button is inside resource dictionary thus I failed to use the button name to call for the command.
You need to create a class which will subscribe to the event you are publishing and then execute the logic you want.
For example:
public class AddItemViewModel : INotifyPropertyChanged
{
private IEventAggregator _eventAggregator;
public AddItemViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.GetEvent<AddItemEvent>().Subscribe(AddItem);
}
private void AddItem(AddItemPayload payload)
{
// Your logic here
}
}
Then when you publish the event it will trigger the subscriber and execute.
Using Expression Blend SDK, you can create a Behavior that encapsulates all your custom logic. This behavior will offer two dependency properties for your command and its parameter, so you can easily create Bindings for them, exactly as you do this for your InputBindings.
Move your event handlers and DispatcherTimer logic into this behavior:
using System.Windows.Interactivity;
class ClickBehavior : Behavior<Button>
{
// a dependency property for the command
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand),
typeof(ClickBehavior), new PropertyMetadata(null));
// a dependency property for the command's parameter
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object),
typeof(ClickBehavior), new PropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)this.GetValue(CommandProperty); }
set { this.SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return this.GetValue(CommandParameterProperty); }
set { this.SetValue(CommandParameterProperty, value); }
}
// on attaching to a button, subscribe to its Click and MouseDoubleClick events
protected override void OnAttached()
{
this.AssociatedObject.Click += this.AssociatedObject_Click;
this.AssociatedObject.MouseDoubleClick += this.AssociatedObject_MouseDoubleClick;
}
// on detaching, unsubscribe to prevent memory leaks
protected override void OnDetaching()
{
this.AssociatedObject.Click -= this.AssociatedObject_Click;
this.AssociatedObject.MouseDoubleClick -= this.AssociatedObject_MouseDoubleClick;
}
// move your event handlers here
private void AssociatedObject_Click(object sender, RoutedEventArgs e)
{ //... }
private void AssociatedObject_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{ //... }
// call this method in your event handlers to execute the command
private void ExecuteCommand()
{
if (this.Command != null && this.Command.CanExecute(this.CommandParameter))
{
this.Command.Execute(this.CommandParameter);
}
}
The usage is very simple. You need to declare your additional namespaces:
<Window
xmlns:local="Your.Behavior.Namespace"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
Finally, attach the behavior to the button:
<Button>
<i:Interaction.Behaviors>
<local:ClickBehavior Command="{Binding AddCommand}" CommandParameter="{Binding}"/>
</i:Interaction.Behaviors>
</Button>
In XAML, I have a TextBox with x:Name of MyTextBox.
<TextBox x:Name="MyTextBox">Some text</TextBox>
For speed reasons, I want to call the method .AppendText, e.g. In C# code behind, I would call MyTextBox.AppendText("...")
However, this is not very MVVM like. If I want to make a call to a function on a control using binding to my ViewModel, what is an elegant way to achieve this?
I'm using MVVM Light.
Update
I would use the answer from #XAML Lover if I wanted a simple, quick solution. This answer uses a Blend Behavior which is less C# coding.
I would use the answer from #Chris Eelmaa if I wanted write a reusable Dependency Property which I could apply to any TextBox in the future. This example is based on a Dependency Property which, while slightly more complex, is very powerful and reusable once it is written. As it plugs into the native type, there is also slightly less XAML to use it.
Basically when you call a method from a control, it is obvious that you are doing some UI related logic. And that should not sit in ViewModel. But in some exceptional case, I would suggest to create a behavior. Create a Behavior and define a DependencyProperty of type Action<string> since AppendText should take string as a parameter.
public class AppendTextBehavior : Behavior<TextBlock>
{
public Action<string> AppendTextAction
{
get { return (Action<string>)GetValue(AppendTextActionProperty); }
set { SetValue(AppendTextActionProperty, value); }
}
// Using a DependencyProperty as the backing store for AppendTextAction. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AppendTextActionProperty =
DependencyProperty.Register("AppendTextAction", typeof(Action<string>), typeof(AppendTextBehavior), new PropertyMetadata(null));
protected override void OnAttached()
{
SetCurrentValue(AppendTextActionProperty, (Action<string>)AssociatedObject.AppendText);
base.OnAttached();
}
}
In the OnAttached method, I have assigned the extension method that I have created on TextBlock to the DP of Behavior. Now we can attach this behavior to a TextBlock in View.
<TextBlock Text="Original String"
VerticalAlignment="Top">
<i:Interaction.Behaviors>
<wpfApplication1:AppendTextBehavior AppendTextAction="{Binding AppendTextAction, Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
</TextBlock>
Consider we have a property in ViewModel with same signature. And that property is the source of this binding. Then we can invoke that Action anytime, which will automatically invoke our extension method on TextBlock. Here I am invoking the method on a button click. Remember in this case, our Behavior acts like an Adapter between View and ViewModel.
public class ViewModel
{
public Action<string> AppendTextAction { get; set; }
public ICommand ClickCommand { get; set; }
public ViewModel()
{
ClickCommand = new DelegateCommand(OnClick);
}
private void OnClick()
{
AppendTextAction.Invoke(" test");
}
}
Seems like a reasonable request to me. AppendText is definitely very fast, as it deals with pointers. Pretty much every answer in MVVM world be either subclassing, or attached properties.
You can create new interface, call it ITextBuffer:
public interface ITextBuffer
{
void Delete();
void Delete(int offset, int length);
void Append(string content);
void Append(string content, int offset);
string GetCurrentValue();
event EventHandler<string> BufferAppendedHandler;
}
internal class MyTextBuffer : ITextBuffer
{
#region Implementation of ITextBuffer
private readonly StringBuilder _buffer = new StringBuilder();
public void Delete()
{
_buffer.Clear();
}
public void Delete(int offset, int length)
{
_buffer.Remove(offset, length);
}
public void Append(string content)
{
_buffer.Append(content);
var #event = BufferAppendedHandler;
if (#event != null)
#event(this, content);
}
public void Append(string content, int offset)
{
if (offset == _buffer.Length)
{
_buffer.Append(content);
}
else
{
_buffer.Insert(offset, content);
}
}
public string GetCurrentValue()
{
return _buffer.ToString();
}
public event EventHandler<string> BufferAppendedHandler;
#endregion
}
This will be used throughout the viewmodels. All you have to do now, is write an attached property that takes advance of such interface, when you do bindings.
Something like this:
public sealed class MvvmTextBox
{
public static readonly DependencyProperty BufferProperty =
DependencyProperty.RegisterAttached(
"Buffer",
typeof (ITextBuffer),
typeof (MvvmTextBox),
new UIPropertyMetadata(null, PropertyChangedCallback)
);
private static void PropertyChangedCallback(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs depPropChangedEvArgs)
{
// todo: unrelease old buffer.
var textBox = (TextBox) dependencyObject;
var textBuffer = (ITextBuffer) depPropChangedEvArgs.NewValue;
var detectChanges = true;
textBox.Text = textBuffer.GetCurrentValue();
textBuffer.BufferAppendedHandler += (sender, appendedText) =>
{
detectChanges = false;
textBox.AppendText(appendedText);
detectChanges = true;
};
// todo unrelease event handlers.
textBox.TextChanged += (sender, args) =>
{
if (!detectChanges)
return;
foreach (var change in args.Changes)
{
if (change.AddedLength > 0)
{
var addedContent = textBox.Text.Substring(
change.Offset, change.AddedLength);
textBuffer.Append(addedContent, change.Offset);
}
else
{
textBuffer.Delete(change.Offset, change.RemovedLength);
}
}
Debug.WriteLine(textBuffer.GetCurrentValue());
};
}
public static void SetBuffer(UIElement element, Boolean value)
{
element.SetValue(BufferProperty, value);
}
public static ITextBuffer GetBuffer(UIElement element)
{
return (ITextBuffer)element.GetValue(BufferProperty);
}
}
The idea here is to wrap StringBuilder into an interface (as it raises no events by default :) which can then be exploited by an attached property & TextBox actual implementation.
In your viewmodel, you'd probably want something like this:
public class MyViewModel
{
public ITextBuffer Description { get; set; }
public MyViewModel()
{
Description= new MyTextBuffer();
Description.Append("Just testing out.");
}
}
and in the view:
<TextBox wpfApplication2:MvvmTextBox.Buffer="{Binding Description}" />
I have a dropdown (ComboBox) that displays all the com ports available on a machine. Now, ports come and go when you connect and disconnect devices.
For performance reasons I don't want to keep calling System.IO.Ports.SerialPort.GetPortNames(), but rather just call that when the user clicks on the Combobox? Is this possible? Is there an MVVM approach to this problem?
Use InvokeCommandAction.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
DropDownOpenedCommand is an ICommand property on your ViewModel.
<ComboBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DropDownOpened">
<i:InvokeCommandAction Command="{Binding DropDownOpenedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Edit: obviously DropDownOpened not SelectionChanged, as Patrice commented.
You can use something like MVVMLight's EventToCommand to accomplish this. Basically, the event of clicking the combo would be hooked to your MVVM command binding, which would then fire the method that calls GetPortNames().
Here are some alternatives:
MVVM Light: Adding EventToCommand in XAML without Blend, easier way or snippet? (check the accepted answer)
http://www.danharman.net/2011/08/05/binding-wpf-events-to-mvvm-viewmodel-commands/ (Prism)
What I would recommend is scrapping the 'only update on clicks' idea, and just use binding and notifications for this (unless for some reason you think there will be so many Connect/Disconnect events it will slow your system). The simplest version of that would be a dependency property.
Provide an IObservableList<Port> property as a dependency property on your ViewModel like this:
/// <summary>
/// Gets or sets...
/// </summary>
public IObservableList<Port> Ports
{
get { return (IObservableList<Port>)GetValue(PortsProperty); }
set { SetValue(PortsProperty, value); }
}
public static readonly DependencyProperty PortsProperty = DependencyProperty.Register("Ports", typeof(IObservableList<Port>), typeof(MyViewModelClass), new PropertyMetadata(new ObservableList<Port>));
Now you may add/remove items to/from that list whenever you connect or disconnect devices, just do not replace the list. This will force the list to send off a ListChangedEvent for each action on the list, and the ComboBox (or any other bound UI) will react to those events.
This should be performant enough for you, as this will only cause the UI ComboBox to update whenever an event goes through.
I took a stab at routing events to a command:
XAML:
<ComboBox
ItemsSource="{Binding Items}"
local:ControlBehavior.Event="SelectionChanged"
local:ControlBehavior.Command="{Binding Update}" />
Code:
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
namespace StackOverflow
{
public class ControlBehavior
{
public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(ControlBehavior));
public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ControlBehavior));
public static DependencyProperty EventProperty = DependencyProperty.RegisterAttached("Event", typeof(string), typeof(ControlBehavior), new PropertyMetadata(PropertyChangedCallback));
public static void EventHandler(object sender, EventArgs e)
{
var s = (sender as DependencyObject);
if (s != null)
{
var c = (ICommand)s.GetValue(CommandProperty);
var p = s.GetValue(CommandParameterProperty);
if (c != null && c.CanExecute(s))
c.Execute(s);
}
}
public static void PropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs a)
{
if (a.Property == EventProperty)
{
EventInfo ev = o.GetType().GetEvent((string)a.NewValue);
if (ev != null)
{
var del = Delegate.CreateDelegate(ev.EventHandlerType, typeof(ControlBehavior).GetMethod("EventHandler"));
ev.AddEventHandler(o, del);
}
}
}
public string GetEvent(UIElement element)
{
return (string)element.GetValue(EventProperty);
}
public static void SetEvent(UIElement element, string value)
{
element.SetValue(EventProperty, value);
}
public ICommand GetCommand(UIElement element)
{
return (ICommand)element.GetValue(CommandProperty);
}
public static void SetCommand(UIElement element, ICommand value)
{
element.SetValue(CommandProperty, value);
}
public object GetCommandParameter(UIElement element)
{
return element.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(UIElement element, object value)
{
element.SetValue(CommandParameterProperty, value);
}
}
}
I am having a problem with xaml ... a button I have created is not enable. here is the xaml part:
<Button Margin="0,2,2,2" Width="70" Content="Line"
Command="{x:Static local:DrawingCanvas.DrawShape}"
CommandTarget="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}, Path=DrawingTarget}"
CommandParameter="Line">
</Button>
Before Constructor it goes:
public static RoutedCommand DrawShape = new RoutedCommand();
in ctor I have:
this.CommandBindings.Add(new CommandBinding(DrawingCanvas.DrawShape, DrawShape_Executed, DrawShapeCanExecute));
Then I have:
private void DrawShapeCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true; **//Isn't this enough to make it enable?**
en.Handled = true;
}
private void DrawShape_Executed(object sender, ExecutedRoutedEventArgs e)
{
switch (e.Parameter.ToString())
{
case "Line":
//some code here (incomplete yet)
break;
}
When I remove the first line (Command="{x:Static ...}") in the block it gets enable again!
Be sure the CanExecute property of that command is returning true. If it returns false, it automatically disables the control that utilizes that command.
Can execute should return a bool, I'm a little surprised that doesn't give a compile error. Anyway try to change it to this.
private bool DrawShapeCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
return true;
}
EDIT:
Ok since you just revealed all you want is a simple button that executes a command here's a very simple implementation copied from one of my recent projects. First define this class somewhere.
public class GenericCommand : ICommand
{
public event EventHandler CanExecuteChanged { add{} remove{} }
public Predicate<object> CanExecuteFunc{ get; set; }
public Action<object> ExecuteFunc{ get; set; }
public bool CanExecute(object parameter)
{
return CanExecuteFunc(parameter);
}
public void Execute(object parameter)
{
ExecuteFunc(parameter);
}
}
Next define a command in your view model and define both the properties I created in the generic command (it's just the basic stuff that comes along with implementing the ICommand interface).
public GenericCommand MyCommand { get; set; }
MyCommand = new GenericCommand();
MyCommand.CanExecuteFunc = obj => true;
MyCommand.ExecuteFunc = obj => MyMethod;
private void MyMethod(object parameter)
{
//define your command here
}
Then just wire up the button to your command.
<Button Command="{Binding MyCommand}" />
If this is all too much for you (MVVM does require a little extra initial setup). You can always just do this...
<Button Click="MyMethod"/>
private void MyMethod(object sender, RoutedEventArgs e)
{
//define your method
}
I have this markupExtension Class
[MarkupExtensionReturnType(typeof(FrameworkElement))]
[ContentProperty("content")]
public class InsereSom : MarkupExtension
{
public InsereSom()
{ }
[ConstructorArgument("Ligado")]
public bool Ligado
{
get;
set;
}
[ConstructorArgument("Evento")]
public RoutedEvent Evento
{
get;
set;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
FrameworkElement elemento = target.TargetObject as FrameworkElement;
RoutedEventHandler metodo = new RoutedEventHandler(EventoInsereSom);
elemento.AddHandler(Evento, metodo);
EventInfo eventInfo = elemento.GetType().GetEvent("Click");
FrameworkElement parentClass = (MainWindow)((Grid)elemento.Parent).Parent;
Delegate methodDelegate = Delegate.CreateDelegate(eventInfo.EventHandlerType, parentClass, "Button_Click");
eventInfo.RemoveEventHandler(elemento, methodDelegate);
eventInfo.AddEventHandler(elemento, methodDelegate);
return new System.Windows.Controls.Label();
}
public void EventoInsereSom(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello Extension Markup");
}
And this Xaml
<Button Width="80" Height="25" Click="Button_Click" Name="BtnTeste">
<Cei:InsereSom Ligado="True" Evento="Button.Click"/>
</Button>
And this code behind
public void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Event code behind");
}
I'd like that my method in my markup class execute first than the method in the code behind.
I try to add and remove the EventHandler but for that I need the event name ("Button_Click"). But cant use it hard code.
are there any other way to to id?
Thanks.
I'd like that my method in my markup class execute first than the method in the code behind.
It's not possible, the order in which event handlers are called can only be controlled by the class that raises the event (the button in that case). It's like a newspaper: when you subscribe to it, you can't say "I want to receive my paper before my neighbor"...
However there is a way to have the markup extension detect the click before the code-behind: you can make it handle the PreviewClick event (which is the tunnelling version of Click)