1
votes

I'd like to bind the SelectedItem of a ComboBox to a specific item of an ObservableCollection inside the ViewModel.

In the ViewModel I have one ObservableCollection property:

Public Property SourceList As ObservableCollection(Of CustomItem)

Then I have these two custom class

Public Class CustomItem 
    Public Property Code As String
    Public Property Source As List(Of CustomValue)
    Public Property Selection As Object
End Class

Public Class CustomValue 
    Public Property Id As Integer
    Public Property Desc As String
End Class

The ComboBox in XAML

<Border Tag="10">
    <ComboBox DisplayMemberPath="Desc">
            <ComboBox.ItemsSource>
                <MultiBinding Converter="{StaticResource comboSourceConverter}">
                    <Binding Path="SourceList"/>
                    <Binding Path="Tag" RelativeSource="{RelativeSource AncestorLevel=1, Mode=FindAncestor, AncestorType=Border}" />
                </MultiBinding>
            </ComboBox.ItemsSource>
        </ComboBox>
</Border>

The converter I use to set the ItemsSource, which works correctly.

Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
    If values(0) IsNot Nothing AndAlso values(1) IsNot Nothing Then
        Return DirectCast(values(0), ObservableCollection(Of CustomItem)).Where(Function(x) x.Code = CStr(values(1))).First().Source
    End If
    Return Binding.DoNothing
End Function

Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
    Throw New NotImplementedException
End Function

I tried to do something similar for the SelectedItem. In the XAML:

<Border Tag="10">
<ComboBox DisplayMemberPath="Desc">
        <ComboBox.ItemsSource>
            <MultiBinding Converter="{StaticResource comboSourceConverter}">
                <Binding Path="SourceList"/>
                <Binding Path="Tag" RelativeSource="{RelativeSource AncestorLevel=1, Mode=FindAncestor, AncestorType=Border}" />
            </MultiBinding>
        </ComboBox.ItemsSource>
        <ComboBox.SelectedItem>
            <MultiBinding Converter="{StaticResource comboSourceConverter}">
                <Binding Path="SourceList"/>
                <Binding Path="Tag" RelativeSource="{RelativeSource AncestorLevel=1, Mode=FindAncestor, AncestorType=Border}" />
            </MultiBinding>
        </ComboBox.SelectedItem>
    </ComboBox>

The converter return the correct value chosen in the ComboBox, but this way it will raise the Set method on the SourceList property since it is the one declared as the first Path in the Multibinding.

Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
    If values(0) IsNot Nothing AndAlso values(1) IsNot Nothing Then
        Return DirectCast(values(0), ObservableCollection(Of CustomItem)).Where(Function(x) x.Code = CStr(values(1))).First().Selection 
End If
    Return Binding.DoNothing
End Function

Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
    Return New Object(){value} 
End Function

Setting the binding manually everything works fine, but I'd prefer doing everything through the converter if possible.

Dim _item As CustomItem = SourceList.Where(Function(x) x.Code = Border.Tag).FirstOrDefault()
Dim _binding = New Binding("Selection")
_binding.Mode = BindingMode.TwoWay
_binding.Source = _item 
ComboBox.SetBinding(ComboBox.SelectedItemProperty, _binding)
1
"I tried to do something similar for the SelectedItem returning the Selection property" -- please share the complete code for what you tried, and exactly how it failed. "Something similar" and "It doesn't work" isn't enough for anybody to guess what you did wrong.15ee8f99-57ff-4f92-890c-b56153
@EdPlunkett My bad. I updated my question.Fabio L.

1 Answers

1
votes

You should be letting XAML do a lot more of the hard work for you. Get your CustomItem once, and bind to its properties without any fussing around.

<Border DataContext="{Binding SourceList[10]}">
    <ComboBox 
        DisplayMemberPath="Desc"
        ItemsSource="{Binding Source}"
        SelectedItem="{Binding Selection}"
        />
</Border>

What that means is this. Let's call SourceList[10] itemX:

var itemX = SourceList[10];
  • The ComboBox will display the collection of CustomValue found in itemX.Source.

  • The ComboBox will assign its selected item to itemX.Selection.


Or better yet, this. I'm not certain this is the context of what you're doing, but it illustrates the way to think about this stuff:

<ItemsControl ItemsSource="{Binding SourceList}">
    <ItemsControl.ItemsTemplate>
        <DataTemplate>
            <!-- 
            Inside the DataTemplate, the DataContext will be one item 
            from SourceList.
            -->
            <Border>
                <ComboBox 
                    DisplayMemberPath="Desc"
                    ItemsSource="{Binding Source}"
                    SelectedItem="{Binding Selection}"
                    />
            </Border>
        </DataTemplate>
    </ItemsControl.ItemsTemplate>
</ItemsControl>