3

I want to use an User control from a different assembly as DataGridTemplateColumn. I've already looked at a lot of examples and questions, like this, this, this and this. I can't figure out why my code's not working. Here it is:

MainWindow.xaml

<Window x:Class="WpfTemplatesDemo3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:extraUserControl="clr-namespace:Templates;assembly=Templates"
    xmlns:wpfTemplatesDemo3="clr-namespace:WpfTemplatesDemo3"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <DataGrid x:Name="TableDataGrid" DataContext="{Binding   UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False" >
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Name}" Width="*"/>
            <DataGridTextColumn Binding="{Binding Age}" Width="*"/> 
            <DataGridTextColumn Binding="{Binding Description}" Width="2*"/>
            <DataGridTemplateColumn  Width="3*" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <extraUserControl:BirthDateControl BirthDayObj="{Binding BirthDay, ElementName=TableDataGrid}"   />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>
</Window>

MainWindow.xaml.cs

namespace WpfTemplatesDemo3
{
  public partial class MainWindow : Window
  {
    public ObservableCollection<Person> Persons { get;set; }

    public MainWindow()
    {
      InitializeComponent();
      this.populatePersons();
      this.TableDataGrid.ItemsSource = this.Persons;
    }

    private void populatePersons()
    {
      this.Persons = new ObservableCollection<Person>();
      Persons.Add(new Person { Age = 10, Name = "John0", BirthDay = new BirthDate(1, 2, 3) });
      Persons.Add(new Person { Age = 11, Name = "John1", BirthDay = new BirthDate(2, 3, 4) });
      Persons.Add(new Person { Age = 12, Name = "John2", BirthDay = new BirthDate(3, 4, 5) });
      Persons.Add(new Person { Age = 13, Name = "John3", BirthDay = new BirthDate(4, 5, 6) });
      Persons.Add(new Person { Age = 14, Name = "John4", BirthDay = new BirthDate(5, 6, 7) });
      Persons.Add(new Person { Age = 15, Name = "John5", BirthDay = new BirthDate(6, 7, 8) });
    }
  }
}

BirthDateControl.xaml

<UserControl x:Class="Templates.BirthDateControl"
             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" 
             mc:Ignorable="d" d:DesignWidth="300" Height="52.273"
             DataContext="{Binding RelativeSource={RelativeSource Self}}"
             >
    <Grid>
        <StackPanel>
        <Label Content="Date:"/>
            <StackPanel Orientation="Horizontal" >
                <TextBox Text="{Binding BirthDayObj.Day}" />
                <TextBox Text="{Binding BirthDayObj.Month}"/>
                <TextBox Text="{Binding BirthDayObj.Year}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>

BirthDateControl.xaml.cs

namespace Templates
{
  public partial class BirthDateControl : System.Windows.Controls.UserControl
  {
    public static DependencyProperty BirthDayObjProperty =
      DependencyProperty.Register("BirthDayObj", typeof(BirthDate), typeof(BirthDateControl));

    public BirthDate BirthDayObj
    {
      get
      {
        return ((BirthDate)GetValue(BirthDayObjProperty));
      }
      set
      {
        SetValue(BirthDayObjProperty, value);
      }
    }

    public BirthDateControl()
    {
      InitializeComponent();
    }
  }
}

Person.cs

namespace Entities
{
  public class Person : INotifyPropertyChanged
  {
    private string name;
    private int age;
    private BirthDate _birthDay;

    public string Name
    {
      get { return this.name; }
      set { 
        this.name = value;
        this.OnPropertyChanged("Name");
        this.OnPropertyChanged("Description");
      }
    }

    public int Age
    {
      get { return this.age; }
      set
      {
        this.age = value;
        this.OnPropertyChanged("Age");
        this.OnPropertyChanged("Description");
      }
    }

      public string Description
      {
        get { return Name + "_" + Age + "_" + BirthDay.Day; }
      }


