WPF: Listview with IValueConverter

This entry is part 46 of 54 in the series Learn WPF

This next example is a rather involved look at designing a ListView and using IValueConverter to show if a customer is still having its warranty on the product the customer has purchased.

After converting the warranty info to either Y or N, it will be a lot more easier for the service personal to serve the customers.

MainWindow.xaml
<Window x:Class="ListViewSpace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ListViewSpace"
        Title="MainWindow"
        Height="350" 
        Width="620" 
        Loaded="OnLoad">
    <Window.Resources>
        <!--ListView Styling Start-->
        <LinearGradientBrush x:Key="BackgroundBrush" StartPoint="0,0" EndPoint="0,1">
            <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#FF6FBDE8" Offset="0"/>
                    <GradientStop Color="#FF4385BE" Offset="1"/>
                </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>

        <LinearGradientBrush x:Key="HighlightBackgroundBrush" StartPoint="0,0" EndPoint="0,1">
            <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#FF97d3f3" Offset="0"/>
                    <GradientStop Color="#FF4385BE" Offset="1"/>
                </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>

        <LinearGradientBrush x:Key="BorderBrush" StartPoint="0,0" EndPoint="0,1">
            <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#FFAFDDF6" Offset="0"/>
                    <GradientStop Color="#FF2969AA" Offset="1"/>
                </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>

        <LinearGradientBrush x:Key="PressedBorderBrush" StartPoint="0,0" EndPoint="0,1">
            <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#FF75aac7" Offset="0"/>
                    <GradientStop Color="#FF143c65" Offset="1"/>
                </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>

        <Style x:Key="GridViewColumnHeaderGripper" TargetType="Thumb">
            <Setter Property="Width" Value="20"/>
            <Setter Property="Background" Value="#2e566b"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Thumb}">
                        <Border Padding="{TemplateBinding Padding}" Background="Transparent">
                            <Rectangle HorizontalAlignment="Center" Width="1" Fill="{TemplateBinding Background}"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style x:Key="{x:Type GridViewColumnHeader}" TargetType="GridViewColumnHeader">
            <Setter Property="HorizontalContentAlignment" Value="Center"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Foreground" Value="#FFFFFF" />
            <Setter Property="FontWeight" Value="DemiBold"/>

            <Setter Property="MinWidth" Value="40"/>
            <Setter Property="Height" Value="21"/>

            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="GridViewColumnHeader">
                        <Grid>
                            <Border Name="HeaderBorder" Padding="{TemplateBinding Padding}" BorderThickness="0,1,0,1" BorderBrush="{StaticResource BorderBrush}" Background="{StaticResource BackgroundBrush}">
                                <ContentPresenter Name="HeaderContent" Margin="0,0,0,1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            </Border>
                            <Thumb x:Name="PART_HeaderGripper" HorizontalAlignment="Right" Margin="0,0,-9,0" Style="{StaticResource GridViewColumnHeaderGripper}"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="true">
                                <Setter TargetName="HeaderBorder" Property="Background" Value="{StaticResource HighlightBackgroundBrush}"/>
                            </Trigger>
                            <Trigger Property="IsPressed" Value="true">
                                <Setter TargetName="HeaderBorder" Property="Background" Value="{StaticResource PressedBorderBrush}"/>
                                <Setter TargetName="HeaderContent" Property="Margin" Value="1,1,0,0"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Role" Value="Floating">
                    <Setter Property="Opacity" Value="0.7"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="GridViewColumnHeader">
                                <Canvas Name="PART_FloatingHeaderCanvas">
                                    <Rectangle Fill="#60000000" Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding ActualHeight}"/>
                                </Canvas>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Trigger>
                <Trigger Property="Role" Value="Padding">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="GridViewColumnHeader">
                                <Border Name="HeaderBorder" BorderThickness="0,1,0,1" BorderBrush="{StaticResource BorderBrush}" Background="{StaticResource BackgroundBrush}"/>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Trigger>
            </Style.Triggers>
        </Style>

        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalAlignment" Value="Left" />
            <Setter Property="FontSize" Value="12" />
            <Setter Property="Height" Value="18" />
            <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
        </Style>

        <Style x:Key="BorderedItem" TargetType="ListViewItem">
            <Setter Property="BorderBrush" Value="Red" />
            <Setter Property="BorderThickness" Value="2" />
        </Style>

        <!--ListView Styling End-->
    </Window.Resources>
    <Grid>
        <ListView Name="ListSales" Grid.Row="4" Grid.ColumnSpan="3" 
                  ScrollViewer.VerticalScrollBarVisibility="Auto" 
                  Height="300" 
                  Width="580"
                  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                  SelectionMode="Multiple"
                  Margin="0,6,6,0"
                  VerticalAlignment="Top">

            <ListView.Resources>
                <local:WarrantyConverter x:Key="boolConverter"/>
            </ListView.Resources>

            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Order Numner" DisplayMemberBinding="{Binding OrderNumber}" Width="100"/>
                    <GridViewColumn Header="Order Date" DisplayMemberBinding="{Binding OrderDate,StringFormat=MMM dd yyyy}" Width="100"/>
                    <GridViewColumn Header="Customer Name" DisplayMemberBinding="{Binding CustomerName}" Width="100"/>
                    <GridViewColumn Header="Address" DisplayMemberBinding="{Binding Address}" Width="190"/>
                    <GridViewColumn Header="Warranty" DisplayMemberBinding="{Binding Warranty,Converter={StaticResource boolConverter}}" Width="100"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace ListViewSpace
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnLoad(object sender, RoutedEventArgs e)
        {
            ListSales.ItemsSource = GetSalesData();
        }

        private List<SalesData> GetSalesData()
        {
            List<SalesData> listData = new List<SalesData>();

            try
            {
                Random rnd = new Random();

                String[] states = { "Texas", "California", "Ohio", "Nebraska", "New Jersey" };
                String[] names = { "Janet", "Alison", "Michael", "Dave", "Molly", "Eileen", "Samuel", "John", "Rose", "Carol", "Peter", "April", "Steven", "Bryan", "Doreen" };
                Boolean[] warr = { false, true, false, true, false, true, true, false };

                SalesData[] salesdata = new SalesData[10];

                for (int index = 0; index < salesdata.Length; index++)
                {
                    salesdata[index] = new SalesData();
                    salesdata[index].Address = String.Format("No.  {0}, {1} ", rnd.Next(1, 1000), states[rnd.Next(0, states.Length - 1)].ToString());
                    salesdata[index].CustomerName = names[rnd.Next(0, 14)].ToString();
                    salesdata[index].OrderDate = DateTime.Now.AddDays(rnd.Next(1, 30));
                    salesdata[index].OrderNumber = rnd.Next(1, 1000);
                    salesdata[index].Warranty = warr[rnd.Next(0, warr.Length - 1)];

                    listData.Add(salesdata[index]);
                }
            }
            catch (Exception)
            {
                throw;
            }
            return listData;
        }
    }

    internal class SalesData
    {
        public SalesData()
        { }

        public int OrderNumber { get; set; }
        public DateTime OrderDate { get; set; }
        public String CustomerName { get; set; }
        public String Address { get; set; }
        public Boolean Warranty { get; set; }
    }

    public class WarrantyConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            String val = String.Empty;

            if ((Boolean) value == true)
            {
                val = "Y";
                return val;
            }
            else
            {
                val = "N";
                return val;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Boolean val = false;

            if ((String) value == "Y")
            {
                val = true;
                return val;
            }
            else
            {
                val = false;
                return val;
            }
        }
    }
}

