Friday, July 4, 2014

Creating Styles and Binding Converters in code

For WPF 4.0

Recently a colleague of mine has been struggling to write generic code for assigning converters to control the background of a DataGridTextColumn. I pointed him to my April 24th 2014 blog entry entitled "NamedObject Error in Converters" which starts off by showing how to utilize a foreground and background converter in XAML for a DataGridTextColumn (and then goes on to break them).

He got that working but could not determine the code for implementing those same converters in code. While I don't normally feel the need to do something in code that can be done in XAML, I recognize that there are times when you don't know everything at design time and things have to be delayed to run time.

It really isn't too difficult to do this. You start by assigning an ElementStyle to the DataGridColumn, then creating a binding for the background and a multibinding for the foreground and binding them to the ElementStyle. The correlation between the code and the XAML it replaces is easy to see.

Here's the XAML with the style, bindings, and converters removed...


<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:WpfApplication6"
        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 x:Name="Description" Header="Description" Binding="{Binding Path=Description}" IsReadOnly="true"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>


The converters stay the same as you move from XAML to code...


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 lastly here is the code that replaces the XAML. Note that about eight lines of simple XAML has been replaced by 13 lines of difficult to read code. And for anyone who might point out that code is reusable, I would point out that styles make XAML just as reusable.

Notice I do not attempt to set the Source property of the bindings. The binding will use the ItemsSource property of the column's DataGrid which itself makes use of the DataContext of the window. If we were trying to control properties of UIElements such as a Label we would need to set the binding's Source property ie Binding.Source = DT.Rows(0).

Imports System.Data

Class MainWindow

    Public Property DT As DataTable

    Public Sub New()

        ' Create and populate the DataContext
        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")))

        DT.Rows.Add("Error", True, False, True)
        DT.Rows.Add("Warning", False, True, False)
        DT.Rows.Add("Info", False, False, True)

        InitializeComponent()

        ' Create ElementStyle
        Dim s As New Style(GetType(TextBlock))
        Description.ElementStyle = s

        ' Create background setter
        Dim bBinding As New Binding()
        Dim BackgroundConverter As Object = FindResource("BackgroundConverter")
        bBinding.Path = New System.Windows.PropertyPath("IsImportant")
        bBinding.Converter = BackgroundConverter
        s.Setters.Add(New Setter(TextBlock.BackgroundProperty, bBinding))

        ' Create Foreground setter
        Dim fBinding As New MultiBinding()
        Dim ForegroundConverter As Object = FindResource("ForegroundConverter")
        fBinding.Bindings.Add(New Binding("IsError"))
        fBinding.Bindings.Add(New Binding("IsWarning"))
        fBinding.Converter = ForegroundConverter
        s.Setters.Add(New Setter(TextBlock.ForegroundProperty, fBinding))

    End Sub
End Class


No comments:

Post a Comment