    public BirthDate BirthDay
    {
      get { return this._birthDay; }
      set
      {
        this._birthDay = value;
        this.OnPropertyChanged("BirthDate");
        this.OnPropertyChanged("Description");
      }
    }


      public event PropertyChangedEventHandler PropertyChanged;

      protected void OnPropertyChanged(string propName)
      {
        if (this.PropertyChanged != null)
          this.PropertyChanged(
              this, new PropertyChangedEventArgs(propName));
      }
    }
}

BirthDate.cs

namespace Entities
{
  public class BirthDate : INotifyPropertyChanged
  {
    private int day;
    private int month;
    private int year;

    public BirthDate(int day, int month, int year)
    {
      this.day = day;
      this.month = month;
      this.year = year;
    }

    public int Day {
      get { return this.day; }
      set
      {
        this.day = value;
        this.OnPropertyChanged("day");
      }
    }

    public int Month {
      get { return this.month; }
      set
      {
        this.month = value;
        this.OnPropertyChanged("month");
      }
    }

    public int Year {
      get { return this.year; }
      set
      {
        this.year = value;
        this.OnPropertyChanged("year");
      }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propName)
    {
      if (this.PropertyChanged != null)
        this.PropertyChanged(
            this, new PropertyChangedEventArgs(propName));
    }
  }
}

If I run it, it will show the columns, but the birthdate columns are empty.

There are no errors or warnings, but this shows up in Output:

System.Windows.Data Error: 40 : BindingExpression path error: 'BirthDay' property not found on 'object' ''DataGrid' (Name='TableDataGrid')'. BindingExpression:Path=BirthDay; DataItem='DataGrid' (Name='TableDataGrid'); target element is 'BirthDateControl' (Name=''); target property is 'BirthDayObj' (type 'BirthDate')
System.Windows.Data Error: 40 : BindingExpression path error: 'BirthDay' property not found on 'object' ''DataGrid' (Name='TableDataGrid')'. BindingExpression:Path=BirthDay; DataItem='DataGrid' (Name='TableDataGrid'); target element is 'BirthDateControl' (Name=''); target property is 'BirthDayObj' (type 'BirthDate')
System.Windows.Data Error: 40 : BindingExpression path error: 'BirthDay' property not found on 'object' ''DataGrid' (Name='TableDataGrid')'. BindingExpression:Path=BirthDay; DataItem='DataGrid' (Name='TableDataGrid'); target element is 'BirthDateControl' (Name=''); target property is 'BirthDayObj' (type 'BirthDate')
System.Windows.Data Error: 40 : BindingExpression path error: 'BirthDay' property not found on 'object' ''DataGrid' (Name='TableDataGrid')'. BindingExpression:Path=BirthDay; DataItem='DataGrid' (Name='TableDataGrid'); target element is 'BirthDateControl' (Name=''); target property is 'BirthDayObj' (type 'BirthDate')
System.Windows.Data Error: 40 : BindingExpression path error: 'BirthDay' property not found on 'object' ''DataGrid' (Name='TableDataGrid')'. BindingExpression:Path=BirthDay; DataItem='DataGrid' (Name='TableDataGrid'); target element is 'BirthDateControl' (Name=''); target property is 'BirthDayObj' (type 'BirthDate')
System.Windows.Data Error: 40 : BindingExpression path error: 'BirthDay' property not found on 'object' ''DataGrid' (Name='TableDataGrid')'. BindingExpression:Path=BirthDay; DataItem='DataGrid' (Name='TableDataGrid'); target element is 'BirthDateControl' (Name=''); target property is 'BirthDayObj' (type 'BirthDate')
'WpfTemplatesDemo3.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\UIAutomationTypes\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationTypes.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
System.Windows.Data Error: 40 : BindingExpression path error: 'BirthDay' property not found on 'object' ''DataGrid' (Name='TableDataGrid')'. BindingExpression:Path=BirthDay; DataItem='DataGrid' (Name='TableDataGrid'); target element is 'BirthDateControl' (Name=''); target property is 'BirthDayObj' (type 'BirthDate')
The program '[16364] WpfTemplatesDemo3.exe: Managed (v4.0.30319)' has exited with code 0 (0x0).