listview

If you just remove Converter={StaticResource boolConverter} in line 153 in MainWindow.xaml, the output will be as below:

listview1

 

WPF: Date conversion with IValueConverter

This entry is part 45 of 54 in the series Learn WPF

Each time when we retrieve a date from the database, it will usually be in a format that include the time.

Here, we will use IValueConverter to convert a date into the format dd-MMM-yyyy.  For example, 20-Apr-2014.

MainWindow.xaml
<Window x:Class="ConverterName.ValueConverter"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ConverterName"
        Title="IvalueConverterDemo" Height="350" Width="525">
    <Window.Resources>
        <local:DateConverter x:Key="dateconverter" />
    </Window.Resources>
    <Grid>
        <DataGrid x:Name="batch" AutoGenerateColumns="False" >
            <DataGrid.Columns>
                <DataGridTextColumn Header="Batch ID" Binding="{Binding BatchID }" />
                <DataGridTextColumn Header="Batch Name" Binding="{Binding BatchName }"/>
                <DataGridTextColumn Header="Batch Start Date" Binding="{Binding StartDate, Converter={StaticResource dateconverter}}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace ConverterName
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>

    public class Batches
    {
        public int BatchID { get; set; }
        public string BatchName { get; set; }
        public DateTime StartDate { get; set; }
    }

    public class DateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value != null)
            {
                DateTime dt = (DateTime)value;
                return dt.ToString("dd-MMM-yyyy");
            }
            return string.Empty;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new Exception();
        }
    }

    public partial class ValueConverter : Window
    {
        List<Batches> batchList = new List<Batches>();

        public ValueConverter()
        {
            InitializeComponent();

            batchList.Add(new Batches { BatchID = 1, BatchName = "Winter", StartDate = Convert.ToDateTime("2011-09-01 08:58:28.410") });
            batchList.Add(new Batches { BatchID = 2, BatchName = "Summer", StartDate = Convert.ToDateTime("2012-02-01 08:58:28.410") });
            batchList.Add(new Batches { BatchID = 3, BatchName = "Rainy", StartDate = Convert.ToDateTime("2012-07-01 08:58:28.410") });

            batch.ItemsSource = batchList;
        }
    }
}

The Convert.ToDateTime in line 46 will take in a string put them in date-time format recognisable by C# code.

It is only with this conversion that we can cast it to DateTime object in line 26.

ivalueconverter

 

WPF: A simple IValueConverter implementation

This entry is part 44 of 54 in the series Learn WPF

In real life, we have always need to convert a value from one unit to another.  For example, we might need to convert inches into meters, Fahrenheit into Celsius etc.

The IValueConverter provides a way to apply custom conversion to a binding.

Value converters are culture-aware. Both the Convert and ConvertBack methods have a culture parameter that indicates the cultural information.  Those cultural information includes the language settings, location or date and time etc of your PC.

We look at an example where typing 0 will give us a false and typing 1 will give us a true.

MainWindow.xaml
<Window x:Class="ValueConverter.Window"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:ValueConverter"
  Title="ValueConverter"
  Height="300"
  Width="300">
    <Window.Resources>
        <local:Converter x:Key="Converter" />
    </Window.Resources>
    <StackPanel Margin="10">
        <TextBox Name="tb" />
            <TextBlock Text="0:False, 1:True: " />
        <TextBlock Foreground="Blue" Text="{Binding ElementName=tb, Path=Text, Converter={StaticResource Converter}}"></TextBlock>
    </StackPanel>
</Window>

MainWindow.xaml
using System;
using System.Globalization;
using System.Windows.Data;

namespace ValueConverter
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    /// 

    public class Converter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value.ToString() == "1")
            {
                return true;
            }
            else
                return false;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if ((bool)value == true)
            {
                return "1";
            }
            else
                return "0";
        }
    }

    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

From the example above, you can see that we need to create a class Converter that implements the IValueConverter interface and then implement the Convert and ConvertBack methods.

converter