I am building a composite application using CAL/Prism. The main region is a tab control, with multiple types of views in it. Each view has a custom set commands that it can handle which are bound to toolbar buttons at the top of the window. I've done this before in non-CAL apps by simply setting the InputBinding on the command, but I haven't been able to find any such mechanism in the source code for the CAL modules.
My question is, what is the best way to hook up a keystroke to my view, so that when the user presses Alt + T, the associated DelegateCommand object handles it? Hooking up a shortcut can't be THAT difficult...
Just for reference, the CommandReference class is currently not included in an assembly that you can reference, but is included with the M-V-VM project template. So if you don't build your application from the template, then you have to get the class from somewhere else. I chose to copy it from the sample project. I included it below to allow everyone easy access to this little chunk of goodness, but be sure to check for updates to the template in future versions of the M-V-VM Toolkit.
/// <summary>
/// This class facilitates associating a key binding in XAML markup to a command
/// defined in a View Model by exposing a Command dependency property.
/// The class derives from Freezable to work around a limitation in WPF when data-binding from XAML.
/// </summary>
public class CommandReference : Freezable, ICommand
{
public CommandReference( )
{
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register( "Command", typeof( ICommand ), typeof( CommandReference ), new PropertyMetadata( new PropertyChangedCallback( OnCommandChanged ) ) );
public ICommand Command
{
get { return (ICommand)GetValue( CommandProperty ); }
set { SetValue( CommandProperty, value ); }
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (Command != null)
return Command.CanExecute( parameter );
return false;
}
public void Execute(object parameter)
{
Command.Execute( parameter );
}
public event EventHandler CanExecuteChanged;
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandReference commandReference = d as CommandReference;
if (commandReference != null)
{
ICommand oldCommand = e.OldValue as ICommand;
if (oldCommand != null)
oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged;
ICommand newCommand = e.NewValue as ICommand;
if (newCommand != null)
newCommand.CanExecuteChanged += commandReference.CanExecuteChanged;
}
}
#endregion
#region Freezable
protected override Freezable CreateInstanceCore( )
{
return new CommandReference();
}
#endregion
}
Enjoy!
The MVVM Toolkit has a class called a CommandReference that will allow you to use a reference to a command as a keybinding.
<Window ...
xmlns:toolkit="clr-namespace:CannotRememberNamspace;assembly=OrTheAssembly"
>
<Window.Resources>
<toolkit:CommandReference
x:Key="ExitCommandReference"
Command="{Binding ExitCommand}" />
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="X"
Modifiers="Control"
Command="{StaticResource ExitCommandReference}" />
</Window.InputBindings>
</Window>
This'll do it.
Edit: Since this was written, WPF 4.0 fixed this particular issue and you no longer have to use the static resource workaround. You can reference the command in your viewmodel directly from the KeyBinding.
Related
I'm using the MVVM pattern so my view-model doesn't know anything about the view, and the view is displayed via DataTemplates.
When the view isn't displayed anymore, I want to take a screenshot of it (with a utility class). So I want to bind to FrameworkElement.Unloaded, and when its hit, take a screenshot of the usercontrol for use in another control to select which view to go to.
I read this article, which makes it appear as if attached properties would work (I'm using it on the UserControl object)
http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html
I get the error that a binding can only be set on a DependencyObject or DependencyProperty. I followed his instructions properly. Any idea why this isnt working or how I can bind to that in a MVVM scenario?
Is it not possible to bind to that particular event or to an event in the root xaml node?
Here's teh code (in addition to the EventBehaviorFactory in the link above)
public static class FrameworkElementBehavior
{
public static readonly DependencyProperty UnloadedCommandProperty = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(FrameworkElement.UnloadedEvent, "UnloadedCommand", typeof(FrameworkElementBehavior));
public static void SetUnloadedCommand(DependencyObject o, ICommand value)
{
o.SetValue(UnloadedCommandProperty, value);
}
public static ICommand GetUnloadedCommand(DependencyObject o)
{
return o.GetValue(UnloadedCommandProperty) as ICommand;
}
}
<UserControl x:Class="WTF.BuxComp.Prism.Modules.Win.OrderEntryPos.Views.CustomerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WTF.BuxComp.Prism.Modules.Win.OrderEntryPos.Helpers"
mc:Ignorable="d"
d:DesignHeight="510" d:DesignWidth="716"
local:FrameworkElementBehavior.UnloadedCommand="{Binding UnloadedCommand}">
and the exact error is
A 'Binding' cannot be set on the 'SetUnloadedCommand' property of type
'CustomerView'. A 'Binding' can only be set on a DependencyProperty of
a DependencyObject.
The best thing I can suggest is to map to a regular event handler then call OutOfViewCommand.Execute from within your control to your DataContext. You will also need to map UserControl.DataContextChanged on your control and save your datacontext locally.
public partial class MainWindow : Window
{
private object Data { get; set; }
public MainWindow()
{
InitializeComponent();
}
private void Window_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
this.Data = e.NewValue;
}
private void Window_Unloaded(object sender, RoutedEventArgs e)
{
if(this.Data != null)
this.Data.OutOfViewCommand.Execute(null);
}
}
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" DataContextChanged="Window_DataContextChanged" FrameworkElement.Unloaded="Window_Unloaded">
<Grid>
</Grid>
Though this does not strictly conform with MVVM, a compromise you will often face with framework calls, it still works in a re-usable way with any view model.
For this you may need to correctly name your attached proerty.... its name declared is OutOfViewCommand but it should be UnloadedCommand
public static class FrameworkElementBehavior
{
public static readonly DependencyProperty UnloadedCommandProperty =
EventBehaviourFactory.CreateCommandExecutionEventBehaviour
(FrameworkElement.UnloadedEvent,
"UnloadedCommand",
typeof(FrameworkElementBehavior));
public static void SetUnloadedCommand
(DependencyObject o, ICommand value)
{
o.SetValue(UnloadedCommandProperty, value);
}
public static ICommand GetUnloadedCommand
(DependencyObject o)
{
return o.GetValue(UnloadedCommandProperty) as ICommand;
}
}
I have created a few Custom Controls (NOT UserControls) with bind-able "ClearCommand" ICommand dependency properties. This property will do exactly what it sounds: it will clear all the values from the control (textboxes, etc). I also bind (some) of those same properties to the VM I describe below.
Now I'm stuck trying to trigger the ClearCommand in those controls in the following MVVM scenario:
I've added a few such controls into my View. The View also includes a "Save" button that binds to my ViewModel's SaveCommand DelegateCommand property.
What I need to happen is that, upon a successful save, the VM should trigger the ClearCommand on those controls found in the View.
UPDATE
I've added code examples below. I have a few controls that resemble the ExampleCustomControl. Also, just to note, I am open to restructuring some of this if it's completely off.
Example Control snippet:
public class ExampleCustomControl : Control {
public string SearchTextBox { get; set; }
public IEnumerable<CustomObject> ResultList { get; set; }
public ExampleCustomControl() {
ClearCommand = new DelegateCommand(Clear);
}
/// <summary>
/// Dependency Property for Datagrid ItemSource.
/// </summary>
public static DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem",
typeof(CustomObject), typeof(ExampleCustomControl), new PropertyMetadata(default(CustomObject)));
public CustomObject SelectedItem {
get { return (CustomObject)GetValue(SelectedCustomObjectProperty); }
set { SetValue(SelectedCustomObjectProperty, value); }
}
public static DependencyProperty ClearCommandProperty = DependencyProperty.Register("ClearCommand", typeof(ICommand),
typeof(ExampleCustomControl), new PropertyMetadata(default(ICommand)));
/// <summary>
/// Dependency Property for resetting the control
/// </summary>
[Description("The command that clears the control"), Category("Common Properties")]
public ICommand ClearCommand {
get { return (ICommand)GetValue(ClearCommandProperty); }
set { SetValue(ClearCommandProperty, value); }
}
public void Clear(object o) {
SearchTextBox = string.Empty;
SelectedItem = null;
ResultList = null;
}
}
Example View snippet:
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<control:ExampleCustomControl Grid.Row="0"
SelectedItem="{Binding Selection, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Row="1" x:Name="ResetButton" Command="{Binding SaveCommand}">
Save
</Button>
</Grid>
Example ViewModel:
public class TestViewModel : WorkspaceTask {
public TestViewModel() {
View = new TestView { Model = this };
SaveCommand = new DelegateCommand(Save);
}
private CustomObject _selection;
public CustomObject Selection {
get { return _selection; }
set {
_selection = value;
OnPropertyChanged("Selection");
}
}
public DelegateCommand SaveCommand { get; private set; }
private void Save(object o) {
// perform save
// clear controls
}
}
As others have said the VM shouldn't know about the view directly in MVVM so it doesn't make sense really that the VM triggers something on your custom control to clear everything.
I would have set the DataContext of the custom control to an object that has all the properties you want to clear, which are all each bound (two-way) to your textboxes etc. Then in the Save() method you can set a new object (which the custom control DataContext is bound to) and all the properties will be cleared for you (assuming you have implemented INotifyPropertyChanged on the object).
UPDATED:
As per my comment, see an example of the workaround for your current setup (untested btw):
public static DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem",
typeof(CustomObject), typeof(ExampleCustomControl), new PropertyMetadata(default(CustomObject), OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var cont = source as ExampleCustomControl;
//do all the clearing of txtboxes etc here....
cont.SearchTextBox = string.Empty;
}
But I would still try and move all this into the VM. i.e. have a clear command, like you do with the save command and bind the textbox text etc to a property in the VM and when the command is called it clears everything, which you can then easily call from the Save method in the VM too. But obviously I have no idea what you are trying to achieve in the long run or how selectedItem and the textboxes etc are related, so depends (as always) i guess.
It sounds like you are thinking about this the wrong way. In MVVM the ViewModel should never know anything about the custom controls (hence you are having a problem with this Clear functionality).
Your requirements are a bit vague, but have you considered:
1) If the properties are bound from the VM, can't the Control detect when these are changed?
2) If you really need to call Clear from the XAML layer and want to keep it pure MVVM, then consider something like the Expression Blend SDK's CallMethodAction.
As a followup to my comment. I suspect your command is targeting the View and clearing the TextBoxes directly. Instead, have your command target the ViewModel and clear the properties the View is bound to. Then you can have the command be a property on the ViewModel and call it whenever needed.
We use the MVVM pattern. In the View, I have binding the save command to a button:
In the view model, I would like to find out the save command binding target, does it possible?
private Button GetBindingControl(ICommand command)
{
// What should I do here:
return button;
}
It's not possible, and it defeats the purpose of MVVM (having the UI logic in the VM regardless of the controls used)
Maybe you could ask instead what problem you are trying to solve.
As #Diego said, this defats the purpose of MVVM because we must try hard not to include visuals or controls in the view models in MVVM...
Having said that there are two options...
Using RoutedCommands
Using Attached Behaviors.
RoutedCommands are not readily allowed in MVVM as they need to be closely command bound to the UI element i.e. in our case the Button. Hence they too defeat the purpose of MVVM.
But MVVM happily co-exists with the Attached Behaviors.
Many developers shy away from this immensely powerful feature. And we can use it along with RoutedCommands.
In your case
Attach to the Button, with a Action delegate.
Attach the string object as command parameter.
Inside the behavior, set the Button.Command with some Routed command.
In the executed event handler, get the button action delegate from the sender / originalsource / source as the button and then call your Action<> accordingly by using e.Parameter string value.
Sample code below...
Assume you have common button utilities of signature Action<Button, string>
public static class ButtonActionUtilities
{
public static Action<Button, string> ButtonActionDelegate
{
get
{
return ExecuteButtonClick;
}
}
public static void ExecuteButtonClick(Button btn, string param)
{
MessageBox.Show(
"You clicked button " + btn.Content + " with parameter " + param);
}
}
Then the attched behavior is as below...
public static class ButtonAttachedBehavior
{
public static readonly DependencyProperty ActionDelegateProperty
= DependencyProperty.RegisterAttached(
"ActionDelegate",
typeof(Action<Button, string>),
typeof(ButtonAttachedBehavior),
new PropertyMetadata(null, OnActionDelegatePropertyChanged));
public static Action<Button, string> GetActionDelegate(
DependencyObject depObj)
{
return (Action<Button, string>)depObj.GetValue(
ActionDelegateProperty);
}
public static void SetActionDelegate(
DependencyObject depObj, Action<Button, string> value)
{
depObj.SetValue(ActionDelegateProperty, value);
}
private static void OnActionDelegatePropertyChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs e)
{
if (depObj is Button
&& e.NewValue is Action<Button, string>)
{
((Button)depObj).Command
= new RoutedCommand(
"ActionRoutedCommand",
typeof(ButtonAttachedBehavior));
((Button) depObj).CommandBindings.Add(
new CommandBinding(
((Button) depObj).Command,
OnActionRoutedCommandExecuted));
}
}
private static void OnActionRoutedCommandExecuted(
object sender, ExecutedRoutedEventArgs e)
{
var actionDelegate = GetActionDelegate((Button)e.Source);
actionDelegate((Button) e.Source, (string)e.Parameter);
}
}
And on XAML it will look like this....
<StackPanel>
<Button x:Name="TestButton" Content="Test Me"
local:ButtonAttachedBehavior.ActionDelegate
="{x:Static local:ButtonActionUtilities.ButtonActionDelegate}"
CommandParameter
="{Binding Text, ElementName=ParameterTextBox}"/>
<TextBox x:Name="ParameterTextBox"/>
</StackPanel>
So with the code above you will need to just set the ActionDelegate attached property to approapriate delegate and it will execute that.
I would still suggest you to revamp your existing code setup to separate button specific behaviors to make it more MVVM friendly.
Suppose I have a view implemented as a DataTempate inside a resource Dictionary.
And I have a corresponding ViewModel.
Binding Commands are easy. But what if my View contains a control such as a ListBox, and I need to Publish an application wide event (Using Prism's Event Aggreagtor) based on the Item being Changed on the List.
if ListBox supports a command I could just bind it to a command in the ViewModel and publish the event. But Listbox doesn't allow such an option.
How do I bridge this?
EDIT:
Many great answers.
Take a look at this link http://blogs.microsoft.co.il/blogs/tomershamam/archive/2009/04/14/wpf-commands-everywhere.aspx
Thanks
Ariel
Instead of trying to bind a command to when the item changes, I looked at the problem another way.
If you bind the selected item of the ListBox to a property in the ViewModel, then when that property is changed you can publish the event. That way the ViewModel remains the source of the event and it is triggered by the item changing, which is what you want.
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
...
public class ViewModel
{
public IEnumerable<Item> Items { get; set; }
private Item selectedItem;
public Item SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem == value)
return;
selectedItem = value;
// Publish event when the selected item changes
}
}
Extend the control to support ICommandSource and decide which action should trigger the command.
I did this with Combo Box and used OnSelectionChanged as the trigger for the command. First I will show in XAML how I bind the command to the extended Control ComboBox which I called CommandComboBox, then I will show the code for CommandComboBox that adds the support for the ICommandSource to ComboBox.
1) Using CommandComboBox in your XAML code:
In your XAML namespace declarations include
xmlns:custom="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary">
Use the CommandComboBox in place of ComboBox and bind the command to it like so: Note that in this example I have a defined a command called SetLanguageCommand im my ViewModel and I am passing the selected value for this ComboBox as the parameter to the command.
<custom:CommandComboBox
x:Name="ux_cbSelectLanguage"
ItemsSource="{Binding Path = ImagesAndCultures}"
ItemTemplate="{DynamicResource LanguageComboBoxTemplate}"
Command="{Binding Path=SetLanguageCommand, Mode=Default}"
CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=SelectedValue, Mode=Default}"
IsSynchronizedWithCurrentItem="True"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Grid.Column="1" Margin="0,0,20,0" Style="{DynamicResource GlassyComboBox}" ScrollViewer.IsDeferredScrollingEnabled="True"
/>
2) The code for CommandComboBox
The code for the file CommandComboBox.cs is included below. I added this file to a Class Library called WpfCommandControlsLibrary and made it a separate project so I could easily add any extend commands to whatever solution needed to use them and so I could easily add additional WPF Controls and extend them to support the ICommandSource inteface.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WpfCommandControlsLibrary
{
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:CustomControl1/>
///
/// </summary>
public class CommandComboBox : ComboBox, ICommandSource
{
public CommandComboBox() : base()
{
}
#region Dependency Properties
// Make Command a dependency property so it can use databinding.
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(CommandComboBox),
new PropertyMetadata((ICommand)null,
new PropertyChangedCallback(CommandChanged)));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
// Make CommandTarget a dependency property so it can use databinding.
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register(
"CommandTarget",
typeof(IInputElement),
typeof(CommandComboBox),
new PropertyMetadata((IInputElement)null));
public IInputElement CommandTarget
{
get
{
return (IInputElement)GetValue(CommandTargetProperty);
}
set
{
SetValue(CommandTargetProperty, value);
}
}
// Make CommandParameter a dependency property so it can use databinding.
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(CommandComboBox),
new PropertyMetadata((object)null));
public object CommandParameter
{
get
{
return (object)GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
#endregion
// Command dependency property change callback.
private static void CommandChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
CommandComboBox cb = (CommandComboBox)d;
cb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
// Add a new command to the Command Property.
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
// If oldCommand is not null, then we need to remove the handlers.
if (oldCommand != null)
{
RemoveCommand(oldCommand, newCommand);
}
AddCommand(oldCommand, newCommand);
}
// Remove an old command from the Command Property.
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
// Add the command.
private void AddCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.Command != null)
{
RoutedCommand command = this.Command as RoutedCommand;
// If a RoutedCommand.
if (command != null)
{
if (command.CanExecute(CommandParameter, CommandTarget))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
// If a not RoutedCommand.
else
{
if (Command.CanExecute(CommandParameter))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
}
// If Command is defined, selecting a combo box item will invoke the command;
// Otherwise, combo box will behave normally.
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (this.Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
{
command.Execute(CommandParameter, CommandTarget);
}
else
{
((ICommand)Command).Execute(CommandParameter);
}
}
}
// Keep a copy of the handler so it doesn't get garbage collected.
private static EventHandler canExecuteChangedHandler;
}
}
One option is to extend the control in question and add support for the particular command you require. For example, I've modified ListView before to support the ItemActivated event and related command.
Well, nobody answered.
So I've gave up and moved the implementation of the View outside the Dictionary into a regular UserControl, I've injected him a reference to the ViewModel.
Now when the ListBox fire the Event it's calls the ViewModel and from there everything is possible again.
Ariel
A great solution to this type of problem comes from the usage of Attached Properties.
Marlon Grech has taken the usage of Attached Properties to the next level by creating Attached Command Behaviors. Using these it is possible to bind any Command existing in a ViewModel to any Event existing in the view.
This is something I use a lot to deal with similar issues with ListBoxes, where I want them to open, or edit or do some action on a double click.
In this example I'm using an older version of Attached Command Behaviors, but the effect is the same. I have a style that is used for ListBoxItems which I am explicitly keying to.
However, it would be easy enough to create a application or window wide style applying to all ListBoxItems that sets the commands at a much higher level. Then, whenever the event for the ListBoxItem attached to the CommandBehavior.Event property would fire, it instead fires off the attached Command.
<!-- acb is the namespace reference to the Attached Command Behaviors -->
<Style x:Key="Local_OpenListItemCommandStyle">
<Setter Property="acb:CommandBehavior.Event"
Value="MouseDoubleClick" />
<Setter Property="acb:CommandBehavior.Command"
Value="{Binding ElementName=uiMyListBorder, Path=DataContext.OpenListItemCommand}" />
<Setter Property="acb:CommandBehavior.CommandParameter"
Value="{Binding}" />
</Style>
<DataTemplate x:Key="MyView">
<Border x:Name="uiMyListBorder">
<ListBox ItemsSource="{Binding MyItems}"
ItemContainerStyle="{StaticResource local_OpenListItemCommandStyle}" />
</Border>
</DataTemplate>
I have been writing behaviors (attached properties) to do this, and there are still cases where I need them.
For the usual case however, simply binding an event to a command, you can do everything in Xaml if you have Blend SDK 4 installed. Note that you will have to add a reference to System.Windows.Interactivity.dll, and to redistribute this assembly.
Expression Blend SDK for .NET 4
Microsoft SDKs (for future reference)
This example is invoking an ICommand DragEnterCommand on the ViewModel when the DragEnter event of the Grid is fired:
<UserControl xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" >
<Grid>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DragEnter">
<i:InvokeCommandAction Command="{Binding DragEnterCommand}" CommandParameter="{Binding ...}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</UserControl>
Try using Prism 2.
It comes with great extensions to commanding and opens many new posibilites (like commands to being tied to visual tree).
I'm trying to implement a WPF application using MVVM (Model-View-ViewModel) pattern and I'd like to have the View part in a separate assembly (an EXE) from the Model and ViewModel parts (a DLL).
The twist here is to keep the Model/ViewModel assembly clear of any WPF dependency. The reason for this is I'd like to reuse it from executables with different (non-WPF) UI techs, for example WinForms or GTK# under Mono.
By default, this can't be done, because ViewModel exposes one or more ICommands. But the ICommand type is defined in the System.Windows.Input namespace, which belongs to the WPF!
So, is there a way to satisfy the WPF binding mechanism without using ICommand?
Thanks!
You should be able to define a single WPF custom routed command in your wpf layer and a single command handler class. All your WPF classes can bind to this one command with appropriate parameters.
The handler class can then translate the command to your own custom command interface that you define yourself in your ViewModel layer and is independent of WPF.
The simplest example would be a wrapper to a void delegate with an Execute method.
All you different GUI layers simply need to translate from their native command types to your custom command types in one location.
WinForms doesn't have the rich data binding and commands infrastructure needed to use a MVVM style view model.
Just like you can't reuse a web application MVC controllers in a client application (at least not without creating mountains of wrappers and adapters that in the end just make it harder to write and debug code without providing any value to the customer) you can't reuse a WPF MVVM in a WinForms application.
I haven't used GTK# on a real project so I have no idea what it can or can't do but I suspect MVVM isn't the optimal approach for GTK# anyway.
Try to move as much of the behavior of the application into the model, have a view model that only exposes data from the model and calls into the model based on commands with no logic in the view model.
Then for WinForms just remove the view model and call the model from the UI directly, or create another intermediate layer that is based on WinForms more limited data binding support.
Repeat for GTK# or write MVC controllers and views to give the model a web front-end.
Don't try to force one technology into a usage pattern that is optimized for another, don't write your own commands infrastructure from scratch (I've done it before, not my most productive choice), use the best tools for each technology.
Sorry Dave but I didn't like your solution very much. Firstly you have to code the plumbing for each command manually in code, then you have to configure the CommandRouter to know about each view/viewmodel association in the application.
I took a different approach.
I have an Mvvm utility assembly (which has no WPF dependencies) and which I use in my viewmodel. In that assembly I declare a custom ICommand interface, and a DelegateCommand class that implements that interface.
namespace CommonUtil.Mvvm
{
using System;
public interface ICommand
{
void Execute(object parameter);
bool CanExecute(object parameter);
event EventHandler CanExecuteChanged;
}
public class DelegateCommand : ICommand
{
public DelegateCommand(Action<object> execute) : this(execute, null)
{
}
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public void Execute(object parameter)
{
_execute(parameter);
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged;
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
}
}
I also have a Wpf library assembly (which does reference the System WPF libraries), which I reference from my WPF UI project. In that assembly I declare a CommandWrapper class which has the standard System.Windows.Input.ICommand interface. CommandWrapper is constructed using an instance of my custom ICommand and simply delegates Execute, CanExecute and CanExecuteChanged directly to my custom ICommand type.
namespace WpfUtil
{
using System;
using System.Windows.Input;
public class CommandWrapper : ICommand
{
// Public.
public CommandWrapper(CommonUtil.Mvvm.ICommand source)
{
_source = source;
_source.CanExecuteChanged += OnSource_CanExecuteChanged;
CommandManager.RequerySuggested += OnCommandManager_RequerySuggested;
}
public void Execute(object parameter)
{
_source.Execute(parameter);
}
public bool CanExecute(object parameter)
{
return _source.CanExecute(parameter);
}
public event System.EventHandler CanExecuteChanged = delegate { };
// Implementation.
private void OnSource_CanExecuteChanged(object sender, EventArgs args)
{
CanExecuteChanged(sender, args);
}
private void OnCommandManager_RequerySuggested(object sender, EventArgs args)
{
CanExecuteChanged(sender, args);
}
private readonly CommonUtil.Mvvm.ICommand _source;
}
}
In my Wpf assembly I also create a ValueConverter that when passed an instance of my custom ICommand spits out an instance of the Windows.Input.ICommand compatible CommandWrapper.
namespace WpfUtil
{
using System;
using System.Globalization;
using System.Windows.Data;
public class CommandConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new CommandWrapper((CommonUtil.Mvvm.ICommand)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
}
Now my viewmodels can expose commands as instances of my custom command type without having to have any dependency on WPF, and my UI can bind Windows.Input.ICommand commands to those viewmodels using my ValueConverter like so. (XAML namespace spam ommited).
<Window x:Class="Project1.MainWindow">
<Window.Resources>
<wpf:CommandConverter x:Key="_commandConv"/>
</Window.Resources>
<Grid>
<Button Content="Button1" Command="{Binding CustomCommandOnViewModel,
Converter={StaticResource _commandConv}}"/>
</Grid>
</Window>
Now if I'm really lazy (which I am) and can't be bothered to have to manually apply the CommandConverter every time then in my Wpf assembly I can create my own Binding subclass like this:
namespace WpfUtil
{
using System.Windows.Data;
public class CommandBindingExtension : Binding
{
public CommandBindingExtension(string path) : base(path)
{
Converter = new CommandConverter();
}
}
}
So now I can bind to my custom command type even more simply like so:
<Window x:Class="Project1.MainWindow"
xmlns:wpf="clr-namespace:WpfUtil;assembly=WpfUtil">
<Window.Resources>
<wpf:CommandConverter x:Key="_commandConv"/>
</Window.Resources>
<Grid>
<Button Content="Button1" Command="{wpf:CommandBinding CustomCommandOnViewModel}"/>
</Grid>
</Window>
I needed an example of this so I wrote one using various techniques.
I had a few design goals in mind
1 - keep it simple
2 - absolutely no code-behind in the view (Window class)
3 - demonstrate a dependency of only the System reference in the ViewModel class library.
4 - keep the business logic in the ViewModel and route directly to the appropriate methods without writing a bunch of "stub" methods.
Here's the code...
App.xaml (no StartupUri is the only thing worth noting)
<Application
x:Class="WpfApplicationCleanSeparation.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>
App.xaml.cs (load up the main view)
using System.Windows;
using WpfApplicationCleanSeparation.ViewModels;
namespace WpfApplicationCleanSeparation
{
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
var view = new MainView();
var viewModel = new MainViewModel();
view.InitializeComponent();
view.DataContext = viewModel;
CommandRouter.WireMainView(view, viewModel);
view.Show();
}
}
}
CommandRouter.cs (the magic)
using System.Windows.Input;
using WpfApplicationCleanSeparation.ViewModels;
namespace WpfApplicationCleanSeparation
{
public static class CommandRouter
{
static CommandRouter()
{
IncrementCounter = new RoutedCommand();
DecrementCounter = new RoutedCommand();
}
public static RoutedCommand IncrementCounter { get; private set; }
public static RoutedCommand DecrementCounter { get; private set; }
public static void WireMainView(MainView view, MainViewModel viewModel)
{
if (view == null || viewModel == null) return;
view.CommandBindings.Add(
new CommandBinding(
IncrementCounter,
(λ1, λ2) => viewModel.IncrementCounter(),
(λ1, λ2) =>
{
λ2.CanExecute = true;
λ2.Handled = true;
}));
view.CommandBindings.Add(
new CommandBinding(
DecrementCounter,
(λ1, λ2) => viewModel.DecrementCounter(),
(λ1, λ2) =>
{
λ2.CanExecute = true;
λ2.Handled = true;
}));
}
}
}
MainView.xaml (there is NO code-behind, literally deleted!)
<Window
x:Class="WpfApplicationCleanSeparation.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplicationCleanSeparation="clr-namespace:WpfApplicationCleanSeparation"
Title="MainWindow"
Height="100"
Width="100">
<StackPanel>
<TextBlock Text="{Binding Counter}"></TextBlock>
<Button Content="Decrement" Command="WpfApplicationCleanSeparation:CommandRouter.DecrementCounter"></Button>
<Button Content="Increment" Command="WpfApplicationCleanSeparation:CommandRouter.IncrementCounter"></Button>
</StackPanel>
</Window>
MainViewModel.cs (includes the actual Model as well since this example is so simplified, please excuse the derailing of the MVVM pattern.
using System.ComponentModel;
namespace WpfApplicationCleanSeparation.ViewModels
{
public class CounterModel
{
public int Data { get; private set; }
public void IncrementCounter()
{
Data++;
}
public void DecrementCounter()
{
Data--;
}
}
public class MainViewModel : INotifyPropertyChanged
{
private CounterModel Model { get; set; }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public MainViewModel()
{
Model = new CounterModel();
}
public int Counter
{
get { return Model.Data; }
}
public void IncrementCounter()
{
Model.IncrementCounter();
PropertyChanged(this, new PropertyChangedEventArgs("Counter"));
}
public void DecrementCounter()
{
Model.DecrementCounter();
PropertyChanged(this, new PropertyChangedEventArgs("Counter"));
}
}
}
Just a quick and dirty and I hope it's useful to someone. I saw a few different approaches through various Google's but nothing was quite as simple and easy to implement with the least amount of code possible that I wanted. If there's a way to simplify even further please let me know, thanks.
Happy Coding :)
EDIT: To simplify my own code, you might find this useful for making the Adds into one-liners.
private static void Wire(this UIElement element, RoutedCommand command, Action action)
{
element.CommandBindings.Add(new CommandBinding(command, (sender, e) => action(), (sender, e) => { e.CanExecute = true; }));
}
Instead of the VM exposing commands, just expose methods. Then use attached behaviors to bind events to the methods, or if you need a command, use an ICommand that can delegate to these methods and create the command through attached behaviors.
Off course this is possible. You can create just another level of abstraction.
Add you own IMyCommand interface similar or same as ICommand and use that.
Take a look at my current MVVM solution that solves most of the issues you mentioned yet its completely abstracted from platform specific things and can be reused. Also i used no code-behind only binding with DelegateCommands that implement ICommand. Dialog is basically a View - a separate control that has its own ViewModel and it is shown from the ViewModel of the main screen but triggered from the UI via DelagateCommand binding.
See full Silverlight 4 solution here Modal dialogs with MVVM and Silverlight 4
I think you are separating your Project at wrong point. I think you should share your model and business logic classes only.
VM is an adaptation of model to suit WPF Views. I would keep VM simple and do just that.
I can't imagine forcing MVVM upon Winforms. OTOH having just model & bussiness logic, you can inject those directly into a Form if needed.
" you can't reuse a WPF MVVM in a WinForms application"
For this please see url http://waf.codeplex.com/ , i have used MVVM in Win Form, now whenver i would like to upgrade application's presentation from Win Form to WPF, it will be changed with no change in application logic,
But i have one issue with reusing ViewModel in Asp.net MVC, so i can make same Desktop win application in Web without or less change in Application logic..
Thanks...