1

I'm working on a WPF application that makes heavy use of data grids. I have a situation that comes up frequently that requires me to use a custom control within a data grid. Of course, the data being bound is different for each row.

My approach to the problem, was to make a custom control that was of the type DataGridTemplateColumn and add my own dependency properties to it. Then when I need to use this column type in my grids, I can do it in one line.

It seems like the data context of the custom control is all out of whack.

Right now, I have the following code...

DataGridCheckedComboColumn.xaml

<DataGridTemplateColumn x:Class="DataTracker.Presentation.GridControls.Views.DataGridCheckedComboColumn"
                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                        xmlns:ccb="clr-namespace:CheckedComboBoxControl;assembly=CheckedComboBox"
                        mc:Ignorable="d">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ccb:CheckedComboBox ItemsSource="{Binding Path=ComboSource}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ccb:CheckedComboBox ItemsSource="{Binding Path=ComboSource}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

And this for code behind...

DataGridCheckedComboColumn.xaml.cs

namespace DataTracker.Presentation.GridControls.Views
{
    /// <summary>
    /// Interaction logic for DataGridCheckedComboColumn.xaml
    /// </summary>
    public partial class DataGridCheckedComboColumn : DataGridTemplateColumn
    {
        public DataGridCheckedComboColumn()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Gets or sets the runway data.
        /// </summary>
        /// <value>The runway data.</value>
        public IEnumerable<Object> ComboSource
        {
            get
            {
                return (IEnumerable<Object>)this.GetValue(ComboSourceProperty);
            }
            set
            {
                this.SetValue(ComboSourceProperty, value);
            }
        }

        private static void OnComboSourceChanged(DependencyObject dependentView, DependencyPropertyChangedEventArgs e)
        {
            /////////////////////////////////////////////////////////////////
            //                                                             //
            // This is the code I used when I was making user controls and //
            // sending data to the view models                             //
            //                                                             //
            //                                                             //
            // Not sure what, if anything to do here now...                //
            //                                                             //
            /////////////////////////////////////////////////////////////////

            //var control = (DataGridComboColumn)dependentView;
            //var viewModel = (DataGridComboColumnViewModel)control.DataContext;

            //viewModel.ComboSource = (IEnumerable<Object>)e.NewValue;
        }

        public static readonly DependencyProperty ComboSourceProperty =
            DependencyProperty.Register("ComboSource",
                typeof(IEnumerable<Object>),
                typeof(DataGridCheckedComboColumn),
                new FrameworkPropertyMetadata()
                {
                    PropertyChangedCallback = OnComboSourceChanged,
                    BindsTwoWayByDefault = true
                });

    }
}

I add the custom control to my data grid like so...

<gc:DataGridCheckedComboColumn ComboSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}, Path=DataContext.DataVersions}" />

Everything renders appropriately, but no matter what data I pass through, nothing makes it. I've even hard coded lists and sent those, but no luck. I've also tried sending other simple properties like strings, but that has failed too.

WPF gives the following error. I should point out that "GenericGridObject" is what I am populating the full data grid with.

System.Windows.Data Error: 40 : BindingExpression path error: 'ComboSource' property not found on 'object' ''GenericGridObject`1' (HashCode=37030675)'. BindingExpression:Path=ComboSource; DataItem='GenericGridObject`1' (HashCode=37030675); target element is 'CheckedComboBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')

Like I said before, I think this is an issue with the data context of the custom control, but I can't seem to figure out how to force it to use its own class as the data context rather than the data context of the overall grid.

Any ideas?

