Search code examples
wpfbindingabstract-class

How to bind to different abstract class implementations with additional properties without binding failure in WPF


I implemented the classes UtilityElectricityConsumptionModel and UtilityNaturalGasConsumptionModel from the abstract class UtilityConsumptionModel. The UtilityNaturalGasConsumptionModel just has the extra ConversionCoefficient property added.

I bind to ConversionCoefficient in a DataGrid via the DataGridTemplateColumn and Trigger. I don't get any binding failure initially, but I get binding failures since I begin scrolling the DataGrid. Is there a way to avoid these binding failures?

<!--  Conversion Coefficient  -->
<DataGridTemplateColumn Width="100" Header="Conversion Coefficient">

    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>

            <TextBlock>

                <TextBlock.Style>

                    <Style TargetType="{x:Type TextBlock}">

                        <Setter Property="HorizontalAlignment" Value="Stretch" />
                        <Setter Property="Background" Value="{StaticResource PrimarySolidColorBrush}" />

                        <Style.Triggers>

                            <DataTrigger Binding="{Binding Source}" Value="{x:Static models:UtilitySource.NaturalGas}">

                                <Setter Property="Padding" Value="2,0,2,0" />
                                <Setter Property="HorizontalAlignment" Value="Right" />
                                <Setter Property="Background" Value="Transparent" />
                                <Setter Property="Text" Value="{Binding ConversionCoefficient, StringFormat='0.00'}" />

                            </DataTrigger>

                        </Style.Triggers>

                    </Style>

                </TextBlock.Style>

            </TextBlock>

        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>

</DataGridTemplateColumn>

Solution

  • After testing the "quick mock" in my other post, it occurred to me that there is an improved approach that might answer your question:

    Is there a way to avoid these binding failures?

    Use a DataTemplateSelector in place of the DataTrigger stategy.


    public class UtilityConsumptionTemplateSelector : DataTemplateSelector
    {
        public DataTemplate? ElectricityTemplate { get; set; }
        public DataTemplate? NaturalGasTemplate { get; set; }
    
        public override DataTemplate? SelectTemplate(object item, DependencyObject container)
        {
            if (item is UtilityElectricityConsumptionModel)
            {
                return ElectricityTemplate;
            }
            if (item is UtilityNaturalGasConsumptionModel)
            {
                return NaturalGasTemplate;
            }
            return base.SelectTemplate(item, container);
        }
    }
    

    <Window x:Class="abstract_class_binding_issue.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:abstract_class_binding_issue"
            xmlns:models="clr-namespace:abstract_class_binding_issue.Models"
            mc:Ignorable="d"
            Title="MainWindow" Height="250" Width="400">
        <Window.Resources>
            <SolidColorBrush x:Key="PrimarySolidColorBrush" Color="LightGray"/>
            <DataTemplate x:Key="ElectricityTemplate">
                <TextBlock Text="Electricity" Background="{StaticResource PrimarySolidColorBrush}" HorizontalAlignment="Center"/>
            </DataTemplate>
            <DataTemplate x:Key="NaturalGasTemplate">
                <TextBlock Text="{Binding ConversionCoefficient, StringFormat='0.00'}"
                       Background="Transparent" HorizontalAlignment="Right" Padding="2,0,2,0"/>
            </DataTemplate>
            <local:UtilityConsumptionTemplateSelector 
                x:Key="UtilityTemplateSelector"
                ElectricityTemplate="{StaticResource ElectricityTemplate}"
                NaturalGasTemplate="{StaticResource NaturalGasTemplate}" />
            </Window.Resources>
            <Window.DataContext>
            <models:MainViewModel/>
        </Window.DataContext>
        <Grid>
            <DataGrid ItemsSource="{Binding UtilityConsumptions}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTemplateColumn Width="100" Header="Conversion Coefficient"
                                        CellTemplateSelector="{StaticResource UtilityTemplateSelector}" />
                    <DataGridTextColumn Header="Utility Type" Binding="{Binding Source}" Width="200"/>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>