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") = TrueNow 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