How should I move a WPF Window using MVVM? - wpf

This is probably overkill on the MVVM pattern but it's new to me and I'm interested to see if it is possible.
If I attach to the MouseMove event for a Window and do DragMove, I can move a bordeless window. Can I achieve this by some other method in MVVM or should I just accept adding this code to the Window's codebehind?

This is pure UI logic and doesn't belong in a ViewModel. The only reason you wouldn't want to put this in your code-behind would be for re-use and that is better solved with a custom Window derived control.

Personally I think any solution using MVVM would not make this code any better. Also, this is typically something that's view related and hasn't got anything to do with the data you're displaying.

IMHO, unless this is something that effects your data (aka the Model) then it is View code and should be in the View's code-behind and not in the Model.

I'm going to actually answer your question. The answer is yes. I'm using Cinch to assist me in the event binding and view model creation. The solution uses DragMove, but not as part of the code-behind (which is what I believe you are asking).
Event binding in the XAML:
<Window
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cinchV2="clr-namespace:Cinch;assembly=Cinch.WPF"
...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<cinchV2:EventToCommandTrigger Command="{Binding MouseLeftButtonDown}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
...
</Grid>
</Window>
In the ViewModel:
[ExportViewModel("MainViewModel")]
[PartCreationPolicy(CreationPolicy.NonShared)]
internal sealed class MainViewModel : ViewModelBase
{
public SimpleCommand<object, EventToCommandArgs> MouseLeftButtonDown { get; private set; }
[ImportingConstructor]
public MainViewModel(IUIVisualizerService uiVisualizerService)
{
...
MouseLeftButtonDown = new SimpleCommand<object, EventToCommandArgs>(OnMouseLeftButtonDown);
}
private static void OnMouseLeftButtonDown(EventToCommandArgs e)
{
((Window)e.Sender).DragMove();
}
}
Fairly simple, right? Any events that come from the UI contain the View as the sender. So, here, we simply call the method on the view within the event handler in the ViewModel.
The project I'm working on uses no code-behind (even if it is not recommended in MVVM).

I know that I am a little late to the question, but this is what I have been using for sometime now and it works like a charm.
DashboardViewModel viewModel;
public DashboardView()
{
InitializeComponent();
viewModel = new DashboardViewModel();
viewModel.RequestClose += (s, e) => Application.Current.Dispatcher.Invoke(this.Close);
viewModel.RequestMinimize += (s, e) => Application.Current.Dispatcher.Invoke(() => { this.WindowState = WindowState.Minimized; });
DataContext = viewModel;
}
and something like this in your viewModel
#region Public Event Handlers
public event EventHandler<EventArgs> RequestClose;
public event EventHandler<EventArgs> RequestMinimize;
#endregion
Using the ICommand interface...
#region ICommand Members
public ICommand CloseCommand { get; private set; }
public ICommand MinimizeCommand { get; private set; }
#endregion
Configure the commands...
private void SetupCommands()
{
CloseCommand = new RelayCommand(CloseApplication);
MinimizeCommand = new RelayCommand(MinimizeApplication);
}
Here is the RelayCommand class.
public class RelayCommand : ICommand
{
#region Private Readonly Properties
private readonly Action<object> executeCommand;
private readonly Predicate<object> canExecute;
#endregion
#region Constructors
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
this.executeCommand = execute;
this.canExecute = canExecute;
}
#endregion
#region Public ICommand Members
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
executeCommand(parameter);
}
#endregion
}
And some example methods...
private void MinimizeApplication(object obj)
{
RequestMinimize(this, new EventArgs());
}
private void CloseApplication(object obj)
{
RequestClose(this, new EventArgs());
}
Hope this helps!

I know it's an old question. However I prepared another simple implementation. Use following behavior to make window moveable:
public class WindowMoveBehavior : Behavior<Grid>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
}
protected override void OnDetaching()
{
AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
base.OnDetaching();
}
private void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
Window.GetWindow(AssociatedObject).DragMove();
}
}
Xaml example:
<Style x:Key="CustomWindowStyle" TargetType="{x:Type Window}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<i:Interaction.Behaviors>
<behaviors:WindowMoveBehavior/>
</i:Interaction.Behaviors>
<!-- different controls and content -->
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

