WPF DataBinding issue: UserControls in DataGrid Cells not binding

1.3k views Asked by At

I am trying to build up a DataGrid control for managing a property grid. I have an abstract class for my parameter base class that parameters of various types implement. This base class contains variables that are shared across each parameter (like Group, Name, IsLocked, etc.), and then each parameter manages the information that relevant to the particular type. For example, a number parameter may use a slider for the user control, which also then uses min/max values, etc.

I have set up a class which is an Observable Collection of my base parameter, and established it as a static resource for a CollectionViewSource:

<UserControl.Resources>
    <local:Parameters x:Key="Parameters"/>
    <CollectionViewSource x:Key="cvsParameters" Source="{StaticResource Parameters}" />
</UserControl.Resources>

Then in my DataGrid, I am using style triggers to switch between user controls for each of my parameters:

<DataGrid x:Name="DataGrid_Globals"
          ItemsSource="{Binding Source={StaticResource cvsParameters}}"
          AutoGenerateColumns="False"
          CanUserAddRows="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Key" Binding="{Binding Key}" IsReadOnly="True"/>
        <DataGridTemplateColumn Width="*" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ContentControl>
                        <ContentControl.Style>
                            <Style TargetType="ContentControl">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding UIType}" Value="Text">
                                        <Setter Property="ContentTemplate">
                                            <Setter.Value>
                                                <DataTemplate>
                                                    <controls:ucText DataContext="{Binding}"/>
                                                </DataTemplate>
                                            </Setter.Value>
                                        </Setter>
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding UIType}" Value="Number">
                                        <Setter Property="ContentTemplate">
                                            <Setter.Value>
                                                <DataTemplate>
                                                    <controls:ucNumber DataContext="{Binding}"/>
                                                </DataTemplate>
                                            </Setter.Value>
                                        </Setter>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </ContentControl.Style>
                    </ContentControl>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

My user controls are set up with their data context declared in XAML:

<UserControl.DataContext>
    <local:TestNumber_cls/>
</UserControl.DataContext>
<Grid HorizontalAlignment="Stretch">
    <Slider 
        Value="{Binding NumberValue}" 
        Minimum="{Binding Min}" 
        Maximum="{Binding Max}" 
        />
</Grid>

I can't figure out how to ensure that my usercontrols bind correctly. My other column works fine, with each Key value appearing, being correctly bound and updating (for this example it's just the "Key" column, but all of the fields from the base class behave just fine.) The correct UserControls are also selected based on the UIType DataTrigger, but the UserControls just instantiate from an empty constructor and don't bind.

I have also confirmed that standalone instances of my UserControls work fine and bind correctly. For, example, it works when I set the datacontext from code behind (which I don't want to do):

this.OneOffNumberUserControl.DataContext = ((Parameters)this.ParameterGrid.Resources["Parameters"])[2];

I have also tried to remove the DataContext="{Binding}" from my user controls, but this doesn't change anything.

2

There are 2 answers

2
mechanic On BEST ANSWER

If you want to put different controls for different subclasses, specify different datatemplates, and then

<!--  subtype-specific column  -->
                            <DataGridTemplateColumn Header="">
                                <DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate>
                                        <ContentPresenter Content="{Binding}" />
                                    </DataTemplate>
                                </DataGridTemplateColumn.CellTemplate>
                            </DataGridTemplateColumn>
0
davestasiuk On

In conjunction with Mechanic's suggestion above, which greatly simplified the XAML for my property grid, through further research I also learned that I needed to implement a DependencyProperty for each subclass-dependent usercontrol. Where I set the DataTemplate for each DataType in my property grid UserControl, I set the binding to my DependencyProperty.

<UserControl.Resources>
    <local:Parameters x:Key="Parameters"/>
    <CollectionViewSource x:Key="cvsParameters" Source="{StaticResource Parameters}" />
    <DataTemplate DataType="{x:Type local:TestText_cls}">
        <controls:ucText TextObject="{Binding}"/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:TestNumber_cls}">
        <controls:ucNumber NumberObject="{Binding}"/>
    </DataTemplate>
</UserControl.Resources>
<Grid>
    <DataGrid x:Name="DataGrid_Globals"
                ItemsSource="{Binding Source={StaticResource cvsParameters}}"
                AutoGenerateColumns="False"
                CanUserAddRows="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Key" Binding="{Binding Key}" IsReadOnly="True"/>
            <DataGridTemplateColumn Width="*" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ContentPresenter Content="{Binding}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

Then in the code behind my UserControls, I implement the DependencyProperty:

public static readonly DependencyProperty TextObjectProperty =
    DependencyProperty.Register("TextObject",
        typeof(TestText_cls),
        typeof(ucText),
        new PropertyMetadata(new TestText_cls()));

[Bindable(true)]
public TestText_cls TextObject
{
    get { return (TestText_cls)this.GetValue(TextObjectProperty); }
    set { this.SetValue(TextObjectProperty, value); }
}

public ucText()
{
    InitializeComponent();
}

Finally, within each UserControl, I ensure that I have given it a name, and then set my binding to the correct variable in my DependencyProperty:

<UserControl x:Class="Testing.ucText"
             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:local="clr-namespace:Testing.Classes"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             Name="TextUI">
    <Grid HorizontalAlignment="Stretch">
        <TextBox Text="{Binding ElementName=TextUI, Path=TextObject.Value}"/>
    </Grid>
</UserControl>

I hope this helps anyone else!