4
votes

I'm having a problem understanding the basics of databinding in WPF. I have a generic DataGrid (with AutoGenerateColumns set) that is bound to a DataTable with column names that vary on every load. When the dataTable contains columns that are of type boolean, I want to render a column that contains custom images representing true and false.

To accomplish this, I have a StaticResource declared on the page for the celltemplate, and I have c# code that traps the AutoGenerateColumn event and uses this template:

<DataTemplate x:Key="CheckmarkColumnTemplate">
    <Image x:Name="CheckmarkImage" Source="..\..\images\check.png" Height="16" Width="16" />
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value}" Value="False">
            <Setter TargetName="CheckmarkImage" Property="Source" Value="..\..\images\nocheck.png" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

C# code:

private void dgData_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    if (e.PropertyType == typeof(bool))
    {
        DataGridTemplateColumn col = new DataGridTemplateColumn();
        Binding binding = new Binding(e.PropertyName);
        col.CellTemplate = (this.Resources["CheckmarkColumnTemplate"] as DataTemplate);
        col.Header = e.PropertyName;
        e.Column = col;
    }
}

This mostly works, except I've got the DataTrigger Binding property messed up. It never detects when the value of the column is "false", so it never shows the nocheck.png image. I don't know how to write the Binding property so that's referencing the databound value of the column (remember, the column name is different every time, so I can't hard-code a column name in the Path part of the binding).

Can anyone tell me what the Binding property should look like so that it just grabs the value of the column?

2

2 Answers

2
votes

I am having the same problem and still looking for an answer. My current solution is to create the DataTemplate in AutoGeneratingColumn event handler so the DataTemplate knows the property name.

private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    e.Column = new DataGridTemplateColumn
    {
        Header = e.PropertyName,
        CellTemplate = CreateSimpleCellTemplate(e.PropertyName)
    }
}

private static DataTemplate CreateSimpleCellTemplate(string propertyName)
{
    DataTemplate template = new DataTemplate();
    template.VisualTree = new FrameworkElementFactory(typeof(Label));
    template.VisualTree.SetBinding(ContentProperty, new Binding(propertyName));
    return template;
}
1
votes

I achieved the result I was after by using a different method. Instead of using a DataGridTemplateColumn, I used a DataGridCheckBoxColumn and set a custom ElementStyle based off the sample style used in the WPF Toolkit's "Hands-On Lab":

<Style x:Key="NoBorderCheckBoxStyle" TargetType="{x:Type CheckBox}">
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="Background" Value="{StaticResource CheckBoxFillNormal}"/>
    <Setter Property="BorderBrush" Value="{StaticResource CheckBoxStroke}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="FocusVisualStyle" Value="{StaticResource EmptyCheckBoxFocusVisual}"/>
    <Setter Property="HorizontalAlignment" Value="Center"/>
    <Setter Property="IsEnabled" Value="false"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CheckBox}">
                <BulletDecorator SnapsToDevicePixels="true" Background="Transparent">
                    <BulletDecorator.Bullet>
                        <Canvas x:Name="canvas"  Width="16" Height="16" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5">
                            <Image x:Name="checkImage" Source="..\..\images\check.png" Height="16" Width="16"></Image>
                        </Canvas>
                    </BulletDecorator.Bullet>
                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" RecognizesAccessKey="True"/>
                </BulletDecorator>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasContent" Value="True">
                        <Setter Property="FocusVisualStyle" Value="{StaticResource CheckRadioFocusVisual}"/>
                        <Setter Property="Padding" Value="4,0,0,0"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                    <Trigger Property="IsChecked" Value="False">
                        <Setter Property="Source" TargetName="checkImage" Value="..\..\images\nocheck.png" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

I had problems reading the "IsEnabled" property from the actual DataGrid column settings (its property is "IsReadOnly") but because my use of the DataGrid is read-only, I just set it to false here.