Thursday, April 24, 2014

NamedObject Error in Converters

This post is for WPF version 4.0

There is a subtle difference in the way IValueConverter and IMultiValueConverter handles DBNull that seems to confuse a lot of people. It certainly confused me.

I was intermittently getting an error in a MultiValueConverter where it was being passed a "NamedValue" object instead of the Boolean it was expecting. I spent quite a lot of time verifying the binding definitions in the XAML before I realized it was a data issue. To make things even more confusing a very similar ValueConverter (not multi) was working just fine.

Although this problem is more obvious in VB, it also has the potential to cause unexpected behavior in C#.

My example has a simple data grid that is bound to a datatable. It uses two converters - a multi value converter for the foreground and a simple converter for the background. Here's the XAML...


<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication5"
        Title="MainWindow" Height="350" Width="525" 
        
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <local:ForegroundConverter x:Key="ForegroundConverter"></local:ForegroundConverter>
        <local:BackgroundConverter x:Key="BackgroundConverter"></local:BackgroundConverter>
    </Window.Resources>
    <Grid>
        <DataGrid Name="DG" ItemsSource="{Binding Path=DT.DefaultView}" AutoGenerateColumns="False" CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Description" Binding="{Binding Path=Description}" IsReadOnly="true">
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="Foreground">
                                <Setter.Value>
                                    <MultiBinding Converter="{StaticResource ForegroundConverter}">
                                        <Binding Path="IsError"></Binding>
                                        <Binding Path="IsWarning"></Binding>
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                            <Setter Property="Background" Value="{Binding Path=IsImportant, Converter={StaticResource BackgroundConverter}}"></Setter>
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

Here are the converter classes...


Public Class ForegroundConverter
    Implements IMultiValueConverter

    Public Function Convert(values() As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert

        Dim IsError As Boolean = values(0)
        Dim IsWarning As Boolean = values(1)

        If IsError Then
            Return New SolidColorBrush(System.Windows.Media.Colors.Red)
        End If

        If IsWarning Then
            Return New SolidColorBrush(System.Windows.Media.Colors.Blue)
        End If

        Return New SolidColorBrush(System.Windows.Media.Colors.Black)

    End Function

    Public Function ConvertBack(value As Object, targetTypes() As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class

Public Class BackgroundConverter
    Implements IValueConverter

    Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert

        If CBool(value) Then
            Return New SolidColorBrush(System.Windows.Media.Colors.Yellow)
        Else
            Return New SolidColorBrush(System.Windows.Media.Colors.White)
        End If
    End Function

    Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class


And here is the code for the MainWindow class that builds the datatable.

Imports System.Data

Class MainWindow

    Public Property DT As DataTable

    Public Sub New()

        DT = New DataTable

        DT.Columns.Add(New DataColumn("Description", Type.GetType("System.String")))
        DT.Columns.Add(New DataColumn("IsError", Type.GetType("System.Boolean")))
        DT.Columns.Add(New DataColumn("IsWarning", Type.GetType("System.Boolean")))
        DT.Columns.Add(New DataColumn("IsImportant", Type.GetType("System.Boolean")))

        Dim E As DataRow = DT.NewRow()
        E("Description") = "Error"
        E("IsError") = True
        E("IsWarning") = False
        E("IsImportant") = True
        DT.Rows.Add(E)

        Dim W As DataRow = DT.NewRow()
        W("Description") = "Warning"
        W("IsError") = False
        W("IsWarning") = True
        W("IsImportant") = False
        DT.Rows.Add(W)

        Dim I As DataRow = DT.NewRow()
        I("Description") = "Info"
        I("IsError") = False
        I("IsWarning") = False
        I("IsImportant") = True
        DT.Rows.Add(I)

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

    End Sub
End Class

If you run this project you will see a simple grid like this...

 
Now let's break things and see what happens...
 
Let's start by breaking the background converter. Comment out the code line in MainWindow that contains
E("IsImportant") = True
Now run the project again. Your BackgroundConverter will throw an exception because the value that WPF passes to it is DBNull which cannot be converted to a Boolean.

Now uncomment that line and instead comment out the line above it. This will break the ForegroundConverter which is a multi value converter. Now when you run the project the ForegroundConverter throws an exception because the type "NamedObject" cannot be converted to a Boolean.

Why does a DBNull get passed correctly to a ValueConverter but get changed to a NamedObject for a MultiValueConverter? I don't know.

No comments:

Post a Comment