imdandman
  • 393
  • 2
  • 6
  • 15
  • If you try to re-create this control to see what I'm talking about, you can just change the to and it will still have the same problem. – imdandman Aug 07 '14 at 19:58
  • I think the problem is that you are binding to the DataContext which will have no knowledge of the Dependancy property. I think you need to bind to the DataGridTemplateColumn which you can do using relative source. See http://stackoverflow.com/questions/1636807/what-exactly-does-wpf-data-bindings-relativesource-findancestor-do – daniellepelley Aug 07 '14 at 20:08
  • If I try to name the root element (DataGridTemplateColumn), and then use , I receive an error saying.... "System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myName'". Plus an error appears in VS saying... "Cannot register duplicate Name 'myName' in this scope." – imdandman Aug 07 '14 at 21:04

1 Answers1

0

I had a similar problem with the datacontext not inheriting and I solved my problem using the proxy technique described here http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

Update:

Try adding a relativesource within DataGridCheckedComboColumn

<ccb:CheckedComboBox ItemsSource="{Binding Path=ComboSource, RelativeSource={RelativeSource AncestorType=ccb:DataGridCheckedComboColumn}"

Since no Datacontext within the control is specified I'm guessing it'll take the Datacontext of its parent. If that doesn't work because the DataTemplates are not in the same logical or visual tree as the DataGridTemplateColumn then use the proxy idea such that

<DataGridTemplateColumn x:Class="DataTracker.Presentation.GridControls.Views.DataGridCheckedComboColumn"
                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                        xmlns:ccb="clr-namespace:CheckedComboBoxControl;assembly=CheckedComboBox"
                        mc:Ignorable="d">
            <DataGridTemplateColumn .Resources>
                <ccb:BindingProxy x:Key="proxy" Data="{Binding RelativeSource={RelativeSource AncestorType=ccb:DataGridCheckedComboColumn}}" />
            </DataGridTemplateColumn .Resources>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ccb:CheckedComboBox ItemsSource="{Binding Path=Data.ComboSource, Source={StaticResource proxy}}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ccb:CheckedComboBox ItemsSource="{Binding Path=Data.ComboSource, Source={StaticResource proxy}}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
user3910810
  • 234
  • 1
  • 6
  • This looks promising, but I can't seem to find a way to add resources to ONLY a column. Remember, my control is just a DataGridTemplateColumn that exists independently of the actual data grid in which it will be used. – imdandman Aug 07 '14 at 21:05
  • So each row in the datagrid can have a different itemsource for the CheckedComboBox and this itemsource is determined by datagrid itemssource DataVersions property? – user3910810 Aug 07 '14 at 21:25
  • No. Each row in the grid is populated by one item source. This in turn fills all the columns I have defined for the grid. Additionally, the checked combo box for each record will be populated with all the same data (at least in this example, some implementations will require different record sets). The problem is, the data for the checked combo box is not making it through the binding to the custom control. – imdandman Aug 07 '14 at 21:58
  • Consider this, it still illustrates the same problem. For example, REPLACE the checked combo box with a simple button. CHANGE the IEnumerable dependency property to a string type. If you try to bind that string dependency property to the content of the button in the custom control, it still breaks in the same manner. If it was string and I did , the binding still breaks and would not show up in the content of the button. Whatever data I send over the dependency properties is not making it through the binding. – imdandman Aug 07 '14 at 22:01
  • I've added an update to my answer with a couple of suggestions to try. – user3910810 Aug 07 '14 at 22:10
  • Your first solution resulted in the following error. My XAML is... ...... The error is.....System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='DataTracker.Presentation.GridControls.Views.DataGridComboColumn', AncestorLevel='1'' – imdandman Aug 08 '14 at 01:26
  • For the second attempt, with the binding proxy, I had the following issues. First - resources is not a property of the DataGridTemplateColumn. Knowing this, I decided to wrap the ComboBox in a stack panel, and set the resources of the stack panel to the binding proxy like your example..... but.....(continued in next) – imdandman Aug 08 '14 at 01:32
  • That resulted in an error of......System.Windows.Data Error: 40 : BindingExpression path error: 'ComboSource' property not found on 'object' ''RuntimeType' – imdandman Aug 08 '14 at 01:32