I had been merrily using WPF’s built-in support for the Command Pattern for ages (see Commanding Overview, MSDN Docs, and article on implementing the command pattern in WPF, Jeff Druyt)… when suddenly it occured to me that I had no idea what triggered WPF to determine whether or not a command can be executed.
Let me explain by reduction to an absurd example:
Say I have a command that can only execute when
DateTime.Now.Second % 2 == 0.
I construct this command by home-brewing a static RoutedCommand instance:
public static class Commands
{
public static RoutedCommand MyCommand { get { return m_MyCommand; } }
private static RoutedCommand m_MyCommand = new RoutedCommand
(
“Execute My Command”,
typeof(Commands),
new InputGestureCollection()
{
new KeyGesture(Key.C, ModifierKeys.Alt)
}
);
}
And then I add a Command Binding for that command to my Window, and assign the command to a Button:
<Window x:Class=”TestCommands.Window1″…>
<Window.CommandBindings>
<CommandBinding
Command=”{x:Static local:Commands.MyCommand}”
Executed=”MyCommandExecuted”
CanExecute=”MyCommandCanExecute”
/>
</Window.CommandBindings>
…
<Button Width=”200″ Height=”200″
Command=”{x:Static local:Commands.MyCommand}”
Content=”{Binding Path=IsEnabled}”
/>
…
</Window>
By nature of WPF’s awesomeness and WPF Commanding in general, the above Button’s IsEnabled property should automatically be set to true or false based on whether or not the command can or can’t be executed.
Speaking of which, let’s set up my Command’s absurd logic in the CodeBehind by implementing its Execute and CanExecute event handlers:
private void MyCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
DateTime now = DateTime.Now;
if (CanExecuteOutput != null)
{
CanExecuteOutput.Text = “MyCommand CanExecute determined at “ +
now.ToLongTimeString() + ” (and “ + now.Millisecond + “ms)”;
}
e.CanExecute = DateTime.Now.Second % 2 == 0;
}
private void MyCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
TextOutput.Text = “MyCommand executed at “ + DateTime.Now.ToLongTimeString();
}
So, my example is absurd but I bet you see my point by now: WPF is meant to automatically set the IsEnabled Property on that button to true or false, based on the results of the CanExecute method. But in this case, the results of CanExecute are a function only of time, and thus change repeatedly and independently of “obvious” application events. So… how does the Commanding system know when to query CanExecute and consequently enable/disable the button once a second?
In this case, without further intervention, it doesn’t. It seems that when events are raised on the Window (a mouse button click, etc.), CanExecute is re-evaluated. (I don’t know the details and wish I did.) But, without further programmatic or user intervention, the button will not automatically change its IsEnabled state once a second.
This led me back to the MSDN docs, where I discovered the aptly-named InvalidateRequerySuggested event. To coerce – er, suggest – that WPF should query CanExecute, I set up the following DispatcherTimer:
m_DispatcherTimer = new DispatcherTimer()
{
Interval = TimeSpan.FromSeconds(0.25),
IsEnabled = true
};
m_DispatcherTimer.Tick += delegate
{
CommandManager.InvalidateRequerySuggested();
};
Now, the IsEnabled property of the Button blinks on and off as the Command’s ability to be executed changes with the passing seconds.
Only then did I discover there’s an MSDN Docs sample called “Disable Command Source Via Dispatcher Timer Sample” which is remarkably similar.
There you have it. Now go forth and command WPF’s Commanding. I’m sure you can all execute on that request <g>
P.S. Code for this sample is here.
P.P.S. What are folks using for pasting XAML and C# code into their blogs? This entry is looking a little rough…
End-to-End Custom WPF Shader Example →← Mike Holmes Makes it Right
Dmitry says
Tht’s great investigation! It was very helpfull for me. Thanks!
May 22, 2008Rhywun says
Me too – thanks!
August 31, 2008igor says
thanks, very helpfull
October 28, 2008Daniel says
This doesn’t work when used with a built in command like ApplicationCommands.Print, any idea why?
Kind regards,
Daniel
November 7, 2008David Smith says
By default CanExecute is requeried on focus change for RoutedCommand (RoutedUICommand inherits from RoutedCommand as well).
December 1, 2008Geert van Horrik says
Just what I was looking for! Thank you!
January 23, 2009Amatzia says
You just made my day!
Thanks a lot
:-)
February 18, 2009Oskar says
InvalidateRequerySuggested() was just what I was looking for, thanks for sharing!
May 25, 2009Kanary says
Excellent!!! Thank you!
February 2, 2010Miky says
Many thanks :)
February 22, 2010Thank god google put you on first page ;)
Saved me a lot of digging
tim says
Lifesaver.
June 3, 2010Sudarsan Srinivasan says
Thanks a lot
July 14, 2010mikki says
thanks dude, exactly what I needed. Seems like the whole command architecture is really only worth it at a certain level of complexity of your application. otherwise you’d do the same thing old school with less lines of code.
August 1, 2010Odne says
Very helpfull :-)
September 14, 2010Could not imagine how to get my button to be enabled after a search, before to user clicked in the form. The sentence “CommandManager.InvalidateRequerySuggested();” saved my day.
Pelle says
I normally create my own implementation of ICommand but with the following code:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
Take a look at http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090030 for more details.
January 4, 2011Pelle says
Oops, posted the wrong link. Should be http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/
January 4, 2011Claude Glauser says
Thanks a lot for this good article!
November 29, 2011But remember that using a DispatchTimer might cause a memory leak! If you have many forms and each time the user switches to another form a new one is created, then with dispatch timer a memory leak is created.
Do not forget to deregister the timer when the form is closed.