ItemsControl that contains bound ComboBox in ItemTemplate (WPF MVVM with Caliburn.Micro)

108 Views Asked by At

I'm stuck with a problem to select one item in a single combo box from a list of combo boxes bound in a ItemTemplate. When I select a value in one, that value gets updated in all of them. It resembles this problem ItemsControl that contains bound ComboBox in ItemTemplate but I can't get what I'm missing. Can someone help, please?

If need more code for clarification, just ask :) Thank you!

ItemsControl:

<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto"
          VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="InputsList"
              Margin="15 10 10 5">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Components:InputUCView/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

UserControl (with combobox)

<ComboBox x:Name="PdfListCombo"
      Style="{DynamicResource conditionalComboBox}"
      Grid.Column="1" Grid.Row="0"
      Margin="2 5 5 2" Text="Select pdf"
      ItemsSource="{Binding RelativeSource={
                RelativeSource AncestorType={
                x:Type ItemsControl}},
                Path=DataContext.PdfListCombo}"
      SelectedItem="{Binding RelativeSource={
                RelativeSource AncestorType={
                x:Type ItemsControl}},
                Path=DataContext.SelectedPdfName, Mode=TwoWay}"/>

parts in question from ViewModel

private BindableCollection<InputModel> _inputsList;

public BindableCollection<InputModel> InputsList
{
   get
     {
    
       _inputsList = new(inputsList);
       return _inputsList;
     }
  set
    {
      _inputsList = value;
      NotifyOfPropertyChange(() => InputsList);
    }
  }


    private BindableCollection<string> _pdfListCombo;

    public BindableCollection<string> PdfListCombo
    {
        get
        {
            //_pdfListCombo ??= new();
            foreach (var item in inputsList)
            {
                List<string> pdfFiles = new();
                foreach (var pdf in item.PdfNames)
                {
                    pdfFiles.Add(Path.GetFileName(pdf));
                }
                _pdfListCombo=new(pdfFiles);
            }
            return _pdfListCombo;
        }
        set
        {
            _pdfListCombo = value;
            NotifyOfPropertyChange(() => PdfListCombo);
        }
    }

    private string _selectedPdfName;

    public string SelectedPdfName
    {
        get
        {
            return _selectedPdfName;
        }
        set
        {
            _selectedPdfName = value;
            NotifyOfPropertyChange(() => SelectedPdfName);
        }
    }
2

There are 2 best solutions below

9
Mr. Squirrel.Downy On BEST ANSWER

By your code

<ComboBox x:Name="PdfListCombo"
      Style="{DynamicResource conditionalComboBox}"
      Grid.Column="1" Grid.Row="0"
      Margin="2 5 5 2" Text="Select pdf"
      ItemsSource="{Binding RelativeSource={
                RelativeSource AncestorType={
                x:Type ItemsControl}},
                Path=DataContext.PdfListCombo}"
      SelectedItem="{Binding RelativeSource={
                RelativeSource AncestorType={
                x:Type ItemsControl}},
                Path=DataContext.SelectedPdfName, Mode=TwoWay}"/>

Your ComboBox binds the ItemsSource to the property PdfListCombo of the DataContext of the first found ItemsControl parent.
And ComboBox binds the SelectedItem to the property SelectedPdfName of the DataContext of the first found ItemsControl parent...........
enter image description here
TwoWays binding to same property of same object, that's caused your problem.

0
Razvan Dumitrescu On

As @Mr. Squirrel.Downy said, the issue was with the binding source being shared between all comboboxes (InputsList property) in a shared viewmodel for both main view and the usercontrol with combos. Thanks for your patience!

The correct way (at least for caliburn.micro) is to make a separate viewmodel for the usercontrol, populate a BindableCollection with it based on the number of inputs in that InputsList and bind the ItemsControl to that list.

Here is the code:

ItemsControl:

<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto"
          VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="InputVMList"
              Margin="15 10 10 5">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Components:InputUCView/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

UserControl (combos):

            <ComboBox x:Name="FillListCombo"
                  Style="{DynamicResource commonComboBox}"
                  Grid.Column="1" Grid.Row="0"
                  Margin="2 5 5 2"
                  ItemsSource="{Binding Path=FillListCombo}"
                  SelectedItem="{Binding Path=SelectedFillColor, Mode=TwoWay}"/>

            <ComboBox x:Name="PdfListCombo"
                  Style="{DynamicResource conditionalComboBox}"
                  Grid.Column="1" Grid.Row="0"
                  Margin="2 5 5 2"
                  ItemsSource="{Binding Path=PdfListCombo}"
                  DisplayMemberPath="PdfName"
                  SelectedItem="{Binding Path=SelectedPdfName, Mode=TwoWay}" />

Collection in main viewmodel:

    private BindableCollection<InputUCViewModel> _inputVMList;

    public BindableCollection<InputUCViewModel> InputVMList
{
    get
    {
        _inputVMList ??= new();
        _inputVMList.AddRange(inputsUcVMList);
        return _inputVMList;
    }
    set
    {
        _inputVMList = value;
        NotifyOfPropertyChange(() => InputVMList);
    }
}

Collection for ComboBoxes:

    private BindableCollection<PdfObjectModel> _pdfListCombo;

    public BindableCollection<PdfObjectModel> PdfListCombo
    {
        get
        {
            _pdfListCombo ??= new();
            _pdfListCombo.AddRange(_pdfObjList);
            return _pdfListCombo;
        }
        set
        {
            _pdfListCombo = value;
            NotifyOfPropertyChange(() => PdfListCombo);
        }
    }

    private PdfObjectModel _selectedPdfName;

    public PdfObjectModel SelectedPdfName
    {
        get
        {
            return _selectedPdfName;
        }
        set
        {
            _selectedPdfName = value;
            NotifyOfPropertyChange(() => SelectedPdfName);
        }
    }