Recently I got chance to play a little bit with Windows 8 Release Preview (Build 8400 to be specific). My aim was to investigate bugs which appeared in our product (WPF application) only under Windows 8. The bugs looked very simple – few buttons were disabled when they shouldn’t. It looked easy to fix but I decided to find the root cause.
It turned out that when a control bound to a command receives CanExecuteChanged notification it doesn’t requery command’s CanExecute method if sender is not the same command. This is a bit of a problem in a situation when command performs some operation on a model and it’s ability to execute depends on model’s state. For example, imagine you have a model:
class MyModel
{
public void ChangeModel(bool makeValidForCommand)
{
Valid = makeValidForCommand;
if (ModelChanged != null)
ModelChanged(this, new EventArgs());
}
public bool Valid { get; private set; }
public event EventHandler ModelChanged;
}
And a command:
class MyCommand : ICommand
{
public MyCommand(MyModel model)
{
_model = model;
}
public bool CanExecute(object parameter)
{
return _model.Valid;
}
public event EventHandler CanExecuteChanged
{
add { _model.ModelChanged += value; }
remove { _model.ModelChanged -= value; }
}
public void Execute(object parameter) { }
private MyModel _model;
}
Unfortunately, this won’t work on Windows 8 – the button bound to the command will stay improperly disabled (or enabled) after the model changed state. It works perfectly well on Windows 7 though!
The command can be rewritten like this:
class MyCommand : ICommand
{
public MyCommand(MyModel model)
{
_model = model;
}
public bool CanExecute(object parameter)
{
return _model.Valid;
}
public event EventHandler CanExecuteChanged
{
add
{
_canExecuteChanged += value;
_model.ModelChanged -= _modelChanged;
_model.ModelChanged += _modelChanged;
}
remove
{
_canExecuteChanged -= value;
_model.ModelChanged -= _modelChanged;
}
}
public void Execute(object parameter)
{
}
private void _modelChanged(object sender, EventArgs e)
{
if (_canExecuteChanged != null)
_canExecuteChanged(this, new EventArgs());
}
private event EventHandler _canExecuteChanged;
private MyModel _model;
}
Now sender is the command itself, and everything is fine. Another option is to use CommandManager and its RequerySuggested event:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
And again it works! Hm, now I’m completely puzzled. Sender is not the command – it’s null, but if I try to send my own event with null sender it doesn’t work again.
Did anyone faced the same? Is it such a strange way of optimization on new Windows? Honestly it looks more like a bug.
Seems, this is a breaking change in .Net Framework 4.5.
The issue is reported to Microsoft here:
http://connect.microsoft.com/VisualStudio/feedback/details/751429/wpf-icommand-canexecutechanged-behaviour-change-in-net-4-5