0

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.

a_here_and_now
  • 177
  • 3
  • 15

1 Answers1

0

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
        });
    }
}
Kevin Walsh
  • 114
  • 8
  • Hey, this is a great answer. Thank you for this. – a_here_and_now Jan 08 '19 at 00:45
  • Can I use an observable collection to populate a composite collection to go into a combo box? I want to give a list of objects as options and then have a "None" and an "All" option at the end or beginning. I want to display key values and select object values. When I tried to make the composite collection using the dictionary like in this example https://stackoverflow.com/questions/13542072/adding-predefined-item-to-a-combobox-with-itemssource it just tells me that SelectedValuePath and DisplayMemberPath weren't found in the properties of CollectionContainer. – a_here_and_now Jan 08 '19 at 21:50
  • I updated the example to use composite collections. I also tend to not to use SelectedValuePath and DisplayMemberPath. I use SelectedItem with a two-way binding to a property in the view model (which is also in the example) and use DataTemplates to do the heavy lifting. I often use icons in a combo box list, so this approach makes it easy to do that. – Kevin Walsh Jan 09 '19 at 15:43
  • You've clearly spent a good bit of time on this answer (or maybe this is just that natural to you). I wish I could give you all the bonus points, but all I can say is thank you. So thank you for taking the time to outline this for me, Kevin. If this doesn't work the way I need to then I'll at least learn a thing or two from this. – a_here_and_now Jan 10 '19 at 01:31