I can't figure out why it won't bind the data to the user control.

Community
  • 1
  • 1
MarianC
  • 103
  • 2
  • 9

2 Answers2

3

The DataGridRow has the DataContext of Person object. The DataContext is inherited among your VisualTree, thus your UC has the same context. To make your sample work:

  1. Throw away BirthDayObjProperty from your UC.
  2. Throw away this line from your DataGrid: DataContext="{Binding UpdateSourceTrigger=PropertyChanged}"
  3. Throw away this line in your UC: DataContext="{Binding RelativeSource={RelativeSource Self}}"
  4. Your UC bindings can be as simple as:

    <Grid>
        <StackPanel>
            <Label Content="Date:"/>
            <StackPanel Orientation="Horizontal" >
                <TextBox Text="{Binding BirthDay.Day}" />
                <TextBox Text="{Binding BirthDay.Month}"/>
                <TextBox Text="{Binding BirthDay.Year}" />
            </StackPanel>
        </StackPanel>
    </Grid>
    
  5. Read more about DataContext inheritance and bindings in general, as you're obviously misusing them.

amnezjak
  • 2,011
  • 1
  • 14
  • 18
  • 4. would work but its not the intention of a usercontrol :) if someone with a viewmodel property called MyBirthday wanna bind to the uercontrol you need the DependencyProperties binding: – blindmeis May 28 '14 at 10:16
  • @blindmeis You can also set `DataContext` of the UC in `DataGridColumnTemplate` to `BirthDay` and use plain `Day` etc. bindings in UC – amnezjak May 28 '14 at 10:23
  • thats true. but if you extent the uc control to show the birthtime and this information is hold in another property then the type of Birthday then the setting of the datacontext would not work, but with DP binding its easy. BirthDateControl BirthDayObj="{Binding MyBirthday}" BirthTimeObj={Binding MyBirthtime} – blindmeis May 28 '14 at 10:29
  • Then you may pass the whole `Person` as `DataContext` object instead of creating multiple DPs. – amnezjak May 28 '14 at 10:35
  • thats not the problem, but imagine i create a Uc with a nice textblock where i can show a number. then i have a DP eg. Number. now any vm can bind to this DP no matter what property name is within the viewmodel. MyNumberControl Number="{Binding MyNumber}" or MyNumberControl Number="{Binding Age}" or MyNumberControl Number="{Binding SomeInt}". thats why i prefer DP uc solutions. – blindmeis May 28 '14 at 10:52
  • Well you're right, for such general purpose UC it's good approach :) – amnezjak May 28 '14 at 11:02
1

you have to remove the DataContext="{Binding RelativeSource={RelativeSource Self}}" within your usercontrol and use eg ElementName binding. otherwise you break the datacontext inheriting

<UserControl x:Class="Templates.BirthDateControl"
         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" 
         mc:Ignorable="d" d:DesignWidth="300" Height="52.273"
         x:Name="uc"
         >
<Grid>
    <StackPanel>
    <Label Content="Date:"/>
        <StackPanel Orientation="Horizontal" >
            <TextBox Text="{Binding ElementName=uc, Path=BirthDayObj.Day}" />
            <TextBox Text="{Binding ElementName=uc, Path=BirthDayObj.Month}"/>
            <TextBox Text="{Binding ElementName=uc, Path=BirthDayObj.Year}" />
        </StackPanel>
    </StackPanel>
</Grid>
</UserControl>

and your DataGridTemplateColumn did NOT need a"new" DataContext because the birthday property is within your person object

<DataGridTemplateColumn  Width="3*" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <extraUserControl:BirthDateControl BirthDayObj="{Binding BirthDay}"   />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
 </DataGridTemplateColumn>
blindmeis
  • 22,175
  • 7
  • 55
  • 74