I am using the MVVM Light framework to build a SL4 application. My simple app is composed primarily by a single main view (shellView), which is divided into multiple UserControls. They are just a convenient separation of the UI, therefore they don't have their own ViewModel.
The ShellView contains a Keypad (custom usercontrol) that contains multiple KeypadButtons (custom usercontrols).
I am quite sure (because I've checked) that the DataContext is set properly and it is used by all usercontrols in the hierarchy. (ShellView's Datacontext is ShellViewModel, Keypad's DataContext is ShellViewModel, etc.).
In the ShellViewModel I have a ICommand (RelayCommand) that is named "ProcessKey".
In the Keypad control, I have something like:
<controls:KeypadButton x:Name="testBtn" Text="Hello">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding PressStandardKeyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</controls:KeypadButton>
The KeypadButton is basically a Grid that contains a Button. The MouseLeftButtonUp event is caught and a custom "Click" event is fired. Let me show you some code to explain easily what I am doing:
public partial class KeypadButton : UserControl
{
public delegate void KeypadButtonClickHandler(object sender, RoutedEventArgs e);
public event KeypadButtonClickHandler Click;
public KeypadButton()
{
// Required to initialize variables
InitializeComponent();
}
private void innerButton_Click(object sender, MouseButtonEventArgs e)
{
if (Click != null)
Click(sender, new KeypadButtonEventArgs());
}
}
public class KeypadButtonEventArgs : RoutedEventArgs
{
public string test { get; set; }
}
Now, if I set a breakpoint to the body of innerButton_Click, I can see the Click is properly caught and it contains points to the RelayCommand. However, nothing happens: "Click(sender, new KeypadButtonEventArgs());" is executed but nothing more.
Why is this behaving so? Shouldnt execute the target function that is defined in the RelayCommand? Is maybe a scope-related issue?
Thanks in advance,
Cheers,
Gianluca.
As noted by other comments, this is probably related to the Click event not being a RoutedEvent.
As a quick hack you might be able to use MouseLeftButtonDown instead of the Click event on your UserControl.
<!-- Kinda Hacky Click Interception -->
<controls:KeypadButton x:Name="testBtn" Text="Hello">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding PressStandardKeyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</controls:KeypadButton>
Another option you could consider is inheriting from Button instead of UserControl. Silverlight Show has an article about inheriting from a TextBox that probably is relevant for this.
Routed events should be defined like this (see documentation):
public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
"Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));
// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
add { AddHandler(TapEvent, value); }
remove { RemoveHandler(TapEvent, value); }
}
Related
I have a custom event named OnVisualChartRangeChanged being fired from a UserControl called HistoricChartControl.
I am using the control in my main application like this:
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModels:HistoricViewModel/>
</Window.DataContext>
<Grid>
<historicChart:HistoricChartControl >
<historicChart:HistoricChartControl
behaviours:ChartBehavior.OnVisualChartRangeChanged="VisualChartRangeChanged"/>
</historicChart:HistoricChartControl>
</Grid>
I want that instead of having the event being handled in the view via the method VisualChartRangeChanged, the event be handled in the ViewModel.
How could I modify my code for this to happen? It would be helpful if you could post specific code as I am new to the WPF way of doing things.
Thanks.
The solution is to use Commands.
Since its a UserControl you may manipulate it to implement ICommandSource interface.
Then your UserControl will be able to bind a Command to ViewModel.
Once the event is being fired you simply call the command which will invoke Execute() method from the ViewModel.
For commanding in WPF I suggest you to read following link:
http://msdn.microsoft.com/en-us/library/ms752308(v=vs.110).aspx
In your ViewModel you will have to offer a property of type ICommand.
EDIT Since you cannot manipulate your UserControl you will have to attach a command on it in XAML.
Interactivity is also an alternative to solve your issue. Take a look at this code:
xmlns:I="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<ListBox ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
Interactivity is a third party dll from Microsoft Blend.
If you have nuget in visual studio you will be able to find that dll. If not here is the link: http://www.nuget.org/packages/System.Windows.Interactivity.WPF/
This answer is changed once.
Interactivity Solution:
If the used behavior is reusable (you have its source) you can simply move the logic of this behavior to ViewModel level. Follow these 4 steps and it should work if the bindings and DataContext values are correct.
Add reference of both System.Windows.Interactivity and Microsoft.Expression.Interactions to your project:
Create a Command in ViewModel
//ViewModel:
public ICommand VisualChartRangeChangedCommand
{
get { return (ICommand)GetValue(VisualChartRangeChangedCommandProperty); }
set { SetValue(VisualChartRangeChangedCommandProperty, value); }
}
public static readonly DependencyProperty VisualChartRangeChangedCommandProperty =
DependencyProperty.Register("VisualChartRangeChangedCommand", typeof(ICommand), typeof(ViewModel), new UIPropertyMetadata(null));
//In ViewModel constructor:
VisualChartRangeChangedCommand = new ActionCommand(() => doStuff());
override the Behavior and add command ability to it
public class OnVisualChartRangeChangedWithCommand : OnVisualChartRangeChanged<HistoricChartControl>
{
//MyCommand Dependency Property
public ICommand MyCommand
{
get { return (ICommand)GetValue(MyCommandProperty); }
set { SetValue(MyCommandProperty, value); }
}
public static readonly DependencyProperty MyCommandProperty =
DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(OnVisualChartRangeChangedWithCommand), new UIPropertyMetadata(null));
protected override void OnAttached()
{
//replace MouseEnter with other events related to OnVisualChartRangeChanged
AssociatedObject.MouseEnter += _eh;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseEnter -= _eh;
base.OnDetaching();
}
void _eh(object sender, MouseEventArgs e)
{
if (MyCommand != null)
MyCommand.Execute(null);
}
}
Link the ViewModel's Command to the overriden Behavior
xmlns:I="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:B="clr-namespace:CoreProject.Behaviors;assembly=CoreProject"
<historicChart:HistoricChartControl>
<I:Interaction.Behaviors>
<B:OnVisualChartRangeChangedWithCommand MyCommand="{Binding VisualChartRangeChangedCommand}"/>
</I:Interaction.Behaviors>
</historicChart:HistoricChartControl>
How can I bind a UserControl's FrameworkElement event to a view model command? I use MVVM and Prism so clear separation between the view the view-model would be nice.
I'd tried multiple things, and none of them worked:
<i:Interaction.Triggers>
<i:EventTrigger EventName="FrameworkElement.Unloaded">
<i:InvokeCommandAction Command="{Binding Unloaded}" />
</i:EventTrigger>
</i:Interaction.Triggers>
also using this tutorial http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html
local:FrameworkElementBehavior.UnloadedCommand="{Binding Unloaded}"
Do I have no choice but to add some functions to my code-behind?
Neither of the attempts above error out, but the command does not execute.
Here's my view model:
public class CustomerViewModel : PosViewModelBase
{
public ICommand Unloaded
{
get { return new UnloadedCommand(); }
}
public CustomerViewModel()
{
}
private class UnloadedCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
Debug.WriteLine("Customer stuff is out of view");
}
}
}
I think the problem could be in Unloaded event.
From MSDN page http://msdn.microsoft.com/en-us/library/ms754221.aspx#common_events:
Unloaded is raised last and is initiated by either the presentation source or the visual parent being removed. When Unloaded is raised and handled, the element that is the event source parent (as determined by Parent property) or any given element upwards in the logical or visual trees may have already been unset, meaning that data binding, resource references, and styles may not be set to their normal or last known run-time value.
I have an UserControl. In my UserControl i have a button that I want bind its command to my ViewModel command. Can I do this?
Yes, you could add a routed event to your user control which gets invoked when the button is pressed.
You can then use various techniques to invoke the view model verb when the user control event fires.
E.g. you could use an attached property, or I would recommend using an MVVM framework such as Caliburn.Micro which has Actions that makes it even more straightforward.
I found it...I can define a DependensyProperty typof RelayCommand in my usercontrol and bind my DependensyProperty to my ViewModel Command
I'm not really sure what you mean but I take a shot.
In your code behind, define a RoutedCommand:
public partial class MyUserControl : UserControl
{
public static RoutedCommand Click =
new RoutedCommand("Click", typeof(UserControl));
}
Then it the xaml, set up a command binding:
<UserControl.CommandBindings>
<CommandBinding
Command="{x:Static MyNameSpace:MyUserControl.Click}"
CanExecute="ClickCanExecute"
Executed="ClickExecuted"/>
</UserControl.CommandBindings>
Then add the handlers in the code behind:
private void ClickCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void ClickExecuted(object sender, ExecutedRoutedEventArgs e)
{
// TODO execution logic goes here
}
Was I close? :)
The easiest way is to implement ButtonClick event handler and invoke Window.Close() method, but how doing this through a Command binding?
All it takes is a bit of XAML...
<Window x:Class="WCSamples.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close"
Executed="CloseCommandHandler"/>
</Window.CommandBindings>
<StackPanel Name="MainStackPanel">
<Button Command="ApplicationCommands.Close"
Content="Close Window" />
</StackPanel>
</Window>
And a bit of C#...
private void CloseCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
this.Close();
}
(adapted from this MSDN article)
Actually, it is possible without C# code. The key is to use interactions:
<Button Content="Close">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
In order for this to work, just set the x:Name of your window to "window", and add these two namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
This requires that you add the Expression Blend SDK DLL to your project, specifically Microsoft.Expression.Interactions.
In case you don't have Blend, the SDK can be downloaded here.
I think that in real world scenarios a simple click handler is probably better than over-complicated command-based systems but you can do something like that:
using RelayCommand from this article http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
public class MyCommands
{
public static readonly ICommand CloseCommand =
new RelayCommand( o => ((Window)o).Close() );
}
<Button Content="Close Window"
Command="{X:Static local:MyCommands.CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"/>
If the window was shown with Window.ShowDialog():
The simplest solution that I know of is to set the IsCancel property to true of the close Button:
<Button Content="Close" IsCancel="True" />
No bindings needed, WPF will do that for you automatically!
This properties provide an easy way of saying these are the "OK" and "Cancel" buttons on a dialog. It also binds the ESC key to the button.
Reference: MSDN Button.IsCancel property.
For .NET 4.5 SystemCommands class will do the trick (.NET 4.0 users can use WPF Shell Extension google - Microsoft.Windows.Shell or Nicholas Solution).
<Window.CommandBindings>
<CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}"
CanExecute="CloseWindow_CanExec"
Executed="CloseWindow_Exec" />
</Window.CommandBindings>
<!-- Binding Close Command to the button control -->
<Button ToolTip="Close Window" Content="Close" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
In the Code Behind you can implement the handlers like this:
private void CloseWindow_CanExec(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CloseWindow_Exec(object sender, ExecutedRoutedEventArgs e)
{
SystemCommands.CloseWindow(this);
}
In the beginning I was also having a bit of trouble figuring out how this works so I wanted to post a better explanation of what is actually going on.
According to my research the best way to handle things like this is using the Command Bindings. What happens is a "Message" is broadcast to everything in the program. So what you have to do is use the CommandBinding. What this essentially does is say "When you hear this Message do this".
So in the Question the User is trying to Close the Window. The first thing we need to do is setup our Functions that will be called when the SystemCommand.CloseWindowCommand is broadcast. Optionally you can assign a Function that determines if the Command should be executed. An example would be closing a Form and checking if the User has saved.
MainWindow.xaml.cs (Or other Code-Behind)
void CloseApp( object target, ExecutedRoutedEventArgs e ) {
/*** Code to check for State before Closing ***/
this.Close();
}
void CloseAppCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
/*** Logic to Determine if it is safe to Close the Window ***/
e.CanExecute = true;
}
Now we need to setup the "Connection" between the SystemCommands.CloseWindowCommand and the CloseApp and CloseAppCanExecute
MainWindow.xaml (Or anything that implements CommandBindings)
<Window.CommandBindings>
<CommandBinding Command="SystemCommands.CloseWindowCommand"
Executed="CloseApp"
CanExecute="CloseAppCanExecute"/>
</Window.CommandBindings>
You can omit the CanExecute if you know that the Command should be able to always be executed Save might be a good example depending on the Application. Here is a Example:
<Window.CommandBindings>
<CommandBinding Command="SystemCommands.CloseWindowCommand"
Executed="CloseApp"/>
</Window.CommandBindings>
Finally you need to tell the UIElement to send out the CloseWindowCommand.
<Button Command="SystemCommands.CloseWindowCommand">
Its actually a very simple thing to do, just setup the link between the Command and the actual Function to Execute then tell the Control to send out the Command to the rest of your program saying "Ok everyone run your Functions for the Command CloseWindowCommand".
This is actually a very nice way of handing this because, you can reuse the Executed Function all over without having a wrapper like you would with say WinForms (using a ClickEvent and calling a function within the Event Function) like:
protected override void OnClick(EventArgs e){
/*** Function to Execute ***/
}
In WPF you attach the Function to a Command and tell the UIElement to execute the Function attached to the Command instead.
I hope this clears things up...
One option that I've found to work is to set this function up as a Behavior.
The Behavior:
public class WindowCloseBehavior : Behavior<Window>
{
public bool Close
{
get { return (bool) GetValue(CloseTriggerProperty); }
set { SetValue(CloseTriggerProperty, value); }
}
public static readonly DependencyProperty CloseTriggerProperty =
DependencyProperty.Register("Close", typeof(bool), typeof(WindowCloseBehavior),
new PropertyMetadata(false, OnCloseTriggerChanged));
private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as WindowCloseBehavior;
if (behavior != null)
{
behavior.OnCloseTriggerChanged();
}
}
private void OnCloseTriggerChanged()
{
// when closetrigger is true, close the window
if (this.Close)
{
this.AssociatedObject.Close();
}
}
}
On the XAML Window, you set up a reference to it and bind the Behavior's Close property to a Boolean "Close" property on your ViewModel:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Behaviors>
<behavior:WindowCloseBehavior Close="{Binding Close}" />
</i:Interaction.Behaviors>
So, from the View assign an ICommand to change the Close property on the ViewModel which is bound to the Behavior's Close property. When the PropertyChanged event is fired the Behavior fires the OnCloseTriggerChanged event and closes the AssociatedObject... which is the Window.
I am just getting started with MVVM and im having problems figuring out how I can bind a key press inside a textbox to an ICommand inside the view model. I know I can do it in the code-behind but im trying to avoid that as much as possible.
Update: The solutions so far are all well and good if you have the blend sdk or your not having problems with the interaction dll which is what i'm having. Is there any other more generic solutions than having to use the blend sdk?
First of all, if you want to bind a RoutedUICommand it is easy - just add to the UIElement.InputBindings collection:
<TextBox ...>
<TextBox.InputBindings>
<KeyBinding
Key="Q"
Modifiers="Control"
Command="my:ModelAirplaneViewModel.AddGlueCommand" />
Your trouble starts when you try to set Command="{Binding AddGlueCommand}" to get the ICommand from the ViewModel. Since Command is not a DependencyProperty you can't set a Binding on it.
Your next attempt would probably be to create an attached property BindableCommand that has a PropertyChangedCallback that updates Command. This does allow you to access the binding but there is no way to use FindAncestor to find your ViewModel since the InputBindings collection doesn't set an InheritanceContext.
Obviously you could create an attached property that you could apply to the TextBox that would run through all the InputBindings calling BindingOperations.GetBinding on each to find Command bindings and updating those Bindings with an explicit source, allowing you to do this:
<TextBox my:BindingHelper.SetDataContextOnInputBindings="true">
<TextBox.InputBindings>
<KeyBinding
Key="Q"
Modifiers="Control"
my:BindingHelper.BindableCommand="{Binding ModelGlueCommand}" />
This attached property would be easy to implement: On PropertyChangedCallback it would schedule a "refresh" at DispatcherPriority.Input and set up an event so the "refresh" is rescheduled on every DataContext change. Then in the "refresh" code just, just set DataContext on each InputBinding:
...
public static readonly SetDataContextOnInputBindingsProperty = DependencyProperty.Register(... , new UIPropetyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var element = obj as FrameworkElement;
ScheduleUpdate(element);
element.DataContextChanged += (obj2, e2) =>
{
ScheduleUpdate(element);
};
}
});
private void ScheduleUpdate(FrameworkElement element)
{
Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
{
UpdateDataContexts(element);
})
}
private void UpdateDataContexts(FrameworkElement target)
{
var context = target.DataContext;
foreach(var inputBinding in target.InputBindings)
inputBinding.SetValue(FrameworkElement.DataContextProperty, context);
}
An alternative to the two attached properties would be to create a CommandBinding subclass that receives a routed command and activates a bound command:
<Window.CommandBindings>
<my:CommandMapper Command="my:RoutedCommands.AddGlue" MapToCommand="{Binding AddGlue}" />
...
in this case, the InputBindings in each object would reference the routed command, not the binding. This command would then be routed up the the view and mapped.
The code for CommandMapper is relatively trivial:
public class CommandMapper : CommandBinding
{
... // declaration of DependencyProperty 'MapToCommand'
public CommandMapper() : base(Executed, CanExecute)
{
}
private void Executed(object sender, ExecutedRoutedEventArgs e)
{
if(MapToCommand!=null)
MapToCommand.Execute(e.Parameter);
}
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute =
MapToCommand==null ? null :
MapToCommand.CanExecute(e.Parameter);
}
}
For my taste, I would prefer to go with the attached properties solution, since it is not much code and keeps me from having to declare each command twice (as a RoutedCommand and as a property of my ViewModel). The supporting code only occurs once and can be used in all of your projects.
On the other hand if you're only doing a one-off project and don't expect to reuse anything, maybe even the CommandMapper is overkill. As you mentioned, it is possible to simply handle the events manually.
The excellent WPF framework Caliburn solves this problem beautifully.
<TextBox cm:Message.Attach="[Gesture Key: Enter] = [Action Search]" />
The syntax [Action Search] binds to a method in the view model. No need for ICommands at all.
Perhaps the easiest transition from code-behind event handling to MVVM commands would be Triggers and Actions from Expression Blend Samples.
Here's a snippet of code that demonstrates how you can handle key down event inside of the text box with the command:
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<si:InvokeDataCommand Command="{Binding MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
The best option would probably be to use an Attached Property to do this. If you have the Blend SDK, the Behavior<T> class makes this much simpler.
For example, it would be very easy to modify this TextBox Behavior to fire an ICommand on every key press instead of clicking a button on Enter.