Home > Back-end >  How to bind selection of combobox in itemscontrol to different list than used as itemssource in WPF
How to bind selection of combobox in itemscontrol to different list than used as itemssource in WPF

Time:01-22

this is concerning WPF and MVVM,

I have a list of items that are unsorted (e.g., "item3, item2, item5, item1, item4"). For each item, I want to display a number of comboboxes equal to the number of items (for n items there needs to be n comboboxes). The user then selects an item for each combo box, essentially sorting the list (resulting in a list of "item1, item2, Item3, item4, item5."). The sorted values are then stored in another list of items. Sorting has to be done by the user.

I have a ViewModel that contains both lists, one with the unsortedItems, the sortedList initially a copy to be the same size.

I manually created a combo box for each item, all with the same Itemssource bound to the unsorted list of items. I then bound each combo box SelectedItem with an index representing which combobox it was i.e., for the first combo box SelectedItem="{binding SortedList[0]}".

The problem with this approach is that I don't know how many items there will be. I thought an Itemscontrol would do the job here but I can't seem to figure out how to embed a combobox inside an itemscontrol so that I can use DisplayMemberPath and SelectedItem properly and not share the selected value. This is what I tried:

Item

public class Item
    {
        public Item(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; set; }
        public string Name { get; set; }
        
    }

Item Selection Part

 public class ItemSelectionPart
    {
        public ItemSelectionPart(List<Item> allItems)
        {
            this.allItems = allItems;
        }

        public List<Item> allItems { get; set; }

        public Item Selection { get; set; }


    }

ViewModel

public class ComboViewModel
    {
        public string test2 { get; set; }
        public List<Item> items { get; set; }

        public ObservableCollection<ItemSelectionPart> ItemsSelection { get; set; }

        public ComboViewModel()
        {
            test2 = "test";
            items = new List<Item>
                    {
                        new Item(100, "Entry #1"),
                        new Item(101, "Entry #2"),
                        new Item(102, "Entry #3"),
                        new Item(103, "Entry #4")

                    };

            ItemsSelection = new ObservableCollection<ItemSelectionPart> (Enumerable.Repeat(new ItemSelectionPart(items), items.Count));
        }

    }

Main Window Xaml

<Window x:Class="TestingComboBox.MainWindow"
        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:TestingComboBox"
        d:DataContext="{d:DesignInstance Type=local:ComboViewModel}"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <ItemsControl
            x:Name="level1"
            ItemsSource="{Binding ItemsSelection}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <ComboBox
                            x:Name="Combo"
                            ItemsSource="{Binding Path=allItems}"
                            DisplayMemberPath="{Binding Path=allItems.Id}"
                            SelectedValue="{Binding Path=Selection}">                           

                        </ComboBox>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</Window>

I know that displaymemberpath doesn't work - but left there for what I intended

Main Window.cs

public partial class MainWindow : Window
    {
        private ComboViewModel D;
        public MainWindow()
        {
            InitializeComponent();
            D = new ComboViewModel();
            this.DataContext = D;
        }
    }

CodePudding user response:

Issue with share is, that with Enumerable.Repeat(new ItemSelectionPart(items), items.Count) you create an enumeration with same instance new ItemSelectionPart(items). Later you use the same instance by each combobox, modifying it you impact all comboboxes.

DisplayMemberPath gets just string, not binding.

See the fix:

<ItemsControl ItemsSource="{Binding ItemsSelection}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <ComboBox
                x:Name="Combo"
                ItemsSource="{Binding Path=allItems}"
                    DisplayMemberPath="Name"
                    SelectedValue="{Binding Selection}">
                </ComboBox>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

public ComboViewModel()
{
    test2 = "test";
    items = new List<Item>
            {
                new Item(100, "Entry #1"),
                new Item(101, "Entry #2"),
                new Item(102, "Entry #3"),
                new Item(103, "Entry #4")
            };
    var selPartList = new List<ItemSelectionPart>(items.Count);
    for (int i = 0; i < items.Count; i  )
    {
        selPartList.Add(new ItemSelectionPart(items));
    }
    ItemsSelection = new ObservableCollection<ItemSelectionPart>(selPartList);
    //ItemsSelection = new ObservableCollection<ItemSelectionPart>(Enumerable.Repeat(new ItemSelectionPart(items), items.Count));
}
  • Related