WPF DataBinding, MVVM and Routed Events - wpf

When I try to call my UserControl's custom RoutedEvent in my XAML using a Command Delegate binding I get this exception:
Exception thrown: 'System.Windows.Markup.XamlParseException' in PresentationFramework.dll
Additional information: 'Provide value on 'System.Windows.Data.Binding' threw an exception.'
My WPF application is using MVVM and IOC so all the logic is in the view models.
The DataContext for a view is set in a resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:MyApp.Order.View"
xmlns:vm="clr-namespace:MyApp.Order.ViewModel">
<DataTemplate DataType="{x:Type vm:OrderViewModel}">
<vw:OrderView />
</DataTemplate>
etc
I have a UserControl in another project within this solution that happens to be a Keyboard and I've wired up a routed event like so:
public partial class MainKeyboard : UserControl
{
public static DependencyProperty TextProperty;
public static DependencyProperty FirstLetterToUpperProperty;
// Create a custom routed event by first registering a RoutedEventID
// This event uses the bubbling routing strategy
public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
"Tap", RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MainKeyboard));
// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
add { AddHandler(TapEvent, value); }
remove { RemoveHandler(TapEvent, value); }
}
// This method raises the Tap event
void RaiseTapEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(MainKeyboard.TapEvent);
RaiseEvent(newEventArgs);
}
Now, when I consume this event using my XAML declration and bind the Command to the view model's command delegate like I normally do:
<assets:MainKeyboard Tap="{Binding DoTapCommandInViewModel}" />
I get an error, but I can do this everywhere else, like in a button right above this code:
<Button Content="GetStarted"
Command="{Binding DoTapCommandInViewModel}"
Style="{StaticResource GetStartedButton}" />
What does work for me is calling a method in the code behind of this XAML file:
<assets:MainKeyboard Tap="MainKeyboard_OnTap"/>
The datacontext for both the button and the keyboard user control are the same; they live in the same view.
So why can't I bind this event directly to a command?

Dytori was right; I needed an Event Trigger on the control declaration. Ah Wpf...
<assets:MainKeyboard>
<b:Interaction.Triggers>
<b:EventTrigger EventName="Tap">
<b:InvokeCommandAction Command="{Binding DoTapTapTap}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
</assets:MainKeyboard>

Related

WPF. Change DataContext on event binding to access code-behind on a MVVM project

i'm developing a WPF application with MVVM.
At the XAML code i have a Grid with its DataContext pointing to a ViewModel, and i need to know if it is possible to change the DataContext at runtime to access an event at its code-behind.
Code-behind for the view:
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = new MainViewModel();
InitializeComponent();
}
private void ValidationEvent(object sender, ValidationErrorEventArgs e)
{
//Something useful
}
}
Here is the code that i tried in XAML:
<Grid Validation.Error={Binding Path=ValidationEvent RelativeSource={RelativeSource Self}}/>
The XAML code throws an XamlParseException telling that it is not possible to do the Binding on an "AddErrorHandler", that it is only possible for a DependencyProperty on a DependencyObject.
I don't want to change the DataContext of the Grid because inside it there are elements that access the MainViewModel properties, so i just want to change the DataContext for the Validation.Error event binding... If it is possible...
Thanks.
Validation.Error is an event, not a property. You can't set Bindings to events.
You can use things like MVVM Light's EventToCommand, or Microsoft's own Interactivity EventTrigger to associate Commands to Events.
But there really isn't anything wrong with just adding a regular event handler in code-behind and calling some viewmodel code from there... Contrary to what many people seem to think, MVVM doesn't forbid the use of code-behind and what you'd be doing is not very different from what an EventToCommand or an EventTrigger are doing under the hood.
First of all, just set the event handler name for the Validation.Error event.
<Grid Validation.Error="ValidationEvent" />
And then in your code-behind do whatever you want.
private void ValidationEvent(object sender, ValidationErrorEventArgs e)
{
// Something useful
// Some call to VM code
(this.DataContext as MainViewModel).SomeMethod();
}
This works independently of your DataContext (as long as you cast this.DataContext to the correct type, of course).
Event handlers don't depend on your DataContext, only Bindings do.

Move custom event handler from View to ViewModel

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 to navigate from one view to another view from viewmodel in silverlight?

I have one ViewModel and two Views. How can I navigate to View2 from ViewModel. I read somewhere that we need to use PRISM, for opening multiple Views from ViewModel in Silverlight. Is there any alternative for PRISM?
Ideally you do not want to use view logic in your viewmodel. Your viewmodel should not know anything about the view. It would be a better idea for your viewmodel to set a property letting the view know it's time to navigate.
Here's an example:
ViewModel:
using System.ComponentModel;
namespace ViewModels
{
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private bool _DoneDoingStuff;
public bool DoneDoingStuff
{
get
{
return _DoneDoingStuff;
}
set
{
if (_DoneDoingStuff != value)
{
_DoneDoingStuff = value;
NotifyPropertyChanged("DoneDoingStuff");
}
}
}
}
}
View:
<navigation:Page
x:Class="Views.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:vm="clr-namespace:ViewModels">
<navigation:Page.Resources>
<vm:MyViewModel
x:Key="MyViewModelInstance" />
</navigation:Page.Resources>
<Grid
x:Name="LayoutRoot"
DataContext="{Binding Source={StaticResource MyViewModelInstance}}">
<i:Interaction.Triggers>
<ei:DataTrigger
Binding="{Binding DoneDoingStuff}"
Value="True">
<ei:HyperlinkAction
NavigateUri="AnotherPage.xaml" />
</ei:DataTrigger>
</i:Interaction.Triggers>
</Grid>
</navigation:Page>
Use a DataTrigger with Binding property set to the DoneDoingStuff property from your viewmodel, and the Value property set to "True". The DataTrigger will trigger when DoneDoingStuff from your viewmodel is set to true.
Now you need a trigger action to navigate. Use HyperlinkAction with the NavigateUri property set to the page you're navigating to.
Be sure to have System.Windows.Interactivity, System.Windows.Controls.Navigation, and Microsoft.Expression.Interactions assemblies in your references.
At first, this might seem to be too much, but your view logic is now where it needs to be.
You don't need to use PRISM, but it might be best.
One way that I have done it (and it is sloppy) is to have a MainView page that has a Navigation frame in it that will load the first view on start up. The MainView has to be a Page and not a UserControl. You need to have a navigation frame with uri mappings in the xaml and have a frame declared as shared/static in the code behind of the MainView Page and then set the loaded event (in the xaml) of the frame like so:
Public Shared MainContentFrame As Frame
Private Sub MainContentFrameXaml_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs)
MainContentFrame = TryCast(sender, Frame)
End Sub
Then in the viewmodel you can just call:
MainView.MainContentFrame.Navigate(New Uri("/SecondView", UriKind.Relative))
This probably violates the MVVM pattern in some way and may not be a good way to do it, but it works. This is how I used to do it, now I use PRISM.

Galasoft RelayCommand not firing

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); }
}

Key press inside of textbox MVVM

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.

Resources