命令的CanExecuteChanged事件未更新

4

我正在尝试学习MVVM模式的基础知识,但是在ICommand CanExecute改变时遇到了困难。我的XAML绑定如下:

    <ListBox ItemsSource="{Binding Contact.Addresses}"  x:Name="AddressCollections" Height="152" SelectedValue="{Binding SelectedAddress}"
             DockPanel.Dock="Top" />
    <Button Content="Add" Command="{Binding AddAddressCommand}" DockPanel.Dock="Top" />
    <Button Content="Remove" Command="{Binding DeleteAddressCommand}" DockPanel.Dock="Bottom" />

命令:

Public Class DeleteCommand
Implements ICommand

Private method As Object
Private methodname As String

Public Sub New(ByVal Controlname As String, ByVal mee As Object)
    methodname = Controlname
    method = mee
End Sub

Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    Select Case methodname
        Case "Address"
            Return TryCast(method, ModelView.Contacts.ContactMV).CanDeleteAddress()
        Case "Numbers"
            Return TryCast(method, ModelView.Contacts.ContactMV).CanDeleteNumbers
        Case Else : Return False
    End Select
End Function

Public Event CanExecuteChanged(sender As Object, e As EventArgs) Implements ICommand.CanExecuteChanged

Public Sub Execute(parameter As Object) Implements ICommand.Execute
    Select Case methodname
        Case "Address"
            TryCast(method, ModelView.Contacts.ContactMV).DeleteAddress()
        Case "Numbers"
            TryCast(method, ModelView.Contacts.ContactMV).DeleteNumbers()
        Case Else

    End Select
End Sub
End Class

我的模型视图:

Public Class ContactMV

Property Contact As Model.Contacts.ContactMod
Property AddAddressCommand As New Commands.AddCommand("Address", Me)
Property DeleteAddressCommand As New Commands.DeleteCommand("Address", Me)
Property SelectedAddress As Model.Contacts.AddressModel
Public Sub AddAddress()
    If Contact.Addresses.Count = 0 Then
        Contact.Addresses.Add(New Model.Contacts.AddressModel(Contact.Primary.ID, True))
    Else
        Contact.Addresses.Add(New Model.Contacts.AddressModel(Contact.Primary.ID, False))
    End If

End Sub
Public Sub DeleteAddress()
    If IsNothing(SelectedAddress) = False Then
        Try
            Contact.Addresses.Remove(SelectedAddress)
        Catch ex As Exception
            MsgBox("Address not found")
        End Try
    End If
End Sub
Public Function CanDeleteAddress()
    If IsNothing(SelectedAddress) Then
        Return False
    Else
        Return Contact.Addresses.Contains(SelectedAddress)
    End If
End Function
End Class

问题在于CanExecuteChanged只在开始时触发,我实际上希望仅在选择列表框中的某些内容时启用删除按钮,并且我希望通过MVVM - ICommand绑定方法来完成。您能否解释一下我错在哪里或者误解了ICommand实现。
谢谢。
我使用的更新的Relay iCommand代码:
    Public Class RelayCommand
        Implements ICommand
        ''' <summary>
        ''' A command whose sole purpose is to relay its functionality to other objects by invoking delegates. The default return value for the CanExecute method is 'true'.
        ''' </summary>
        ''' <remarks></remarks>

#Region "Declarations"
        Private ReadOnly _CanExecute As Func(Of Boolean)
        Private ReadOnly _Execute As Action
#End Region

#Region "Constructors"
        Public Sub New(ByVal execute As Action)
            Me.New(execute, Nothing)
        End Sub

        Public Sub New(ByVal execute As Action, ByVal canExecute As Func(Of Boolean))
            If execute Is Nothing Then
                Throw New ArgumentNullException("execute")
            End If
            _Execute = execute
            _CanExecute = canExecute
        End Sub
#End Region

#Region "ICommand"
        Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged

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

                If _CanExecute IsNot Nothing Then
                    RemoveHandler CommandManager.RequerySuggested, value
                End If
            End RemoveHandler

            RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
                'This is the RaiseEvent block
                'CommandManager.InvalidateRequerySuggested()
            End RaiseEvent
        End Event

        Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
            If _CanExecute Is Nothing Then
                Return True
            Else
                Return _CanExecute.Invoke
            End If
        End Function

        Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
            _Execute.Invoke()
        End Sub
#End Region
    End Class

大部分代码都是复制的,但我通过下面的注释理解了它的工作原理。

1
尝试将MVVM Light的“RelayCommand”与Microsoft Prism的“DelegateCommand”进行比较。当属性更改时,“RelayCommand”将自动引发“CanExecuteChanged”,而“DelegateCommand”则不会。 - Rachel
2个回答

7

正如Raul Otaño所指出的那样,您可以提高CanExecuteChanged。然而,并非所有MVVM框架都提供RaiseCanExecuteChanged方法。值得注意的是,实际事件CanExecuteChanged必须在UI线程上调用。所以,如果您期望从模型中的某个线程获得回调,则需要将其调用回UI线程,像这样:

    public void RaiseCanExecuteChanged()
    {
        if (CanExecuteChanged != null)
        {
            Application.Current.Dispatcher.Invoke((Action)(() => { CanExecuteChanged(this, EventArgs.Empty); }));
        }
    }

我非常不建议调用CommandManager.InvalidateRequerySuggested(),因为尽管这在功能上有效,并且对于小型应用程序是可以的,但它是不加区分的,可能会重新查询每个命令!在有很多命令的大型系统中,这可能非常缓慢!


根据您的建议,我尝试注释掉了"InvalidateRequerySuggested"的代码,并且有趣的是它仍然有效。附上我更新后的ICommand代码。谢谢。 - surpavan
WPF会使用任何本地更改来重新查询命令状态,因此,例如,如果您单击按钮或单击表单,或者如果命令状态的更改是由按钮单击等操作引起的,则此更改将显示,而无需手动激活命令。但是,如果您的命令是异步完成的(考虑到具有oncompleted事件的后台工作者或类似情况),则oncompleted处理程序需要更新命令所依赖的适当数据,然后调用命令上的RaiseCanExecuteChanged事件。希望这可以帮助您! - Immortal Blue

5
你的ICommand实现中必须有类似于`RaiseCanExecuteChanged`的方法,以触发`CanExecuteChanged`事件。每次列表中选定项更改时,在你的视图模型中执行所需命令的`RaiseCanExecuteChanged`方法。无论如何,我建议你使用任何通用命令,例如GalaSoft MVVMLite库的`RelayCommand`或任何`DelegateCommand`的实现。 希望这有所帮助...

1
经过彻底的谷歌搜索或 MSDN 搜索关于中继命令的核心区别,发现它与 ICOMMAND 的区别在于强制触发 raise event 以重新查询 canexecute 状态,并且强制执行的代码是 "CommandManager.InvalidateRequerySuggested()"。谢谢。我现在已经对我的代码进行了更改,可以更好地在 MVVM 路径上工作。 - surpavan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接