MVVM & IsEnabled issue.

MattP

Well-known member
Joined
Feb 29, 2008
Messages
1,206
Location
WY, USA
Programming Experience
5-10
Posting this in the Silverlight forum since that's what I'm using and I don't see an MVVM section. If a mod needs to move it to the MVC section since it's a pattern question please do so.

I'm having an issue getting a button to be disabled when CanExecute in my ViewModel is set to false.

Here's the code for my button:

VB.NET:
                    <Button Content="Remove Collateral" >
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Click">
                                <i:InvokeCommandAction Command="{Binding Source={StaticResource vm}, Path=DeleteCollateralCommand}" 
                                                   CommandParameter="{Binding DataContext, ElementName=dataGrid}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </Button>

Nothing happens when you click on the button because CanExecute is false but the button is still clickable rather than disabled.

Here's the code for my DelegateCommand class:

VB.NET:
Public Class DelegateCommand
    Implements ICommand

    Dim _executeAction As Action(Of Object)
    Dim _canExecuteCache As Boolean
    Dim _canExecute As Func(Of Object, Boolean)

    ''' <summary>
    ''' Initializes a new instance of the <see cref="DelegateCommand"/> class.
    ''' </summary>
    ''' <param name="executeAction">The execute action.</param>
    ''' <param name="canExecute">Whether it can execute.</param>
    Public Sub New(ByVal executeAction As Action(Of Object), ByVal canExecute As Func(Of Object, Boolean))

        _executeAction = executeAction
        _canExecute = canExecute

    End Sub

    ''' <summary>
    ''' Determines whether the command can execute in its current state.
    ''' </summary>
    ''' <param name="parameter">Data used by the command.</param>
    ''' <returns>True if the command can be executed.</returns>
    Public Function CanExecute(ByVal parameter As Object) As Boolean _
        Implements ICommand.CanExecute

        Dim temp = _canExecute(parameter)
        If _canExecuteCache <> temp Then
            _canExecuteCache = temp
            RaiseEvent CanExecuteChanged(Me, New EventArgs)
        End If
        Return _canExecuteCache

    End Function

    ''' <summary>
    ''' Occurs when changes happen that affects whether the command should execute.
    ''' </summary>
    Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
        Implements ICommand.CanExecuteChanged

    ''' <summary>
    ''' Defines method to call when the command is invoked.
    ''' </summary>
    ''' <param name="parameter">Data used by the command.</param>
    Public Sub Execute(ByVal parameter As Object) _
        Implements ICommand.Execute

        _executeAction(parameter)

    End Sub
End Class

Here are the entities I'm working with in the ViewModel:

VB.NET:
        Public ReadOnly Property Collateral As EntitySet(Of Collateral)
            Get
                Return ctx.Collaterals
            End Get
        End Property

        Private _selectedCollateral As Collateral
        Public Property SelectedCollateral As Collateral
            Get
                Return _selectedCollateral
            End Get
            Set(ByVal value As Collateral)
                _selectedCollateral = value
                RaisePropertyChanged("SelectedCollateral")
            End Set
        End Property

Initialization code for my DelegateCommand:

VB.NET:
CollateralSelectionChanged = New DelegateCommand(AddressOf OnCollateralSelectionChangedExecute, AddressOf CanChangeCollateralSelection

VB.NET:
        Public Property DeleteCollateralCommand As DelegateCommand
        Private Sub OnDeleteCollateralExecute(ByVal param As Object)
            DirectCast(param, EntitySet(Of Collateral)).Remove(SelectedCollateral)
            RaisePropertyChanged("Collateral")
        End Sub
        Private Function CanDeleteCollateral() As Boolean
            Return SelectedCollateral IsNot Nothing
        End Function

From everything I've read this should handle the IsEnabled property on the button.

To get around this I've created a new property which I'm raising whenever SelectedCollateral is set and then manually bound it to the IsEnabled property on the button.

VB.NET:
                    <Button Content="Remove Collateral" IsEnabled="{Binding Source={StaticResource vm}, Path=CanDeleteCollateralProp}">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Click">
                                <i:InvokeCommandAction Command="{Binding Source={StaticResource vm}, Path=DeleteCollateralCommand}" 
                                                   CommandParameter="{Binding DataContext, ElementName=dataGrid}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </Button>

This seems hacky to me and shouldn't be necessary from what I've read about MVVM.

If anyone could point out my error it would be appreciated. Thanks in advance.
 
Added these 2 methods to the DelegateCommand class:

VB.NET:
    Public Sub RaiseCanExecuteChanged()
        OnCanExecuteChanged(EventArgs.Empty)
    End Sub

    Protected Overridable Sub OnCanExecuteChanged(ByVal e As EventArgs)
        RaiseEvent CanExecuteChanged(Me, e)
    End Sub

From there I can call RaiseCanExecuteChanged on whatever DelegateCommand I want to have updated.

VB.NET:
        Private _selectedCollateral As Collateral
        Public Property SelectedCollateral As Collateral
            Get
                Return _selectedCollateral
            End Get
            Set(ByVal value As Collateral)
                _selectedCollateral = value
                RaisePropertyChanged("SelectedCollateral")
                DeleteCollateralCommand.RaiseCanExecuteChanged()
            End Set
        End Property
 
Back
Top