Very basic mvvm cross question...
I am creating a WPF UI and want to make an image map. I have my path objects defined and now want to hook up a command from the view model to the mouse up event on the path.
Initially I thought it would just be a case of adding the event handler and calling the command from there, but the code behind does not have a reference to the view model because of IoC, and I cannot see any way of directly using a xaml property.
Are you looking for something like this?
<Path Data="M 80,200 A 100,50 45 1 0 100,50" >
<Path.InputBindings>
<MouseBinding Command="{Binding MyPathCommand}" MouseAction="LeftClick" />
</Path.InputBindings>
</Path>
Related
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.
I have a WPF app which uses DevExpress controls and MVVM with PRISM.
I'm using DockLayoutManager's 'DockOperationCompleted' event to invoke a command on my view model like this:
<dxd:DockLayoutManager x:Name="dockContainer">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DockOperationCompleted">
<i:InvokeCommandAction Command="{Binding DataContext.SaveLayoutCommand, ElementName=dockContainer}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<dxd:LayoutGroup/>
</dxd:DockLayoutManager>
The purpose of the 'SaveLayoutCommand' command is to save the layout so it can be restored later in time.
The 'DockOperationCompleted' event is raised after a DockItem gets docked or closed (there are other cases but they are irrelevant).
The problem is that when I close the main window, the dock items in my DockLayoutManager are getting closed one by one and thus 'SaveLayoutCommand' gets invoked for every closed dock item and I don't want this to happen.
The 'DockOperationCompletedEventArgs' with which the event gets raised has a 'DockOperation' property which I can check agains, but I'm not sure where exactly should this code fit in.
What I am trying to achieve is that the command should be invoked only in one case - when the item is docked
My question is : is there a way to 'filter' when the command gets invoked based on the event's event args?
Thanks :)
In addition to aKzenTs answer I want to point out that with DevExpress its rather easy to pass the event args to a command.
You should use EventToCommand from their MVVM library (PassEventArgsToCommand-Property). If you want to keep your viewmodel clean of DevExpress you can additionally use a Converter to transform the event args to an arbitrary object.
<dxmvvm:Interaction.Triggers>
<dxmvvm:EventToCommand Command="{Binding YOURCOMMAND}"
EventName="THEEVENT"
EventArgsConverter="{StaticResource YOUREVENTARGSCONVERTER}"
PassEventArgsToCommand="true" />
</dxmvvm:Interaction.Triggers>
There is no builtin way to filter the events that are raised before invoking an action. You can however implement your own custom trigger action that does the filtering.
Unfortunately it's also not easy to access the event args and passing them to the command. See this question as a reference:
MVVM Passing EventArgs As Command Parameter
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
FINAL NOTE
Final solution found in another post
Although I appreciated the clarification that was provided, the ultimate solution was in-fact provided by another solution as linked above. No matter WHAT I tried, the binding via the "Element Name" component was not working. I had to go based on the "Relative" hierarchy up the datagrid...
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}"
Height="16" Width="16" HorizontalAlignment="Center" >
<Image Source="MyButtonImage.png" IsHitTestVisible="True"/>
</Button>
Hope something not too complicated in WPF / MVVM environment. Here's the scenario.
I have a Window (.xaml) and a corresponding View Model (.cs). The form displays fine with all the data bindings no problem. (note: this is NOT done via any commercial "framework" )
One of the controls that is in the view window is a custom user control of a datagrid with all pre-defined columns, headings and content to be displayed when the view is shown. This works all no problem even though the control is not directly "defined" in the main window .xaml file, but just dropped on the form as the user control itself (which has its own obvious .cs code-behind).
With the main window's "DataContext" pointing to the View Model, and the user control that has a datagrid
<DataGrid AutoGenerateColumns="False"
Name="dataMyStuff"
ItemsSource="{Binding Path=MyTablePropertyOnViewModel,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}" ... />
Now, what I'm looking for. On this data grid, I have a column that has an image in the first column. When I click on this image, I want to print a report specific to the record as represented by this row (it has a PK value I use). So, how do I tell the image "KeyUp" event to go to the View Model event handler since that is where the data is, and some other methods I'll need for preparing the call to the report. The view portion of the grid is for cosmetic display to the user, and thus no "functionality" directly in this control.
-- EDIT -- per progress from answers
I've adjusted my datagrid per comments from Josh and Rachel, however, something still does not appear to be quite right... Seeing the button was using a "Command" instance, I interpreted this as it needed to attach to an instance of an "ICommand" interface object on my view model. So, I created an instance. I know the command handler works as it is also used for common things like Add, Edit, Save, Cancel, Exit, etc... So I have a new one for this printing purpose. For simplicity, I have it created as an Always Execute, so there is no method to handle the "CanExecute" portion of the control. I've set the button's "Command" to almost all iterations I could think of an still nothing, but here's an update of what I'm seeing going on.
<UserControl>
<Data grid columns / template, etc to the button>
<DataTemplate>
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand}" >
<Image Source="myPrintImage.png"/>
</Button>
</DataTemplate>
</Data grid columns, etc>
</UserControl>
In my ViewModel class (myICommandButtonHandler inherits from ICommand)
private myICommandButtonHandler myPrintCommand;
public myICommandButtonHandler MyPrintCommand
{
get { if (myPrintCommand == null)
myPrintCommand = new myICommandButtonHandler(myPrint);
return myPrintCommand;
}
}
private void myPrint()
{
MessageBox.Show( "Doing the print job..." );
}
Now, what I'm seeing. During step through initialization of all the controls and such. I click menu item to call my Window to be displayed. FIRST, it creates an instance of the View Model controller. THEN, it calls the Window and passes in the View Model controller as parameter so it is immediately set at the Window level as the "DataContext" of the window. The main window then goes into it's "InitializeComponents" call and starts to build out all the other controls embedded, including this separate class that contains the data grid in question. At the constructor of this usercontrol (that has the datagrid), there is no "data context" set yet as the rest of the controls have not been initialized yet, and I don't know why / when the "bindings" apparently get "hooked" in. So, it appears that trying to do the binding to the data grid's command button are failing. HOWEVER, at run-time, the actual data IS updated in the grid, so I know that much is working.
So, the data grid has its "ItemsSource" set to a property of a "DataView" property on the view model, but the binding of the "button" doesn't appear to see the "MyPrintCommand" handler that I thought would get the hit.. and its action is to just display a message box (for now).
Usually I use an AttachedCommand Behavior which allows me to bind Events to ViewModel Commands. For example, you could use
<Image ...
local:CommandBehavior.Event="KeyUp"
local:CommandBehavior.Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
local:CommandBehavior.CommandParameter="{Binding }"/>
I'd would recommend using a different event than KeyUp, since I don't think Images can have Keyboard focus so the KeyUp event will never get fired.
A better alternative is to use a Button and overwrite it's Template to be your Image. This will maintain the Click functionality, and give you access to Command and CommandParameter properties
<Button Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
CommandParameter="{Binding }">
<Button.Template>
<Image ... />
</Button.Template>
</Button>
Also, the CommandParameter="{Binding }" will simply pass the current DataRow's DataContext (your data object) to the command
Change the data template to be a button that has a image as its content. Use the command and commandparameter properties on the button to call your printing method. You can declare your print command in your viewmodel, and bind to it. Your parameter could be the selected row in your datagrid.
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.