Search code examples
xamldictionarycomboboxmainwindow

Update Combo Box contents with the updated dictionary contents that it is data binded to


I have a combo box who's display and value paths I have successfully bound to a dictionary.

The only problem is that the actual dictionary isn't populated by the XAML until after these boxes are layed out in the xaml above.

So the combo boxes don't show any content because the dictionaries didn't even exist when the combo boxes were made.

Is there a way to update the combo boxes after the main window is loaded so that I don't have to tinker around so much with the layout in order to get it back to looking the way I want after I've placed the combo box xaml after the dictionary objects are instantiated?

I feel like this would be the easiest way.


Solution

  • It would be great to see a sample of what you're trying to do but I'll take a crack at answering with what you've provided.

    What's needed is for the combo box to be aware that the collection it is bound to has been updated and to refresh it's visual representation when that happens. The collection itself is responsible for notifying the controls that bind to it that the collection has changed. Dictionaries do not have this ability to notify controls that bind to them.

    You should consider using an ObservableCollection. Assuming you are using a view model, that would look something like this:

    public class MyViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        private ObservableCollection<MyComboBoxItem> _myItems;
        public ObservableCollection<MyComboBoxItem> MyItems
        {
            get => _myItems;
            set { _myItems = value; OnPropertyChanged(); }
        }
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public void InitializeCollection()
        {
            MyItems = new ObservableCollection<MyComboBoxItem>();
            MyItems.Add(new MyComboBoxItem() { Text = "Hello" });
        }
    }
    

    In your XAML you would specify MyItems in your binding. The ComboBox will be updated both when the collection is created and when the collection members change.

    <ComboBox ItemsSource="{Binding MyItems}"/>
    

    This assumes that the data context of the view has been set to an instance of MyViewModel.

    Updated to support composite collection binding

    Here's a example of using a composite collection with a combo box. First, the view. Here I've created a ComboBox that binds to a composite collection. I also have a text box that shows the selected item in the combo box and two lists showing the contents of two ObservableCollections on the view model. Finally there's a button that will add a new item to one of the collections in the composite view.

    <Window x:Class="WpfApp1.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:WpfApp1"
            mc:Ignorable="d"
            Title="MainWindow"
            Height="450"
            Width="800">
    
        <Window.Resources>
            <DataTemplate x:Key="ComboBoxItemDataTemplate"
                          DataType="local:MyComboBoxItem">
    
                    <TextBlock Margin="4"
                               Text="{Binding Text}" />
            </DataTemplate>
    
        </Window.Resources>
        <Grid>
            <StackPanel VerticalAlignment="Center"
                        HorizontalAlignment="Center">
                <StackPanel Margin="4"
                            Orientation="Horizontal"
                            VerticalAlignment="Center"
                            HorizontalAlignment="Center">
                    <ComboBox Margin="4"
                              Width="220"
                              Height="28"
                              ItemsSource="{Binding ComboBoxItems}"
                              SelectedItem="{Binding SelectedComboBoxItem, Mode=TwoWay}"
                              ItemTemplate="{StaticResource ComboBoxDataTemplate}" />
                    <TextBlock Margin="4"
                               Width="220"
                               Text="{Binding SelectedComboBoxItem.Text}" />
                </StackPanel>
    
                <TextBlock Margin="4"
                           Text="Invariable Items" />
                <ListBox Margin="4"
                         ItemsSource="{Binding InvariableItems}"
                         ItemTemplate="{StaticResource ComboBoxDataTemplate}" />
                <TextBlock Margin="4"
                           Text="Variable Items" />
                <ListBox Margin="4"
                         ItemsSource="{Binding VariableItems}"
                         ItemTemplate="{StaticResource ComboBoxDataTemplate}" />
                <Button Width="80"
                        Click="OnAddVariableItem">Add</Button>
            </StackPanel>
        </Grid>
    </Window>
    

    Here's the code behind for the view. It initializes the view's DataContext in the constructor and has a click event handler for the button.

    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            var dc = new MyViewModel();
            DataContext = dc;
    
        }
    
        private void OnAddVariableItem(object sender, RoutedEventArgs e)
        {
            if (DataContext is MyViewModel viewModel)
            {
                viewModel.AddVariableItem();
            }
        }
    }
    

    Now the view model. First, the class containing items:

    public class MyComboBoxItem
    {
        public string Color { get; set; }
        public string Text { get; set; }
    }
    

    Next, the view model itself. We define two observable collections, one for things that never change and one for things that change often. Then we create a composite collection for the views. When we add a new item to the variable collection we simply re-create the composite collection.

    public class MyViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        private ObservableCollection<MyComboBoxItem> _variableItems;
        public ObservableCollection<MyComboBoxItem> VariableItems
        {
            get => _variableItems;
            set { _variableItems = value; OnPropertyChanged(); }
        }
    
        private ObservableCollection<MyComboBoxItem> _invariableItems;
        public ObservableCollection<MyComboBoxItem> InvariableItems
        {
            get => _invariableItems;
            set { _invariableItems = value; OnPropertyChanged(); }
        }
    
        private CompositeCollection _comboBoxItems;
    
        public CompositeCollection ComboBoxItems
        {
            get => _comboBoxItems;
            set { _comboBoxItems = value; OnPropertyChanged(); }
        }
    
        private MyComboBoxItem _selectedComboBoxItem;
        public MyComboBoxItem SelectedComboBoxItem
        {
            get => _selectedComboBoxItem;
            set { _selectedComboBoxItem = value; OnPropertyChanged(); }
        }
    
        public MyViewModel()
        {
            Init();
        }
    
        private void Init()
        {
            InvariableItems = new ObservableCollection<MyComboBoxItem>()
            {
                new MyComboBoxItem() {Text = "(Select One)"},
                new MyComboBoxItem() {Text = "(Default)"},
            };
    
            VariableItems = new ObservableCollection<MyComboBoxItem>()
            {
                new MyComboBoxItem() {Text = "Shoes"},
                new MyComboBoxItem() {Text = "Hat"},
                new MyComboBoxItem() {Text = "Gloves"}
            };
    
            ComboBoxItems = Create(new List<IEnumerable<MyComboBoxItem>>()
            {
                InvariableItems,
                VariableItems
            });
    
            SelectedComboBoxItem = InvariableItems[0];
        }
    
        private CompositeCollection Create(List<IEnumerable<MyComboBoxItem>> collections)
        {
            var compColl = new CompositeCollection();
            foreach (var coll in collections)
            {
                var container = new CollectionContainer()
                {
                    Collection = coll
                };
                compColl.Add(container);
            }
    
            return compColl;
        }
    
        public void AddVariableItem()
        {
            VariableItems.Add(new MyComboBoxItem() { Text = "Another item" });
            ComboBoxItems = Create(new List<IEnumerable<MyComboBoxItem>>()
            {
                InvariableItems,
                VariableItems
            });
        }
    }