Search code examples
wpfvb.netmvvmcomboboxdatagrid

MVVM WPF datagrid Selected binding to combobox with complex object


I'm trying to implement a DataGrid in which one of the columns has a complex object and the comboBox (out of the grid) has a list of the same time object of this column. Using WPF and MVVM.

When I select the Line in the datagrid, I want the combo box to Show the corresponding value.

But I don't know what am I doing wrong. Can anyone help?

    <ComboBox x:Name="cmbResourceList" 
              HorizontalAlignment="Left" 
              ItemsSource="{Binding MainListSource}"
              DisplayMemberPath="Description"
                SelectedValue="{Binding Path=ScheduleVM.Schedule.ResourceList}"
              Width="185"/>

    <DataGrid x:Name="dgScheduleItems"   
              AutoGenerateColumns="False" 
              ItemsSource="{Binding Path=ScheduleSource}" 
              SelectionMode="Single"
              IsReadOnly="True"
              SelectedItem="SelectedSchedule">

        <DataGrid.Columns>
            <DataGridTextColumn Header="Id"             Binding="{Binding Id}"                          Width="40"/>
            <DataGridTextColumn Header="Resource List"  Binding="{Binding ResourceList.Description}"    Width="*"/>
            <DataGridTextColumn Header="Task List"      Binding="{Binding TaskList.Description}"        Width="*"/>
            <DataGridTextColumn Header="Description"    Binding="{Binding Description}"                 Width="100"/>
        </DataGrid.Columns>

    </DataGrid>

And here The classes in Vb.Net

Public Class MainList
    Public Property Id As Integer
    Public Property Description As String
End Class


Public Class Schedule
    Public Property Id As Integer
    Public Property Description As String
    Public Property ResourceList As MainList
    Public Property TaskList As MainList
End Class


Public Class MainListRepository
    Private _mainLists As List(Of MainList)

    Public Sub New()
        _mainLists = New List(Of MainList) From
        {
            New MainList() With {.Id = 1, .Description = "First List"},
            New MainList() With {.Id = 2, .Description = "Second List"},
            New MainList() With {.Id = 3, .Description = "Third List"}
        }
    End Sub

    Public Function GetMainLists() As List(Of MainList)
        Return _mainLists
    End Function
End Class


Public Class ScheduleRepository
    Private _schedules As List(Of Schedule)

    Public Sub New()
        _schedules = New List(Of Schedule) From
        {
            New Schedule() With {.Id = 1, .Description = "Schedule 1", .ResourceList = New MainList() With {.Id = 2, .Description = "Second List"}, .TaskList = New MainList() With {.Id = 1, .Description = "First List"}},
            New Schedule() With {.Id = 2, .Description = "Schedule 2", .ResourceList = New MainList() With {.Id = 1, .Description = "First List"}, .TaskList = New MainList() With {.Id = 2, .Description = "Second List"}},
            New Schedule() With {.Id = 3, .Description = "Schedule 3", .ResourceList = New MainList() With {.Id = 1, .Description = "First List"}, .TaskList = New MainList() With {.Id = 3, .Description = "Third List"}},
            New Schedule() With {.Id = 4, .Description = "Schedule 4", .ResourceList = New MainList() With {.Id = 3, .Description = "Third List"}, .TaskList = New MainList() With {.Id = 1, .Description = "First List"}}
        }
    End Sub

    Public Function GetSchedules() As List(Of Schedule)
        Return _schedules
    End Function
End Class

Public MustInherit Class ViewModelBase
    Implements INotifyPropertyChanged

    Protected Sub New()
    End Sub
    
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
    Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
        Me.VerifyPropertyName(propertyName)

        Dim handler As PropertyChangedEventHandler = Me.PropertyChangedEvent
        If handler IsNot Nothing Then
            Dim e = New PropertyChangedEventArgs(propertyName)
            handler(Me, e)
        End If
    End Sub
    
    Private privateThrowOnInvalidPropertyName As Boolean
    Protected Overridable Property ThrowOnInvalidPropertyName() As Boolean
        Get
            Return privateThrowOnInvalidPropertyName
        End Get
        Set(ByVal value As Boolean)
            privateThrowOnInvalidPropertyName = value
        End Set
    End Property
