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.
Related
I wanted to handle MainWindow's Closing event in one of my ViewModel. This ViewModel belongs to a View which is displayed on top of the MainWindow. I wanted to save values of some properties present in that ViewModel while/before closing of the application.
I am using MvvmCross to implement MVVM pattern. So I tried overriding ViewDisappearing, ViewDisappeared and ViewDestroy methods, but they are only getting called when View get switched, but not when application/window closes.
While searching for this requirement, I could only find this answer: https://social.msdn.microsoft.com/Forums/vstudio/en-US/477e7e74-ccbf-4498-8ab9-ca2f3b836597/how-to-know-when-a-wpf-usercontrol-is-closing?forum=wpf , which is close to my requirement, but needs to be implemented in code-behind.
Can anyone please help me in achieving this in MVVM/MvvmCross in WPF?
I had some experience to implement events by MVVM maybe it can help you.
As I know there is a package on Microsoft.Xaml.Behaviors.Wpf that will help to get the events and execute a command
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding ClosingWindow}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
and if you want to execute a command in other view models you can use a static command
xmlns:local="clr-namespace:App.Views.Windows"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{x:static local:ViewModel.ClosingWindow}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I have a WPF project (C#, MVVM Light, Visual Studio 2010).
I have a bit of a problem regarding separation of concerns (MVVM) which basically is this: I have a command in a view model. I have a context menu that I want to call that command. So far so good. The problem is that the command needs to coordinates that the mouse was clicked.
To be a little more specific, the ContextMenu only appears if you click on a particular Canvas control, and it's the coordinates within said Canvas control that I want.
The easy way to do this is to manage it all in the code behind of the XAML document (and I have been able to do it that way), but I'd rather have it within my ViewModel if I can do so. The reason is that there are calls to my data model within this command so we end up with a problem of separation.
I am aware of PassEventArgsToCommand, and I'm aware that it's a bad practise, however in this case I'm not sure I can see a way around it. So for the moment I did try that, and it looks like this:
<ContextMenu x:Key="BackgroundMenu">
<MenuItem Header="Add new node here">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.DataContext.AddNewNodeAtLocationCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
So now my command, within the view model, looks like this:
void AddNewNodeAtLocationExecute(RoutedEventArgs e)
{
return;
}
Within that method I'd like to get those mouse coordinates, but I don't know if it's possible. e.OriginalSource is 'MenuItem', which doesn't help much.
So how can I do this? Can I do this? Or should I just have this one command handled by the code behind? Said code will involve a call to the database, which is why I'm being so particular about the separation.
Thanks in advance.
Well I stumbled across this question which speaks about separation of concerns and what not.
In the end I did a merging of the two ideas I had. Firstly, the ContextMenu simply links to the code behind. At that point I get the coordinates I want. Then that code behind gets the DataContext of the view (where the command I want is) and calls the Execute method (having first checked the 'can' method).
I suppose it's as ideal as you're going to get.
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
Fairly new to Windows Phone and Xaml and I decided to start using the DataTemplates as it looked neater and I could easily switch them etc.
I have a requirement where on a button click depending on the data on the item in the list I want to call a different function or with different parameters. I thought the easiest way would be to bind a RoutedEventHandler to it via an anonymous function.
When I did this in code-behind with static controls on the formed it worked perfectly. It also worked when I added my own controls to a stack panel etc. But it was all quite messy.
// Example of RoutedEventHandler that works when I create the button in code behind
model.clickEventHandler = (s, e) => LoadResult(r.id);
<ScrollViewer Name="scrvResults" >
<ListBox Name="lbResults" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Command="{Binding clickEventHandler}" >
// Stuff
// Doesn't crash but doesn't fire the event
</Button>
<Button Click="{Binding clickEventHandler}" >
// Stuff
// Throws a com exception
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
I've tried various sub options. All the examples i've seen seem to link to a static function. Is this just some syntax i'm getting wrong is can I not bind to it this way?
You need to bind your command to a type of ICommand. See here for more info:
ICommand interface
Command Binding
Button click event can be bound by using interaction triggers, not by simply binding the event to the click attribute:
Using EventTrigger in XAML for MVVM – No Code Behind Code
I am currently transforming a medium size WPF project to MVVM and I ran into a problem that I wasn't able to solve, yet. Maybe you could help me out?
The target framework is .NET 3.5.1.
I have a list view that gets its items from the underlying view model. That view model is exposing a command to remove the selected items from the list view. Therefore the command parameter is bound to the SelectedItems property of the list view.
<ListView ItemsSource="{Binding MyItems}"
x:Name="MyListView"
SelectionMode="Extended">
</ListView>
<Button x:Name="MyRemoveButton"
Content="Remove item"
Command="{Binding RemoveItemCommand}"
CommandParameter="{Binding ElementName=MyListView, Path=SelectedItems}">
My intention is to execute this command not only when pressing a button, but also when the KeyUp event is fired on the list view and the pressed key is "delete".
I was close to finding the solution when I stumbled upon interaction triggers in this example:
http://joyfulwpf.blogspot.com/2009/05/mvvm-invoking-command-on-attached-event.html?showComment=1250325648481#c3867495357686026904
Now the problem with this demo is that the command parameter is the pressed key, but in my case I need the command parameter to be the SelectedItems property and I need the command to execute only on a specific key.
Is there any way to do this without much overhead and in the MVVM way?
Something like this would be awesome:
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<local:CommandAction Command="{Binding RemoveItemCommand}"
CommandParameter={Binding ElementName=MyListView, Path=SelectedItems}
EventArgument="Key.Delete"/>
</i:EventTrigger>
</i:Interaction.Triggers>
To do it in the MVVM way you need to bind "SelectedItems" property of the ListView to your ViewModel, so you could use it from your commands and wouldn't need to pass it via CommandParameter.
How strict is your separation requirement? If you don't have designers using Blend, then put a call to a ViewModel method into the KeyUp or PreviewKeyUp event handler in your code-behind.