Why is my RelayCommand Parameter Not Available in the ViewModel

RogerSchlueter-7899 1,256 Reputation points
2024-02-27T15:02:31.81+00:00
<ListBox
    DisplayMemberPath="Name"
    ItemsSource="{Binding Path=GeoElements}"
    SelectedValue="{Binding Path=RouteID}"
    SelectedValuePath="ElementID">
    <i:Interaction.Triggers>
        <i:EventTrigger
            EventName="SelectionChanged">
            <i:InvokeCommandAction
                Command="{Binding Path=SelectRoute}"
                CommandParameter="{Binding Path=SelectedValue}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>

In the ViewModel:

Private ReadOnly Property _SelectRoute As New RelayCommand(AddressOf DoSelectRoute)
Public ReadOnly Property SelectRoute As RelayCommand
    Get
        Return _SelectRoute
    End Get
End Property
Private Sub DoSelectRoute(obj As Object)
    Debug.WriteLine(obj Is Nothing)
End Sub

That Debug statement yields True. I have verified that when an item is selected from the ListBox, the SelectedValue is correct. So why is the parameter obj Nothing? I am using the canonical version of the RelayCommand so I don't think the problem is there.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,708 questions
VB
VB
An object-oriented programming language developed by Microsoft that is implemented on the .NET Framework. Previously known as Visual Basic .NET.
2,652 questions
0 comments No comments
{count} votes

Accepted answer
  1. Michael Taylor 50,836 Reputation points
    2024-02-27T15:21:26.61+00:00

    Because your UX is mapping to a RelayCommand. That type has no parameters so the parameters aren't passed onto the command, because it doesn't support any.

    Try changing the command to RelayCommand(Of Object). This exposes a command that accepts a parameter of type Object.

    Private ReadOnly Property _SelectRoute As New RelayCommand(Of Object)(AddressOf DoSelectRoute)
    Public ReadOnly Property SelectRoute As RelayCommand(Of Object)
        Get
            Return _SelectRoute
        End Get
    End Property
    Private Sub DoSelectRoute(obj As Object)
        Debug.WriteLine(obj Is Nothing)
    End Sub
    

    Note that you should ideally be using something more precise then Object.


2 additional answers

Sort by: Most helpful
  1. Hui Liu-MSFT 47,256 Reputation points Microsoft Vendor
    2024-02-28T03:37:44.1666667+00:00

    Hi,@RogerSchlueter-7899. Welcome to Microsoft Q&A.

    It is likely due to the way you're trying to bind the CommandParameter of the InvokeCommandAction. In your XAML, you're trying to bind to SelectedValue directly, but SelectedValue is not directly accessible in the context of the CommandParameter binding within the InvokeCommandAction.

    You could use a RelativeSource binding to access the SelectedValue property of the ListBox.

    <ListBox
        DisplayMemberPath="Name"
        ItemsSource="{Binding Path=GeoElements}"
        SelectedValue="{Binding Path=RouteID}"
        SelectedValuePath="ElementID" >
        <i:Interaction.Triggers>
            <i:EventTrigger
                EventName="SelectionChanged">
                <i:InvokeCommandAction
                    Command="{Binding Path=SelectRoute}"
                    CommandParameter="{Binding Path=SelectedValue}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ListBox>
    

    This change will ensure that the CommandParameter is correctly bound to the SelectedValue of the ListBox. Now, when an item is selected in the ListBox, the DoSelectRoute method in your view model should receive the correct value for obj.

    My test result: User's image


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment". Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

  2. Peter Fleischer (former MVP) 19,311 Reputation points
    2024-02-28T05:36:08.9733333+00:00

    Hi Roger,
    see Hui Lee's answer and change your XAML like in following demo:

    <Window x:Class="Window114"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1.WpfApp114"
            xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
            mc:Ignorable="d"
            Title="Window114" Height="450" Width="800">
      <Window.DataContext>
        <local:ViewModel/>
      </Window.DataContext>
      <Grid>
        <ListBox
          DisplayMemberPath="Name"
          ItemsSource="{Binding Path=GeoElements}"
          SelectedValue="{Binding Path=RouteID}"
          SelectedValuePath="ElementID">
          <i:Interaction.Triggers>
            <i:EventTrigger
                EventName="SelectionChanged">
              <i:InvokeCommandAction
                    Command="{Binding Path=SelectRoute}"
                    CommandParameter="{Binding Path=SelectedValue, RelativeSource={RelativeSource AncestorType=ListBox}}" />
            </i:EventTrigger>
          </i:Interaction.Triggers>
        </ListBox>
      </Grid>
    </Window>
    

    Code:

    Namespace WpfApp114
      Public Class ViewModel
        Public Sub New()
          For i = 1 To 10
            GeoElements.Add(New Data() With {.ElementID = i, .Name = $"Element {i}"})
          Next
        End Sub
        Public Property GeoElements As New List(Of Data)
        Public Property RouteID As Object
        Private ReadOnly Property _SelectRoute As New RelayCommand(Of Integer)(AddressOf DoSelectRoute)
        Public ReadOnly Property SelectRoute As RelayCommand(Of Integer)
          Get
            Return _SelectRoute
          End Get
        End Property
        Private Sub DoSelectRoute(obj As Object)
          Debug.WriteLine($"{obj Is Nothing} {obj}")
        End Sub
      End Class
      Public Class Data
        Public Property ElementID As Integer
        Public Property Name As String
      End Class
      Public Class RelayCommand(Of T)
        Implements ICommand
    #Region "Fields"
        ' delegate to execute
        Private ReadOnly _execute As Action(Of T)
        Private ReadOnly _canExecute As Predicate(Of T)
    #End Region ' Fields
    #Region "Constructors"
        ''' <summary>
        ''' in ctor get delegate to execute
        ''' </summary>
        ''' <param name="execute">The execution logic.</param>
        Public Sub New(ByVal execute As Action(Of T))
          Me.New(execute, Nothing)
        End Sub
        ''' <summary>
        ''' in ctor get delegate to execute and predicate to set enabled
        ''' </summary>
        ''' <param name="execute">The execution logic.</param>
        ''' <param name="canExecute">The execution status logic.</param>
        Public Sub New(ByVal execute As Action(Of T), ByVal canExecute As Predicate(Of T))
          If execute Is Nothing Then Throw New ArgumentNullException("execute")
          Me._execute = execute
          Me._canExecute = canExecute
        End Sub
    #End Region ' Constructors
    #Region "ICommand Members"
        <DebuggerStepThrough()>
        Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
          ' without predicate return true (Enabled), with predicate get result of predicate
          Return If(Me._canExecute Is Nothing, True, Me._canExecute(CType(parameter, T)))
        End Function
        Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
          AddHandler(ByVal value As EventHandler)
            AddHandler CommandManager.RequerySuggested, value
          End AddHandler
          RemoveHandler(ByVal value As EventHandler)
            RemoveHandler CommandManager.RequerySuggested, value
          End RemoveHandler
          RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
          End RaiseEvent
        End Event
        Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
          Me._execute(CType(parameter, T))
        End Sub
    #End Region ' ICommand Members
      End Class
    End Namespace