4

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:

  • Name
  • Customer
  • Color
  • Type

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

  • ColorString
  • Index

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?

Nicholas Miller
  • 4,205
  • 2
  • 39
  • 62
  • 1
    Man your question is long! If you don't hear from me again, I didn't make it through. – Sheridan Jul 14 '14 at 21:51
  • Haha, thats because I wanted to elaborate thoroughly. Anyways, I've found the problem, but have confusion with why it works. See the end of my post :) – Nicholas Miller Jul 14 '14 at 22:05

1 Answers1

1

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}}}" />
Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • Thanks for the help. Are you suggesting that I create some sort of wrapper class to hold the entire data context? You said I should set the data context of the window/user control, but you also said I shouldn't set the context to itself. I'm assuming that an instance of this wrapper class would be more suitable. (Darn, I spend half of my work day reading that MSDN page to understand what I'm doing! :P). By the way, do you know why the the my combobox's code uses two different data contexts? I guess it is because the combobox inherits the datagrid's context, and then it is overridden. – Nicholas Miller Jul 14 '14 at 23:09
  • 1
    *Darn, I spend half of my work day reading that MSDN page to understand what I'm doing!* Nothing good comes easy. *do you know why my combobox's code uses two different data contexts?* It doesn't... there is only one `DataContext` property. *Are you suggesting that I create some sort of wrapper class to hold the entire data context?* That is customary, especially when using MVVM, which is why that is so popular. Really though, it just makes sense to data bind one object to the `DataContext` that has *all* of the properties that you want to bind to in the view. – Sheridan Jul 15 '14 at 07:59
  • I've created a wrapper class which contains `ObservableCollection` and `ObservableCollection` as properties and I've set the MainWindow's data context to point to an instance of the wrapper. How do I overcome the combobox inheriting the data context of the data grid? I only need 1 instance of the color collection, and I've tried accessing it via this: `{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=MyColors}`. Using another combobox, I've been able to access the colors when the inherited data context is my wrapper class. – Nicholas Miller Jul 15 '14 at 14:50
  • Gah I did it again!...I fixed it. When I specified the source, I said that the source was `Window.MyColors` which is wrong, because `MyColors` exists in the `DataContext` property for my Window. Here is the correct code: `{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.MyColors}` – Nicholas Miller Jul 15 '14 at 15:03