Nicholas Miller
Nicholas Miller

Reputation: 4390

Binding ComboBox to DataGrid Entry

Solved problem, but still have questions. See end of post, or read on for context.

I'm trying to setup a WPF datagrid which has many templated columns with comboboxes inside of them. In particular I'm having trouble with data-binding.

Data Model
First, I have Entry, which contains 4 properties:

The most interesting property is Color which has two more sub properties:

Goal
I need to create a data grid where each row corresponds to an Entry object. The Customer, Color, and Type properties all have comboboxes that allow for a selection of dynamically populated choices. I need the selected item for each combobox to bind to the entry's respective property.

Screenshot
enter image description here


Questions:
How do I properly set the data contexts for the data grid and for each combobox?
For the data grid, I'm setting the data context programmatically to an instance of ObservableCollection.

private ObservableCollection<Entry> entries = new ObservableCollection<Entry>();

public MainWindow()
{
    InitializeComponent();

    entries.Add(new Entry("Foo", "Customer1", new MyColor("#00000000", 1), "Type1"));
    entries.Add(new Entry("Bar", "Customer2", new MyColor("#00000000", 1), "Type2"));
    LayerMapGrid.DataContext = entries; //Set data-context of datagrid
}

For the color combobox, I'm using an ObjectDataProvider in my XAML:

<Window.Resources>
    <ObjectDataProvider x:Key="Colors" ObjectType="{x:Type local:MyColor}" MethodName="GetColors"/>
</Window.Resources>

This is how I bind the ObjectDataProvider

<ComboBox ItemsSource="{Binding Source={StaticResource Colors}}"/>

In the MyColor class, I've created the method below:

public static ObservableCollection<MyColor> GetColors()
{
    ObservableCollection<MyColor> colors = new ObservableCollection<MyColor>();
    //... Fill collection... (omitted for brevity)
    return colors;
}

The good news is that ALL of the above code WORKS. However is it a good approach to the problem at hand?

This my next and more important question:
How do I bind the selected items of the comboboxes so that ObservableCollection<Entry> is updated?

I'm aware that I need to set the UpdateSourceTrigger="PropertyChanged", so that my source, which is the Entry collection is updated.

Here is my current XAML code for setting up my entire data grid. Note: I haven't implemented the customer and type comboboxes yet. I'm really just concerned with the color combobox:

<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Name="LayerMapGrid">
        <DataGrid.Columns>

            <!--Name Column-->
            <DataGridTemplateColumn Header="Name">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Label Content="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.HeaderStyle>
                    <Style TargetType="DataGridColumnHeader">
                        <Setter Property="Control.HorizontalContentAlignment" Value="Center" />
                    </Style>
                </DataGridTemplateColumn.HeaderStyle>
            </DataGridTemplateColumn>

            <!--Customer Column-->
            <DataGridTemplateColumn Header="Customer">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox SelectedItem="{Binding CustomerName, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>

            <!--Color Column-->
            <DataGridTemplateColumn Header="Color">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox ItemsSource="{Binding Source={StaticResource Colors}}" SelectedItem="{Binding Color, ElementName=LayerMapGrid, UpdateSourceTrigger=PropertyChanged}">
                            <ComboBox.ItemTemplate>
                                <DataTemplate>
                                    <DockPanel Margin="2">
                                        <Border DockPanel.Dock="Left" HorizontalAlignment="Right" BorderThickness="1" BorderBrush="Black" Margin="1" Width="10" Height="10">
                                            <Rectangle Name="ColorRec" Fill="{Binding ColorString}"/>
                                        </Border>
                                        <TextBlock Padding="4,2,2,2" Text="{Binding ColorString}" VerticalAlignment="Center"/>
                                    </DockPanel>
                                </DataTemplate>
                            </ComboBox.ItemTemplate>
                        </ComboBox>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>

            <!--Type Column-->
            <DataGridTemplateColumn Header="Type">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox SelectedItem="{Binding Type, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

Your assistance is greatly appreciated. I've been banging my head on the wall for about 16 hours with this.

-Nick Miller

EDIT:
I found the solution (and as always immediately after requesting help), but I don't understand how it works.

In the XAML, I've changed the combobox binding to be the following:

<ComboBox ItemsSource="{Binding Source={StaticResource Colors}}" SelectedItem="{Binding Color, UpdateSourceTrigger=PropertyChanged}">

What exactly is happening here?

Why is the combobox now referring to the datagrid's data context? Doesn't that get overriden when I set the ItemsSource to point to my ObjectDataProvider?

Upvotes: 4

Views: 1797

Answers (1)

Sheridan
Sheridan

Reputation: 69959

How do I properly set the data contexts for the data grid and for each combobox?

You don't. Normally in WPF, we set the DataContext property on the UserControl, or Window that we are designing, not on individual controls. In this way, all the controls in the view have access to the data properties.

How do I bind the selected items of the comboboxes so that ObservableCollection is updated?

Again, you don't do what you are doing. Rather than using an ObjectDataProvider, you should just have a property of type ObservableCollection<MyColor> in the code behind and bind to that directly. And you should be binding a public Entries property to the DataGrid.ItemsSource property, not setting the private entries field as the DataContext. You really need to read through the Data Binding Overview page on MSDN for further help with this.

Set the DataContext of MainWindow.xaml to itself (which generally is a not a good idea):

public MainWindow()
{
    InitializeComponent();

    Entries.Add(new Entry("Foo", "Customer1", new MyColor("#00000000", 1), "Type1"));
    Entries.Add(new Entry("Bar", "Customer2", new MyColor("#00000000", 1), "Type2"));
    DataContext = this; //Set DataContext to itself so you can access properties here
}

Then in MainWindow.xaml:

<DataGrid ItemsSource="{Binding Entries}" ... />

The DataTemplates in each row of the DataGrid automatically have the relevant item in the data bound collection set as the DataContext, so you automatically get access to the properties from the relevant class. Again... no need to set any DataContexts manually. If you have further questions, please read the linked article, because the answers are there.


UPDATE >>>

Let's be clear about something... I am not your personal mentor, so this will be my last reply.

How do I overcome the combobox inheriting the data context of the data grid?

To data bind to the object that is set as the Window.DataContext, you just need to use a valid Binding Path and some basic logic:

<ComboBox ItemsSource="{Binding DataContext.MyColors, RelativeSource={RelativeSource 
    AncestorType={x:Type YourLocalPrefix:MainWindow}}}" />

Upvotes: 1

Related Questions