All of us who have been designing and developing using XAML technologies use ICommand interface on a daily basis. The interface requires implementation of Execute() and CanExecute() methods. Here Execute method is used to perform an action in response to a user interaction. However, the action can only be executed if CanExecute() method returns true. You might have commonly seen a Command being bound to a button. In this case, button would be enabled or disabled based on the evaluation of CanExecute() method of the ICommand. Now let me throw the main question in the air...
Can we run into a situation when a button is enabled when it is not supposed to, and How often ICommand.CanExecute() is called?
Let me first give you the answer of first part of the question, YES, we can certainly run into situations when a button should be disabled when it is supposed to be enabled and vice versa.
The second part of the question is tricky. It is dependent upon the implementation of ICommand. Other than the required Execute and CanExecute() methods, the interface also requires implementation of CanExecuteChanged event. So CanExecute method would be called in all those instances when the command raises this event. If you look at Josh Smith's implementation of ICommand i.e. RelayCommand, here we are directly hooking it up with CommandManager.RequerySuggested event. Now all user interactions would result in re-evaluation of CanExecute(). But what if the underlying property changes value in response to an event other than any user action. In all those situations, we could face this problem.
We might have different implementations handling this differently. This is quite similarly handler in MVVMLight Libs.
And this is from Prism's DelegateCommand type. That's why we need to call RaiseCanExecuteChanged for DelegateCommand every time we need re-evaluation of CanExecute method.
In order to understand the severity of the problem, let's create a sample WPF application. The following is a sample view model with FirstName property of type string. It also has an ICommand property, SubmitCommand. Here we are using Josh Smith's implementation of ICommand i.e. RelayCommand. The code can be found at his original article on msdn. In the command's CanExecute() method, we are checking if FirstName is null or empty, we are returning false in this case. This should disable the button.
And here is the view which is using the view model, defined above, as its DataContext. It is Binding the FirstName property to a TextBox. Since UpdateSourceTrigger is set as PropertyChanged, it would continue to update the source property as user keeps modifying the text. It also has a button which binds its Command property to view model's SubmitCommand. Now clicking the button should cause running the Execute method of the command. The button should be enabled only when the command's CanExecute() method returns true.
As we are modifying the text in the TextBox, we see that as we empty the TextBox, the button is disabled, which is as expected.
Actually modifying the content of the TextBox cause CommandManager.RequerySuggested to be triggered. Since RelayCommand uses this, it causes CanExecuteChanged event to be triggered. Since the UI framework hooks up to this event. It causes CanExecute to be called.
In order to understand the problem, let's add the following code to our view model. It just flips the value of FirstName property between string.Empty and "Not Empty" literal. Here we expect that as soon as we set it to String.Empty, the button should be disabled. Here we are using a System.Timers.Timer for this purpose. Since Elapsed event is invoked on a ThreadPool thread, the value is being updated without any user interaction on a non-UI thread. Since WPF is very forgiving about PropertyChanged on a non-UI thread, we don't have Dispatcher issues for assigning a value to FirstName in this thread.
Let's run the application and see the result. As shown here, the button is always enabled. This is how we have defined CanExecuteChanged event in RelayCommand. Since MVVMLight's RelayCommand is also defined similarly, we should have the same problem there. For Prism's DelegateCommand, we need to call RaiseCanExecuteChanged(), which raises CanExecuteChanged event for the command.
Now we understand the problem. In the next post, we are going to see how we can resolve it. Stay tuned!
Tuesday, April 8, 2014
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment