0

This should be fairly simple thing to achieve yet I am busting my head on this for way too long,
Why the ComboBox control will display the list of categories while the DataGridComboBoxColumn control refuse to display them when both use the same settings?

Do I have to specify cell templates for the DataGridComboBoxColumn in order to make it work or is there something I'm doing wrong here?

Steps to reproduce are simple as that:
1. create new wpf project and name it WpfApplication10
2. copy and paste the code into the MainWindow.xaml and MainWindows.xaml.cs.
3. run the project - you will notice the ComboBox shows values while the DataGridComboBoxColumn will not show values.

Example Code:

using System.Collections.ObjectModel;
using System.Windows;

namespace WpfApplication10
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public Vm TheVm;

        public MainWindow()
        {
            TheVm = new Vm
            {
                VmCategories = new ObservableCollection<VmCategory>
                {
                    new VmCategory { CategoryName="Category1" },
                    new VmCategory { CategoryName="Category2" },
                    new VmCategory { CategoryName="Category3" },
                    new VmCategory { CategoryName="Category4" },
                    new VmCategory { CategoryName="Category5" },
                    new VmCategory { CategoryName="Category6" }
                },
                VmUsers = new ObservableCollection<VmUser>
                {
                    new VmUser { Name = "Gil" },
                    new VmUser { Name = "Dan" },
                    new VmUser { Name = "John" },
                }
            };
            InitializeComponent();
            DataContext = TheVm;
        }
    }

    public class Vm
    {
        public ObservableCollection<VmCategory> VmCategories { get; set; }

        public ObservableCollection<VmUser> VmUsers { get; set; }
    }

    public class VmUser
    {
        public string Name { get; set; }
        public VmCategory VmCategoryInfo { get; set; }
    }

    public class VmCategory
    {
        public string CategoryName { get; set; }
    }
}

Example XAML:

<Window x:Class="WpfApplication10.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:WpfApplication10"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        d:DataContext="{d:DesignInstance local:Vm, IsDesignTimeCreatable=True}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <ComboBox Width="200" Height="40"
                  ItemsSource="{Binding DataContext.VmCategories, RelativeSource={RelativeSource AncestorType=Window}}"
                  DisplayMemberPath="CategoryName" />
        <DataGrid Grid.Row="1"
                  ItemsSource="{Binding VmUsers}" AutoGenerateColumns="False"
                  CanUserAddRows="True" CanUserDeleteRows="True" AreRowDetailsFrozen="False" SelectionMode="Single">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                <DataGridComboBoxColumn Header="Category" Width="120"
                                        SelectedItemBinding="{Binding VmCategoryInfo}"
                                        ItemsSource="{Binding DataContext.VmCategories, RelativeSource={RelativeSource AncestorType=Window}}"
                                        DisplayMemberPath="CategoryName"
                                        />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

Important Update
To all others in the future who came to this page due to about same issue,
If you applied the solution and it still not solving your problem - it means you have another problem which causing the same issue - that is a devil spawn unless you figure it soon - I'll explain:
In my case, in the bigger program - not the example I provided,
The DataContext I needed to address was in a TabItem Control which itself is not part of the visual tree and so it didn't work, so actually two problems were there, one is the fact the DataGridColumns are not in the visual tree and the other is that TabItem is also not in the visual tree.. And so you apply a solution but problem remains and you think something is wrong with the solution while in fact unknowingly you are dealing with two problem which result in same bug.
I ended up solving it by creating a grid which served as proxy element to retain the DataContext of the TabItem.

G.Y
  • 6,042
  • 2
  • 37
  • 54

2 Answers2

1

Since DataGridComboBoxColumn or any other supported data grid columns are not part of visual tree of datagrid so they don't inherit the DataContext of datagrid. Since, they don't lie in visual tree so any try to get DataContext using RelativeSource won't work.

Solution - You can create a proxy element to bind the data context of window; use that proxy element to bind the ItemsSource of DataGridComboBoxColumn. For ex :

<Window x:Class="WpfApplication10.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:WpfApplication10"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        d:DataContext="{d:DesignInstance local:Vm, IsDesignTimeCreatable=True}">
    <Grid>
        <Grid.Resources>
            <FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <ComboBox Width="200" Height="40"
                  ItemsSource="{Binding DataContext.VmCategories, RelativeSource={RelativeSource AncestorType=Window}}"
                  DisplayMemberPath="CategoryName" />
        <ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"></ContentControl>
        <DataGrid Grid.Row="1"
                  ItemsSource="{Binding VmUsers}" AutoGenerateColumns="False"
                  CanUserAddRows="True" CanUserDeleteRows="True" AreRowDetailsFrozen="False" SelectionMode="Single">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                <DataGridComboBoxColumn Header="Category" Width="120"
                                        SelectedItemBinding="{Binding VmCategoryInfo}"
                                        ItemsSource="{Binding DataContext.VmCategories, Source={StaticResource ProxyElement}}"
                                        DisplayMemberPath="CategoryName"
                                        />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
user1672994
  • 10,509
  • 1
  • 19
  • 32
  • Ok.. works - but why a ContentControl is needed? - I noticed that if it take it away - this won't work.. ItemsSource points to ProxyElement not the ContentControl. care to explain this please? – G.Y Oct 28 '15 at 08:50
  • 1
    The `ProxyElement` is any FrameworkElement that steals the DataContext from the main View and offers it to the logical child such as DataGridColumn. For that it must be hosted as a Content into an invisible ContentControl which is under the same View. – user1672994 Oct 30 '15 at 03:26
  • I see now what you mean.. it is very clever :) but unfortunately it is also very non-intuitive solution. next programmer or even myself would not know what is going there after few months.. thank you for your help and the valuable explanations. – G.Y Oct 30 '15 at 14:14
1
<DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
            <DataGridComboBoxColumn Header="Category" Width="120" SelectedItemBinding="{Binding VmCategoryInfo}"
                                    DisplayMemberPath="CategoryName">
                <DataGridComboBoxColumn.ElementStyle>
                    <Style TargetType="{x:Type ComboBox}">
                        <Setter Property="ItemsSource" Value="{Binding Path=DataContext.VmCategories, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
                    </Style>
                </DataGridComboBoxColumn.ElementStyle>
                <DataGridComboBoxColumn.EditingElementStyle>
                    <Style TargetType="{x:Type ComboBox}">
                        <Setter Property="ItemsSource" Value="{Binding Path=DataContext.VmCategories, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
                    </Style>
                </DataGridComboBoxColumn.EditingElementStyle>    
            </DataGridComboBoxColumn>
        </DataGrid.Columns>
G.Y
  • 6,042
  • 2
  • 37
  • 54
toumir
  • 621
  • 6
  • 17