11 years passed, but maybe someone is still interested in case to drag window using MVVM. This tricky solution is based on window's property "Tag" - almost no one use it but it's time to find out it's strength :) So all you need is System.Windows.Interactivity nuget, no Cinch or events!
Xaml:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<Window ... Tag="{Binding WindowTag}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding DragMoveWindowCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Let's find out your current window and move it. In ViewModel:
private object _windowTag;
public object WindowTag
{
get
{
return _windowTag;
}
set
{
_windowTag = value;
OnPropertyChanged("WindowTag");
}
}
private RelayCommand _dragMoveWindowCommand;
public RelayCommand DragMoveWindowCommand
{
get
{
return _dragMoveWindowCommand ??
(_dragMoveWindowCommand = new RelayCommand(obj =>
{
try
{
var window = WindowService.GetCurrentWindowByTag(WindowTag = 1);
window.DragMove();
}
catch (Exception ex)
{
}
}));
}
}
public static Window GetCurrentWindowByTag(object tag)
{
return (from Window w in App.Current.Windows
where w.Tag == tag
select w)?.FirstOrDefault();
}
Enjoy!)

Related

WPF how to transfer data between windows (MVVM)?

I know there are a lot of similar questions and I spent two hours by now trying to implementing them but can't proceed. So the problem seems simple. When I don't have a viewmodel, I can set the datacontext to a class and it is very easy to transfer data with that class. But when there is viewmodel, I have to set the datacontext to that and can't find a way to return any value after that. I tried to implement countless solutions to the problem but it seems that they are above my skill level. Thank you so much for your help!
The important parts of my code (its a simple game which i want to save, where save is named by userinput) The first window, where I want to get data from the second window
case Key.Escape: {
Thread t = new Thread(() => {
SaveGameWindow pw = new SaveGameWindow(); //the second window
if ((pw.ShowDialog() == true) && (pw.Name != string.Empty)) //pw.Name always empty
{
ILogicSaveGame l = new LogicSaveGame();
l.Write(pw.Name, "saved_games.txt");
MessageBox.Show("game saved");
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
XAML (from now on everything belongs to the SaveGameWindow):
<Window.Resources>
<local:SaveGameViewModel x:Key="my_viewmodel"/>
</Window.Resources>
<Grid DataContext="{StaticResource my_viewmodel}">
<TextBox Text="{Binding Name}"/> //i want to acces this in the first window
<Button Command="{Binding CloseCommand}"
Content="Save"/>
Code behind:
private readonly SaveGameViewModel vm;
public SaveGameWindow()
{
this.InitializeComponent();
this.vm = this.FindResource("my_viewmodel") as SaveGameViewModel;
if (this.vm.CloseAction == null)
{
this.vm.CloseAction = new Action(() => this.Close());
}
}
Viewmodel
public class SaveGameViewModel : ViewModelBase
{
public SaveGameViewModel()
{
this.CloseCommand = new RelayCommand(() => this.Close());
}
public string Name { get; set; }
public ICommand CloseCommand { get; private set; }
public Action CloseAction { get; set; }
private void Close()
{
this.CloseAction();
}
}
I use galasoft mvvmlightlibs
There are many solutions to this problem. The simplest solution is to use a shared view model for both windows and data binding. Since both windows would share the same DataContext, both have access to the same data or model instance by simply referencing their DataContext property.
But if you prefer to have individual view models, you would choose a different solution.
Solution 1
If you want to use a dedicated view model for each window, you can always use composition and make e.g. an instance SaveGameViewModel a member of MainWindowViewModel. Any class that has access to MainWindowViewModel will also have access to the SaveGameViewModel and its API, either directly or via delegating properties.
This example uses direct access by exposing SaveGameViewModel as a public property of MainWindowViewModel:
SaveGameViewModel.cs
public class SaveGameViewModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : INotifyPropertyChanged
{
public SaveGameViewModel SaveGameViewModel { get; set; }
// Allow to create an instance using XAML
public MainWindowViewModel() {}
// Allow to create an instance using C#
public MainWindowViewModel(SaveGameViewModel saveGameViewModel)
=> this.SaveGameViewModel = saveGameViewModel;
}
App.xaml
<Application>
<Application.Resources>
<MainWindowViewModel x:Key="MainWindowViewModel">
<MainWindowViewModel.SaveGameViewModel>
<SaveGameViewModel />
</MainWindowViewModel.SaveGameViewModel>
</MainWindowViewModel>
</Application.Resources>
</Application>
SaveGameWindow.xaml
<Window DataContext="{Binding Source={StaticResource MainWindowViewModel}, Path=SaveGameViewModel}">
<TextBox Text="{Binding Name}" />
<Window>
MainWindow.xaml
<Window DataContext="{StaticResource MainWindowViewModel}">
<Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private void OnKeyPressed(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
var mainWindowViewModel = this.DataContext as MainWindowViewModel;
string saveGameName = mainWindowViewModel.SaveGameViewModel.Name;
}
}
}
Solution 2
Since you are just showing a dialog, you can store the current instance of the SaveGameViewModel or its values of interest after the dialog has been closed:
MainWindow.xaml.cs
partial class MainWindow : Window
{
private SaveGameViewModel CurrentSaveGameViewModel { get; set; }
private bool IsSaveGameValid { get; set; }
private void ShowDialog_OnSaveButtonClick(object sender, RoutedEventArgs e)
{
var saveGameDialog = new SaveGameWindow();
this.IsSaveGameValid = saveGameDialog.ShowDialog ?? false;
this.CurrentSaveGameViewModel = saveGameDialog.DataContext as SaveGameViewModel;
}
private void OnKeyPressed(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape && this.IsSaveGameValid)
{
string saveGameName = this.CurrentSaveGameViewModel.Name;
}
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<MainWindowViewModel />
</Window.DataContext>
<Window>
SaveGameWindow.xaml
<Window>
<Window.DataContext>
<SaveGameViewModel />
</Window.DataContext>
<TextBox Text="{Binding Name}" />
<Window>

WPF MVVM cancel Window.Closing event

In WPF application together with MVVMLight Toolkit, I would like to see your opinion, what is the best way to implement if I need to Cancel the Window Close event.
In Window.Closing event I can set the e.Cancel = true, which prevents closing the form. To identify if the Close is allowed, or should be prevented is in the ViewModel context.
One solution could be if I define an Application variable, and I can query this in the normal event handler in view code behind?
thanks
With MVVM Light you got EventToCommand:
So you could in xaml wire up the closing event to the VM.
<Window ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding ClosingCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
and in the VM:
public RelayCommand<CancelEventArgs> ClosingCommand { get; private set; }
ctor() {
ClosingCommand = new RelayCommand<CancelEventArgs>(args => args.Cancel = true);
}
If you do not want to pass CancelEventArgs to the VM:
You could always take the similar approach with a Behavior and just use a simple bool from the VM(bind this bool to the Behavior) to indicate the closing event should be cancelled.
Update:
Download Link for following example
To do this with a Behavior you could just have a Behavior such as:
internal class CancelCloseWindowBehavior : Behavior<Window> {
public static readonly DependencyProperty CancelCloseProperty =
DependencyProperty.Register("CancelClose", typeof(bool),
typeof(CancelCloseWindowBehavior), new FrameworkPropertyMetadata(false));
public bool CancelClose {
get { return (bool) GetValue(CancelCloseProperty); }
set { SetValue(CancelCloseProperty, value); }
}
protected override void OnAttached() {
AssociatedObject.Closing += (sender, args) => args.Cancel = CancelClose;
}
}
Now in xaml:
<i:Interaction.Behaviors>
<local:CancelCloseWindowBehavior CancelClose="{Binding CancelClose}" />
</i:Interaction.Behaviors>
Where CancelClose is a bool property from the VM which indicates if the Closing event should be cancelled or not. In the attached example I have a Button to toggle this bool from the VM that should let you test the Behavior
You could to control this using Messages, for instance:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<CloseApplicationMessage>(this, m => Close());
Loaded += MainWindowLoaded;
Closing += MainWindowClosing;
}
private void MainWindowClosing(object sender, CancelEventArgs e)
{
//Ask for saving
var closingMessage = new ClosingApplicationMessage();
Messenger.Default.Send(closingMessage);
if (closingMessage.Cancel)
e.Cancel = true;
}
...
The mvvm message:
public class ClosingApplicationMessage
{
public bool Cancel { get; set; }
}
In this way, in any place you are listening to the ClosingApplicationMessage, you can control when the application is going to close, and may to cancel it.
Hope this helps...

Problems with custom WPF Commands using MVVM

I have tried two different methods of commanding in WPF from the ViewModel and come up short. The top method works great if I put it into the View however I was told this is bad practice. The second method I was told is the proper way to do custom commanding in MVVM however I get stuck on how to actually call/bind the command from the View.
View Model:
class MainViewModel : INotifyPropertyChanged
{
#region INPC
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public readonly static RoutedUICommand myCommand;
static MainViewModel()
{
myCommand = new RoutedUICommand("customCommand","My Command",typeof(MainViewModel));
}
private void ExecutemyCommand(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("textBox1 cleared");
}
private void myCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
On my View the code I have this which gives me an error
<Window x:Class="ConfigManager2.View.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:con="clr-namespace:ConfigManager2.Converters"
xmlns:vm="clr-namespace:ConfigManager2.ViewModel"
xmlns:local="clr-namespace:ConfigManager2.View"
.
.
.
<Window.CommandBindings>
<CommandBinding
Command="{x:Static vm:MainViewModel.myCommand}"
CanExecute="myCommandCanExecute"
Executed="ExecutemyCommand" />
</Window.CommandBindings>
.
.
.
<Button Content="COMMAND ME" Height="50px" Command="{x:Static vm:MainViewModel.myCommand}" />
The Error I am getting is 'ConfigManager2.View.MainView' does not contain a definition for 'ExecutemyCommand' and no extension method 'ExecutemyCommand' accepting a first argument of type 'ConfigManager2.View.MainView' could be found (are you missing a using directive or an assembly reference?)
I tried another method using ICommand and got stumped on how to bind this to the above button from the XAML
ViewModel:
public ICommand ClearCommand { get; private set; }
public MainViewModel()
{
ClearCommand= new ClearCommand(this);
}
class ClearCommand : ICommand
{
private MainViewModel viewModel;
public ClearCommand(MainViewModel viewModel)
{
this.viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
viewModel.vmTextBox1 = String.Empty;
MessageBox.Show("Textbox1 Cleared");
}
}
With the ICommand version (which I would prefer) you can bind directly to the command:
<Button Command="{Binding ClearCommand}"/>
There is no Window.CommandBinding necessary. This works, if an instance of the MainViewModel is set as the window's or button's DataContext.

When does the ui detach from commands?

I'm really scratching my head with this one. I have a mainwindow which opens a dialog. After the dialog closes, the CanExecute method on commands bound in the dialog are still executing. This is causing some serious problems in my application.
Example:
MainWindow has a button with a click handler. This is the click event handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
DialogWindow window = new DialogWindow();
window.ShowDialog();
}
In the dialog I bind an items control to a static resource in the dialog window, and each item in the list has a command:
<Window.Resources>
<Collections:ArrayList x:Key="itemsSource">
<local:ItemViewModel Description="A"></local:ItemViewModel>
<local:ItemViewModel Description="B"></local:ItemViewModel>
<local:ItemViewModel Description="C"></local:ItemViewModel>
</Collections:ArrayList>
<DataTemplate DataType="{x:Type local:ItemViewModel}">
<Button Grid.Column="1" Command="{Binding Path=CommandClickMe}" Content="{Binding Path=Description}" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
</Button>
</DataTemplate>
</Window.Resources>
<Grid>
<ToolBar ItemsSource="{StaticResource itemsSource}"></ToolBar>
</Grid>
This is the viewmodel:
public class ItemViewModel
{
private RelayWpfCommand<object> _commandClickMe;
public RelayWpfCommand<object> CommandClickMe
{
get
{
if (_commandClickMe == null)
_commandClickMe = new RelayWpfCommand<object>(obj => System.Console.Out.WriteLine("Hei mom"), obj => CanClickMe());
return _commandClickMe;
}
}
private bool CanClickMe()
{
return true;
}
public string Description { get; set; }
And this is the DelegateCommand implementation:
public class RelayWpfCommand<T> : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayWpfCommand(Action<T> execute, Predicate<T> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
/// <summary>
/// Forces a notification that the CanExecute state has changed
/// </summary>
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
public bool CanExecute(T parameter)
{
return _canExecute(parameter);
}
public void Execute(T parameter)
{
_execute(parameter);
}
bool ICommand.CanExecute(object parameter)
{
if (!IsParameterValidType(parameter))
return false;
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
if (!IsParameterValidType(parameter))
throw new ArgumentException(string.Format("Parameter must be of type {0}", typeof(T)));
Execute((T)parameter);
}
private static bool IsParameterValidType(object parameter)
{
if (parameter != null && !typeof(T).IsAssignableFrom(parameter.GetType()))
return false;
return true;
}
}
Now, If I close the dialog window and set a breakpoint in the CanExecute (I'm using Prism DelegateCommand with weak event subscription) method on the viewmodel, I notice that it triggers although the dialog has been closed. Why on earth is the binding between the button in the dialog and the command on the ViewModel still alive?
And I am checking if its being executed by closing the window and at a later time setting a breakpoint in the "CanClickMe" method in the viewmodel. It will get executed for a while, then suddenly stop (probably due to GC). This non-determenistic behaviour is causing problems because in the real application the viewmodel might already bee disposed.
You may use the WeakEvent Pattern to mitigate this problem. Please refer to the following Stackoverflow question: Is Josh Smith's implementation of the RelayCommand flawed?
I've seen this catch many times in different projects, I'm not sure whether this creepy bug lurks in your app too, but it's worth checking.
There is a known memory leak issue in WPF 3.5 (including SP1), basically you can encounter it if you are binding to something that isn’t a DependencyProperty or doesn’t implement INotifyPropertyChanged. And this is exactly what your code is about.
Just implement INotifyPropertyChanged on ItemViewModel and see how it goes. Hope this helps.
You could clear the CommandBindings Collection of your window, when it closes.
rather than having your command as a property, could you try the following:
public ICommand CommandClickMe
{
get
{
return new RelayWpfCommand<object>((obj)=>System.Console.Out.WriteLine("Hei mom"), obj => CanClickMe());
}
}

WPF ViewModel Commands CanExecute issue

I'm having some difficulty with Context Menu commands on my View Model.
I'm implementing the ICommand interface for each command within the View Model, then creating a ContextMenu within the resources of the View (MainWindow), and using a CommandReference from the MVVMToolkit to access the current DataContext (ViewModel) Commands.
When I debug the application, it appears that the CanExecute method on the command is not being called except at the creation of the window, therefore my Context MenuItems are not being enabled or disabled as I would have expected.
I've cooked up a simple sample (attached here) which is indicative of my actual application and summarised below. Any help would be greatly appreciated!
This is the ViewModel
namespace WpfCommandTest
{
public class MainWindowViewModel
{
private List<string> data = new List<string>{ "One", "Two", "Three" };
// This is to simplify this example - normally we would link to
// Domain Model properties
public List<string> TestData
{
get { return data; }
set { data = value; }
}
// Bound Property for listview
public string SelectedItem { get; set; }
// Command to execute
public ICommand DisplayValue { get; private set; }
public MainWindowViewModel()
{
DisplayValue = new DisplayValueCommand(this);
}
}
}
The DisplayValueCommand is such:
public class DisplayValueCommand : ICommand
{
private MainWindowViewModel viewModel;
public DisplayValueCommand(MainWindowViewModel viewModel)
{
this.viewModel = viewModel;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (viewModel.SelectedItem != null)
{
return viewModel.SelectedItem.Length == 3;
}
else return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show(viewModel.SelectedItem);
}
#endregion
}
And finally, the view is defined in Xaml:
<Window x:Class="WpfCommandTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCommandTest"
xmlns:mvvmtk="clr-namespace:MVVMToolkit"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<mvvmtk:CommandReference x:Key="showMessageCommandReference" Command="{Binding DisplayValue}" />
<ContextMenu x:Key="listContextMenu">
<MenuItem Header="Show MessageBox" Command="{StaticResource showMessageCommandReference}"/>
</ContextMenu>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding TestData}" ContextMenu="{StaticResource listContextMenu}"
SelectedItem="{Binding SelectedItem}" />
</Grid>
</Window>
To complete Will's answer, here's a "standard" implementation of the CanExecuteChanged event :
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
(from Josh Smith's RelayCommand class)
By the way, you should probably consider using RelayCommand or DelegateCommand : you'll quickly get tired of creating new command classes for each and every command of you ViewModels...
You have to keep track of when the status of CanExecute has changed and fire the ICommand.CanExecuteChanged event.
Also, you might find that it doesn't always work, and in these cases a call to CommandManager.InvalidateRequerySuggested() is required to kick the command manager in the ass.
If you find that this takes too long, check out the answer to this question.
Thank you for the speedy replies. This approach does work if you are binding the commands to a standard Button in the Window (which has access to the View Model via its DataContext), for example; CanExecute is shown to be called quite frequently when using the CommandManager as you suggest on ICommand implementing classes or by using RelayCommand and DelegateCommand.
However, binding the same commands via a CommandReference in the ContextMenu
do not act in the same way.
In order for the same behaviour, I must also include the EventHandler from Josh Smith's RelayCommand, within CommandReference, but in doing so I must comment out some code from within the OnCommandChanged Method. I'm not entirely sure why it is there, perhaps it is preventing event memory leaks (at a guess!)?
public class CommandReference : Freezable, ICommand
{
public CommandReference()
{
// Blank
}
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
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandReference commandReference = d as CommandReference;
ICommand oldCommand = e.OldValue as ICommand;
ICommand newCommand = e.NewValue as ICommand;
//if (oldCommand != null)
//{
// oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged;
//}
//if (newCommand != null)
//{
// newCommand.CanExecuteChanged += commandReference.CanExecuteChanged;
//}
}
#endregion
#region Freezable
protected override Freezable CreateInstanceCore()
{
throw new NotImplementedException();
}
#endregion
}
However, binding the same commands via a CommandReference in the
ContextMenu do not act in the same way.
That's a bug in CommandReference implementation. It follows from these two points:
It is recommended that the implementers of ICommand.CanExecuteChanged hold only weak references to the handlers (see this answer).
Consumers of ICommand.CanExecuteChanged should expect (1) and hence should hold strong references to the handlers they register with ICommand.CanExecuteChanged
The common implementations of RelayCommand and DelegateCommand abide by (1). The CommandReference implementation doesn't abide by (2) when it subscribes to newCommand.CanExecuteChanged. So the handler object is collected and after that CommandReference no longer gets any notifications that it was counting on.
The fix is to hold a strong ref to the handler in CommandReference:
private EventHandler _commandCanExecuteChangedHandler;
public event EventHandler CanExecuteChanged;
...
if (oldCommand != null)
{
oldCommand.CanExecuteChanged -= commandReference._commandCanExecuteChangedHandler;
}
if (newCommand != null)
{
commandReference._commandCanExecuteChangedHandler = commandReference.Command_CanExecuteChanged;
newCommand.CanExecuteChanged += commandReference._commandCanExecuteChangedHandler;
}
...
private void Command_CanExecuteChanged(object sender, EventArgs e)
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, e);
}
In order for the same behaviour, I must also include the EventHandler
from Josh Smith's RelayCommand, within CommandReference, but in doing
so I must comment out some code from within the OnCommandChanged
Method. I'm not entirely sure why it is there, perhaps it is
preventing event memory leaks (at a guess!)?
Note that your approach of forwarding subscription to CommandManager.RequerySuggested also eliminates the bug (there's no more unreferenced handler to begin with), but it handicaps the CommandReference functionality. The command with which CommandReference is associated is free to raise CanExecuteChanged directly (instead of relying on CommandManager to issue a requery request), but this event would be swallowed and never reach the command source bound to the CommandReference. This should also answer your question as to why CommandReference is implemented by subscribing to newCommand.CanExecuteChanged.
UPDATE: submitted an issue on CodePlex
An easier solution for me, was to set the CommandTarget on the MenuItem.
<MenuItem Header="Cut" Command="Cut" CommandTarget="
{Binding Path=PlacementTarget,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"/>
More info: http://www.wpftutorial.net/RoutedCommandsInContextMenu.html

Resources