Problem
I'm currently trying to find a MVVMLight compatible way to use gestures in my WP8 app. Specifically I just want to detect a swipe/flick and bind it to a RelayCommand in my view model. Has there been any recent solution developed over the years that I'm unaware of?
Prior Research
I've done some research before hand, and the results I've come up with are mostly outdated or no longer exist. i.e:
Old Stackoverflow Question
Clarity Consulting Blog Post with non-existant code
toolkit:GestureListener from the Windows Phone Toolkit supports gestures but requires you to couple the ViewModel with the View.
Edit
Note: Found out that toolkit:GestureListener has been deprecated.
Joost Van Schaaik created such a behaviour on wp7: http://dotnetbyexample.blogspot.be/2011/03/simple-windows-phone-7-silverlight.html
He can be contacted on twitter by #localjoost
Found the answer to my question.
Instead of using toolkit:GestureListener, I found out that EventToCommand with ManipulationDelta or ManipulationCompleted works as well:
In XAML
<i:Interaction.Triggers>
<i:EventTrigger EventName="ManipulationDelta">
<Command:EventToCommand Command="{Binding SlideOutDeltaCommand, Mode=OneWay}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="ManipulationCompleted">
<Command:EventToCommand Command="{Binding SlideOutCompletedCommand, Mode=OneWay}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
By passing in EventArgs to the ViewModel, you can detect whether a swipe gesture has been issued:
In ViewModel
Define RelayCommand
public RelayCommand<ManipulationDeltaEventArgs> SlideOutDeltaCommand
{
get;
private set;
}
Define Execute() Method
private void OnSlideDelta(ManipulationDeltaEventArgs e)
{
var delta = e.CumulativeManipulation.Translation;
//If Change in X > Change in Y, its considered a horizontal swipe
var isDeltaHorizontal = Math.Abs(delta.X) > Math.Abs(delta.Y) ? true : false;
}
Register your Command in ViewModel Constructor
public MainViewModel()
{
SlideOutDeltaCommand = new RelayCommand<ManipulationDeltaEventArgs>((e) => OnSlideDelta(e));
}
Related
I am trying to understand how to setup EventTriggerBehaviors in a UWP project.
So I understand that I need to have the package Microsoft.Xaml.Behaviors.Uwp.Managed installed, and the following namespaces declared in my XAML file:
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
The button by itself should be declared as:
<Button x:Name="btnTest >
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="GotFocus" >
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding ... }" />
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
but then I got lost... What I would like is once the button get focus, it set some text (based on button name) within a text box.
Do I need a service, and what should be the ViewModel code?
and actually, is anyone able to recommend great reading, examples, books ... on the subject please?
Update following James reply:
The XAML InvokeCommandAction becomes:
<Core:InvokeCommandAction Command="{Binding OnButtonFocusCommand}" CommandParameter="{Binding Name, ElementName=btnTest}" />
But how do I receive the parameter within the method in the ViewModel?
The InvokeCommandAction Command property requires an implementation of an ICommand in your view model in order to perform an action when the EventTriggerBehavior is fired.
You might have something like this in the XAML:
<Button x:Name="btnTest">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="GotFocus">
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding OnButtonFocusCommand}" />
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
Then in the bound view-model, you would have something similar to this:
public ViewModel()
{
OnButtonFocusCommand = new DelegateCommand(() =>
{
this.TextBoxText = "Hello, World";
});
}
public ICommand OnButtonFocusCommand { get; private set; }
The DelegateCommand is not built in to the platform though but you can find many implementations of a DelegateCommand or RelayCommand online.
EDIT: You can also do this with a passed parameter using like this:
public ViewModel()
{
OnButtonFocusCommand = new DelegateCommand<RoutedEventArgs>(args =>
{
this.TextBoxText = "Hello, World";
});
}
The RoutedEventArgs would be the type of parameter you're passing through. In the case of what's passed by the Focus event, this is the parameter that you'll receive. You'll need the DelegateCommand{T} for these scenarios.
The examples of DelegateCommand that I've referenced also have a mechanism to check whether to run the action by validating the model. You can do that like so:
public ViewModel()
{
OnButtonFocusCommand = new DelegateCommand<RoutedEventArgs>(args =>
{
this.TextBoxText = "Hello, World";
},
args => args.OriginalSource is TextBox);
}
For your scenario with updating the text of a TextBox, you would need to create a property in your view-model (in my example I showed the TextBoxText being updated). That property would then need binding up to the Text property of your TextBox in XAML.
For things to take a look at, off the top of my head, I would suggest taking a look at an MVVM framework (possibly MvvmLight) and reading up on that if you've not already.
Also the official Microsoft samples on GitHub may cover a lot of topics which might be useful to you.
If you need any more information, get in touch and I'm happy to help.
I am new in Caliburn Micro and learn it from this helloworld example. In the example there are only 2 views (.xaml) of type Application and UserControl, and 1 view model.
I avoid to use code behind. Therefore I have only view and view model. I want to know how to catch the window close event of my helloworld application so I can handle it in view model. My target: when user is going to close the app by pressing close [x] button on top-right corner the app gives feedback to the user.
I have read about IViewAware and IScreen, but I find no specific example related to my question.
A simple sample code for view and view model are highly appreciated. Thanks in advance.
PS. I use VS2013, C#.
What you can do is in your View you can attach Caliburn Micro by using
cal:Message.Attach="[Event Closing] = [Action OnClose($eventArgs)]"
So it will look like
<Window cal:Message.Attach="[Event Closing] = [Action OnClose($eventArgs)]">
And on your ViewModel you can just define a public method that says OnClose with CancelEventArgs as the parameter and you can handle it from there.
If your ViewModel inherits Screen, Caliburn Micro has some methods that you can override like
protected override void OnDeactivate(bool close);
this is called when a screen is closed or deactivated or
public override void CanClose(Action<bool> callback)
you can check CanClose usage here
If you are using the BootstrapperBase class you can use:
protected override void OnExit(object sender, EventArgs e)
You're looking for a way to bind an Event to a Command. The typical approach here is to use the EventToCommand behavior from MVVMLight.
Example usage (from the linked article):
<StackPanel Background="Transparent">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<command:EventToCommand
Command="{Binding Main.NavigateToArticleCommand,
Mode=OneWay,
Source={StaticResource Locator}}"
CommandParameter="{Binding Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<!--...-->
</StackPanel>
For your specific scenario, you are not using MVVMLight. Since that framework is open-source, you could copy the implementation of EventToCommand into your own project, or - more simply - you can use the InvokeCommandAction, which is part of the System.Windows.Interactivity.dll library, included with Expression Blend.
Example of InvokeCommandAction:
<TextBox x:Name="TicketNumber">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<i:InvokeCommandAction Command="{Binding OpenTicketCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Lastly, this entire MVVM dogma that you "can't have any code behind" has been shot down time | and | time again (that last link is particularly relevant). MVVM is supposed to be unit-testable, and separates the "View logic" from the "Business logic." The "Close" event is admittedly a bit of a gray area between View and Business logic. But, if you can write an event handler in your code behind, which invokes your ViewModel's appropriate method or command, and if you can unit test that code, then you're as good as gold. Don't worry about removing all traces of code-behind from your project.
One thing I am really not sure about is how to properly pass mouse events to the ViewModel. There is the way of binding triggers using the interactivity extension like for instance in: WPF event binding from View to ViewModel?
But this does not forward the MouseEventArgs to my knowledge, and this solution does not appear very elegant to me.
So what would be the proper solution? One way is to register an event and to handle it in the code behind, e.g.:
private void ListBox_PreviewMouseDown(object sender, System.Windows.Input.MouseEventArgs e)
{
var listbox = sender as ListBox;
if (listbox == null)
return;
var vm = listbox.DataContext as MainViewModel;
if (vm == null)
return;
// access the source of the listbox in viewmodel
double x = e.GetPosition(listbox).X;
double y = e.GetPosition(listbox).Y;
vm.Nodes.Add(new Node(x, y));
}
Here I assume that the listbox's ItemsSource is bound to the vm.Nodes property. So again the question: is it the proper way of doing it? Or is there a better one?
Good timing, I wrote some code to do exactly this about two hours ago. You can indeed pass arguments, and personally I thnk it is elegant because it allows you to fully test your user interface. MVVM Lite allows you to bind events to commands with EventToCommand, so start by adding the relevant namespaces to your control/window:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"
Now add event triggers to the child control whose events you want to intercept:
<ItemsControl ... etc ... >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseDownCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseUp">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseUpCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseMove">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseMoveCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ItemsControl>
In my specific case I'm rendering a collection of items onto a canvas, hence my use of ItemsControl, but it'll work on anything including the parent window. It will also work for key strokes (e.g. KeyDown) but if your child control isn't focus-able then you'll have to add the trigger to the parent instead. In any case all that remains is to add the relevant handlers to your view model:
public class MyViewModel : ViewModelBase
{
public ICommand MouseDownCommand { get; set; }
public ICommand MouseUpCommand { get; set; }
public ICommand MouseMoveCommand { get; set; }
public ICommand KeyDownCommand { get; set; }
// I'm using a dependency injection framework which is why I'm
// doing this here, otherwise you could do it in the constructor
[InjectionMethod]
public void Init()
{
this.MouseDownCommand = new RelayCommand<MouseButtonEventArgs>(args => OnMouseDown(args));
this.MouseUpCommand = new RelayCommand<MouseButtonEventArgs>(args => OnMouseUp(args));
this.MouseMoveCommand = new RelayCommand<MouseEventArgs>(args => OnMouseMove(args));
this.KeyDownCommand = new RelayCommand<KeyEventArgs>(args => OnKeyDown(args));
}
private void OnMouseDown(MouseButtonEventArgs args)
{
// handle mouse press here
}
// OnMouseUp, OnMouseMove and OnKeyDown handlers go here
}
One last thing I will mention that is only a little bit off-topic is that often you'll need to communicate back to the code-behind e.g. when the user presses the left mouse button you might need to capture the mouse, but this can easily be accomplished with attached behaviors. The mouse capture behavior is simple enough, you just add a "MouseCaptured" boolean property to your view model, bind your attached behavior to it and have it's changed handler respond accordingly. For anything more complicated you might want to create an event inside your view model which your attached behaviour can then subscribe to. Either way, your UI is now fully unit-testable and your code-behind has been moved into generic behaviors for re-use in other classes.
I think your approach is good. Those events, that work with View, can be in your code-behind if you handlers work via ViewModel. However, there is an alternative use GalaSoft.MvvmLight (link to download), in which have EventToCommand, supports parameter PassEventArgsToCommand.
Example of using:
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<cmd:EventToCommand Command="{Binding FooCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
I think you can use both approaches. Your solution is simple, does not require the use of the any frameworks but uses code-behind, in this case it is not critical. One thing is certain, it is advisable not to keep ViewModel event handlers, use the command or store these handlers on View side.
Some new notes
I think, your way does not violate the principles of MVVM, all event handlers working with View, should be on the side of the View, the main thing - it's event handlers need to work with a ViewModel and have a dependency via an interface, but not directly with the UI.
The only principle MVVM that you break - is the mantra "no code" and this is not the main principle of MVVM. The main principles:
Split data Model of View
Application logic should not be tied to UI
Support testability code
Once the code-behind violate at least one of these principles, you must already see the alternatives to solve their problem.
Also, you can read opinions about it on this link:
WPF MVVM Code Behind
Does exist a similar EventToCommand (behavior's) on Android?
Example Silverlight/WPF:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Commanding:EventToCommand Command="{Binding DataContext.EditEventTypeCommand,
RelativeSource={RelativeSource AncestorType=telerik:RadGridView}}"
CommandParameter="{Binding}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
If you are using mvvmcross, then bindings exist for all events which have EventHandler signatures - so you can just use bindings like:
'Click':{'Path':'MyViewModelCommand'}
There are plenty of examples of this in the samples, plus there are several questions oh here - search for MvvmCross and click.
If you need to bind to an event which has an EventHandler<T> signature then you can add this yourself - see mvvmcross touch command binding in android
I'm not sure I have understood your question (I don't know Silverlight) but you can do as follows:
In your XML:
<Button ...
android:onClick="onMyButtonClicked".../>
In your java file
public void onMyButtonClicked(View sender) { ... }
There are no other ways to specify event handlers in Android XML, I think. You can specify a lot of properties though.
I have a view containing a button. And i want to perform an action on Hold event. How can i do this in mvvm? For Tap event i can Bind it to a Command property. Is it possible to do this with same way?
I would go with Braulio's answer - MVVM Light is what I would use, but back in the Silverlight 3 days I used custom attached properties to achieve this. See here for an example of custom attached properties: http://umairsaeed.com/2010/04/22/custom-attached-properties-in-silverlight/
You could create a custom attached property for the hold event to bind the command to and then use it like so:
<Border local:MyTextBoxControl.HoldEventCommand="{Binding HoldCommand}"/>
This is a lot of work compared with including the mvvm light toolkit in your project and then doing this:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Hold">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding YourCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Not sure if it supports the command, if not you can use MVVM Light Toolkit (free and open source) behavior: EventToCommand