End Class

Public Class ScheduleVM
    Inherits ViewModelBase

    Private _Schedule As Schedule
    Public Property Schedule() As Schedule
        Get
            Return _Schedule
        End Get
        Set(ByVal value As Schedule)
            _Schedule = value

            OnPropertyChanged("Schedule")
        End Set
    End Property

    Public Sub New(schedule As Schedule)
        _Schedule = schedule
    End Sub

End Class


Friend Class SchedulePopupVM
    Inherits ViewModelBase

    Private _ScheduleSource As List(Of Schedule)
    Public Property ScheduleSource() As List(Of Schedule)
        Get
            Return _ScheduleSource
        End Get
        Set(ByVal value As List(Of Schedule))
            _ScheduleSource = value

            OnPropertyChanged("ScheduleSource")
        End Set
    End Property

    Private _MainListSource As List(Of MainList)
    Public Property MainListSource() As List(Of MainList)
        Get
            Return _MainListSource
        End Get
        Set(ByVal value As List(Of MainList))
            _MainListSource = value

            OnPropertyChanged("MainListSource")
        End Set
    End Property


    Private _SelectedSchedule As Schedule
    Public Property SelectedSchedule() As Schedule
        Get
            Return _SelectedSchedule
        End Get
        Set(ByVal value As Schedule)
            _SelectedSchedule = value
            Me.ScheduleVM = New ScheduleVM(value)

            OnPropertyChanged("SelectedSchedule")
        End Set
    End Property

    Private _ScheduleVM As ScheduleVM
    Public Property ScheduleVM() As ScheduleVM
        Get
            Return _ScheduleVM
        End Get
        Set(ByVal value As ScheduleVM)
            _ScheduleVM = value

            OnPropertyChanged("ScheduleVM")
        End Set
    End Property

    Public Sub New()
        Dim repo As ScheduleRepository = New ScheduleRepository()
        Dim repoMain As MainListRepository = New MainListRepository()

        _ScheduleSource = repo.GetSchedules()
        _MainListSource = repoMain.GetMainLists()

    End Sub

End Class

Solution

  • A few things.

    Both of your SelectedValues (on your ComboBox) are bound to item collections. You do not want this, these properties are meant to be bound to individual items within the item collection.

    Next, your binding for the SelectedItem of your DataGrid is wrong. Update it to (notice binding keyword and braces):

      <DataGrid x:Name="dgScheduleItems"   
          AutoGenerateColumns="False" 
          ItemsSource="{Binding Path=ScheduleSource}" 
          SelectionMode="Single"
          IsReadOnly="True"
          SelectedItem="{Binding SelectedSchedule}"
          Margin="0,106,0,0">
    

    Next, for your ComboBoxes, you then want to bind to an individual value. Though, the value you are binding to for SelectedValue must be a reference to one of the values in its ItemsSource.

    Update them to bind to a NEW property. Example:

       <ComboBox x:Name="cmbTaskList" 
                HorizontalAlignment="Left" 
                ItemsSource="{Binding MainListSource}"
                DisplayMemberPath="Description"
                SelectedValue="{Binding MainListSourceSelectedValue, Mode=TwoWay}"
                Margin="244,30,0,0" 
                VerticalAlignment="Top" 
                Width="185"/>
    

    Then, set the NEW property (MainListSourceSelectedValue) when the SelectedSchedule property changes. Something like (in c#)

    public MainList MainListSourceSelectedValue
    {
        get { return _mainListSourceSelectedValue; }
        set
        {
            if (_mainListSourceSelectedValue == value) return; 
            _mainListSourceSelectedValue = value;
    
            RaisePropertyChanged();
        }
    }
    
    public Schedule SelectedSchedule
    {
        get { return _selectedSchedule; }
        set
        {          
            if (_selectedSchedule == value) return;
            _selectedSchedule = value;
    
            foreach (var mainListItem in MainList)
            {
                if (mainListItem.Description.equals(_selectedSchedule.ResourseList.Description))
                {
                    MainListSourceSelectedValue = mainListItem;
                    break;
                }
            }
    
            RaisePropertyChanged();
        }
    }