Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updated title: Why ICommand.CanExecute is getting called all the time, instead of working like an event?

I am adopting MVVM pattern in WPF and have learned the use of Command. But in my implementation, the delegate I assigned to implement CanExecute is always called. I mean if I put a break point inside the delegate function, it shows that this function keeps getting called. To my understanding (and a natural way of thinking, but of course I can be wrong), this delegate only gets called when I somehow notifies the change of the state and that's when the CommandManager (re)checks the CanExecute property and modify the IsEnabled property of the UI element.

Here is my implementation of VB.NET, which I got originally from a C# version. I did notice that I needed to make some change to the ported code in order for it to compile. Could it be the underlying of C# and VB.NET is different? So can somebody provide me a original VB.NET implementation, or point me out what is wrong or do if I understand the Command behavior correctly?

Here is my VB.NET version:

 Public Class CommandBase
    Implements ICommand

    Public Property ExecuteDelegate() As Action(Of Object)

    Public Property CanExecuteDelegate() As Predicate(Of Object)

    Public Sub New()
    End Sub

    Public Sub New(execute As Action(Of Object))
        Me.New(execute, Nothing)
    End Sub

    Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object))
        If execute Is Nothing Then
            Throw New ArgumentNullException("execute")
        End If
        ExecuteDelegate = execute
        CanExecuteDelegate = canExecute
    End Sub

    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        Return If(CanExecuteDelegate Is Nothing, True, CanExecuteDelegate(parameter))
    End Function

    Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
        AddHandler(ByVal value As EventHandler)

            If CanExecuteDelegate IsNot Nothing Then
                AddHandler CommandManager.RequerySuggested, value
            End If

        End AddHandler
        RemoveHandler(ByVal value As EventHandler)
            If CanExecuteDelegate IsNot Nothing Then
                RemoveHandler CommandManager.RequerySuggested, value
            End If
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
            CommandManager.InvalidateRequerySuggested()
        End RaiseEvent
    End Event

    Public Sub Execute(parameter As Object) Implements ICommand.Execute
        If ExecuteDelegate IsNot Nothing Then ExecuteDelegate.Invoke(parameter)
    End Sub

    Public Sub RaiseCanExecuteChanged()
        CommandManager.InvalidateRequerySuggested()
    End Sub

End Class

And how I instantiate an object is something like this:

MyCommand = New CommandBase(AddressOf CommandExec, AddressOf CanExecuteExec)

where the CanExecuteExec of course has the signature like this:

Private Function CanExecuteExec(obj As Object) As Boolean

Like I mentioned, the CanExecuteExec is getting called all the time. I guess it is inefficient, imagine that I have hundreds of Command objects and most of the CanExecute of them don't get changed most of the time.

UPDATE:

Somebody says the CanExecute indeed gets called all the time, while others say the opposite. I am no expert on this but I have to say the second opinion sounds more natural and makes more sense to me. Although I still need to figure out if that is true, why WPF detects the change all the time so that it keeps checking the CanExecute

like image 956
tete Avatar asked Sep 23 '13 14:09

tete


2 Answers

In your CanExecuteDelegate you have hook to CommandManager.RequerySuggested.

So, whenever CommandManager.RequerySuggested is raised your CanExecuteDelegate will be called.

CommandManager.RequerySuggested event is raised whenever changes to the command source are detected by the command manager which ranges from Keyboard.KeyUpEvent, Mouse.ClickEvent etc.

Also, CommandManager has a static method - InvalidateRequerySuggested which forces the CommandManager to raise the RequerySuggestedEvent. So, you can call that to validate your commands too manually.

If you want to take the control in hand for raising CanExecute, you can use the Delegate Command provided by PRISM. CanExecute delegate will get called only when you explicitly call RaiseCanExecuteChanged() method exposed by Delegate Command.

Incorporating comments to answer

Breakpoint is hitting every time on turning to VS since CommandManager RequerySuggested event gets called on lost focus of window and on activation property changed of window. That's why you notice that breakpoint is hitting every now and then when you move to VS since focus moves from WPF window to Visual Studio.

like image 101
Rohit Vats Avatar answered Oct 24 '22 18:10

Rohit Vats


When you set up your command, there's no reliable way for the runtime to know what data your CanExecute will rely on in order to make its decision. So, when you have commands that are bound into your UI and are registered in the CommandManager, the behaviour is that the CanExecute for all commands is re-evaluated whenever the state of your application changes. The way WPF knows about this is when a bound property is updated, or when a UI event occurs.

Typically you'll see CanExecute called whenever bindings update or when certain control events occur (for example, when a textbox's text is highlighted, the CanExecute of the inbuilt Cut and Copy commands will change, and so the highlight event triggers a re-evaluation that I would imagine is bound to the MouseUp event).

like image 38
Dan Puzey Avatar answered Oct 24 '22 18:10

Dan Puzey