Binding not working when Custom Control is used in a DataGridTemplateColumn
My charge is to provide a control that can be used to select multiple items from a list of items. The items are classification tags. The object is a Tag. It has three properties (ID, Name, Description). Here is the object:
public class Tag : ObservableObject
{
private int tagID;
private string tagName;
private string description;
#region Properties
public int TagID
{
get => tagID;
set => SetProperty(ref tagID, value);
}
public string Name
{
get => tagName;
set => SetProperty(ref tagName, value);
}
public string Description
{
get => description;
set => SetProperty(ref description, value);
}
public Tag()
{ }
}
I have created a CustomControl called TagSelect that takes a List<tag> into a Dependency Property called SelectedTags. These are the tags that should show as selected, initially. It then builds an observable collection of all possible Tags so a user can select/deselect the relevant tags. It also manages the accuracy of the original SelectedTags list.
The problem I need help with is this. When I test this control by itself (just as an element in a Grid) it works without issue. The original goal, though, was for use in a DataGridColumn. When I built a DataGridTemplateColumn and used the TagSelect Control in the CellEditingTemplate, the DP for the SelectedTags is null. I know it is null, because it shows (== null) inside TagSelect control at a breakpoint in the OnApplyTemplate method.
I have built a small project to drive the testing of this control (TagBindExample). It can be accessed at
https://github.com/hardoverton/TagBindExample
Some relevant pieces of the Solution:
Here is how the control is used when standing alone in a grid:
<cc:TagSelect Width="175"
SelectedTags="{Binding SomeTags}" />
<cc:TagSelect Width="175"
SelectedTags="{Binding OtherTags}" />
Here is how the DataGrid is defined:
<DataGrid x:Name="TestGrid"
Grid.Row="3"
AutoGenerateColumns="False"
ItemsSource="{Binding DataEntries}"
Margin="80,0,80,0"
Grid.ColumnSpan="2">
<DataGrid.Columns>
<DataGridTextColumn Width="40"
Binding="{Binding ID}"
Header="ID" />
<DataGridTextColumn Width="200"
Binding="{Binding Name}"
Header="Name" />
<DataGridTemplateColumn Width="175"
Header="Tags">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding TagString}"
Width="175"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<cc:TagSelect Width="175"
SelectedTags="{Binding Tags}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The test data for the DataGrid is an ObservableCollection<dataentry> DataEntries. Here is the test data:
SomeTags List:
3 - Boogie
1 - Accord
OtherTags List:
5 - Christmas Gifts
6 - Data Science
7 - Envoy
DataEntries Collection:
0 - First Person, Tags:
7 - Envoy
2 - Bathroom Project
1 - Second Person, Tags:
5 - Christmas Gifts
1 - Accord
3 - Boogie
8 - Fund-House
2 - Third Person, Tags:
5 - Christmas Gifts
6 - Data Science
7 - Envoy
Here is the definition of DataEntry:
public class DataEntry : ObservableObject
{
private int id;
public int ID
{
get => id;
set => SetProperty(ref id, value);
}
private string? name;
public string? Name
{
get => name;
set => SetProperty(ref name, value);
}
private List<Tag> tags;
public List<Tag> Tags
{
get => tags;
set => SetProperty(ref tags, value);
}
private string tagString;
public string TagString
{
get => tagString;
set => SetProperty(ref tagString, value);
}
public DataEntry()
{
tags = new List<Tag>();
}
Here is the definition of the SelectedTags Dependency Property in the TagSelect control:
public List<Tag> SelectedTags
{
get { return (List<Tag>)GetValue(SelectedTagsProperty); }
set { SetValue(SelectedTagsProperty, value); }
}
public static readonly DependencyProperty SelectedTagsProperty =
DependencyProperty.Register("SelectedTags", typeof(List<Tag>), typeof(TagSelect),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
Only when used in the DataGridTemplateColumn, the DP is null. I do not understand why. For some reason it is not seeing the DataGridRow’s DataContext. The data for the cell in the record is a List<tag> just as when it is used stand-alone. I know it is null for each of the three test records because I see that in the following location for each instance of the control. There are no binding failures shown.:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (SelectedTags == null) SelectedTags = new List<Tag>(); <- break here
....
....
FullTagSet = TagData.TagList;
LoadSelections();
}
What I have tried:
I have tried all reasonable suggestions I have found in researching this problem through StackOverFlow, CodeProject, GitHub, and any other reference from search engines.
I added the TagString binding to use in the CellTemplate just to see if I was able to reference the DataGridRow properly. It works as expected.
What am I doing wrong in the CellEditingTemplate that makes me fail to
access the List<tag> Tags in each record?
Am I trying to do something a DataGridTemplateColumn will not allow?
If so, is there another approach to the requirement I should consider?