Carey's profilemoved to www.codingbandi...PhotosBlogLists Tools Help

Carey Payette

Occupation
Location
Interests
Canuck
twitter: careypayette

moved to www.codingbandit.com/blog

November 15

WPF Data Templates Part 4 - Template Selectors

*Note - Class Definition and sample data used in this example are provided in this previous blog post.

Template Selectors allow you to switch the Data Template used on an item being bound based on some logic.  For instance, in a banking application, you may wish an account that has a negative balance to be highlighted with a red background in order to draw the attention of the user.  Other positive balance accounts can be rendered using a different, milder looking template.  To implement a Template Selector, use inheritance through extending the System.Windows.Controls.DataTemplateSelector and overriding the SelectTemplate method. The signature of the method is as follows:

 
        public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
 
       

In this case, item is the actual object being bound.  You can analyze the properties of the object to determine which template to use when rendering.  In this example, the data template selector will be performing logic on the ImageFavorite class to determine which template to use.  Two data templates have been defined in resources, one being a summary view defined by the SimpleTemplate key, and a more detailed view defined by the DetailedTemplate key.   The logic of the RatedImageTemplateSelector is if the item [ImageFavorite] being bound has a 5 star rating, utilize the detailed template, otherwise have it render using the simple template.  The listing for the RatedImageTemplateSelector is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
 
namespace TemplateSelector
{
    public class RatedImageTemplateSelector : DataTemplateSelector
    {
        public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
        {
            ImageFavorite obj = item as ImageFavorite;
            ContentPresenter pres = container as ContentPresenter;
            DataTemplate dataTemplate;
 
            if (obj.ImageRating == 5)
                dataTemplate = pres.FindResource("DetailedTemplate") as DataTemplate;
            else
                dataTemplate = pres.FindResource("SimpleTemplate") as DataTemplate;
 
            return dataTemplate;
        }
    }
}
In order to utilize the template selector, you need to first declare its namespace in the window tag of the xaml file where it is being used.  In this case the namespace is TemplateSelector which resides in the current project assembly:
xmlns:local="clr-namespace:TemplateSelector"
Then define an instance of the template selector in resources:
<local:RatedImageTemplateSelector x:Key="RatedImageTemplateSelector" />
This instance of the RatedImageTemplateSelector can now be used by the list box rendering ImageFavorite items by assigning it to the ItemTemplateSelector attribute:
<ListBox x:Name="lbResults" Grid.Row="1" Grid.Column="0" Height="240"
                 HorizontalContentAlignment="Stretch" 
                 ItemsSource="{StaticResource FavoriteImages}"
                 ItemTemplateSelector="{StaticResource RatedImageTemplateSelector}" />

Screen shot of two different data templates being rendered in a list box based on logic defined in a template selector:

image

Sample code for this example can be found here

WPF Data Templates Part 3 - Switching Data Templates at Runtime

*Note - Class Definition and sample data used in this example are provided in this previous blog post.

It's a known fact that users like to have options.  Sometimes one user is more familiar with the data being displayed than another, and would to see only a summary of the data. New users of the application may prefer to see a more detailed view of the data.  WPF makes it easy to define multiple Data Templates and switch them out based on user preference.  To do this, you can define Data Templates as resources, and reference them by key to use them.  These data templates are defined in Window.Resources:

<DataTemplate x:Key="DetailedTemplate">
    <Border BorderBrush="Blue" Margin="3" Padding="3" BorderThickness="2" CornerRadius="5" Background="Beige">
        <StackPanel Orientation="Horizontal">
            <Image Margin="10" Width="250" Height="200" Stretch="Fill" Source="{Binding Path=ImageHref}">
                <Image.BitmapEffect>
                    <DropShadowBitmapEffect />
                </Image.BitmapEffect>
            </Image>
            <StackPanel Orientation="Vertical" VerticalAlignment="Center">
                <TextBlock FontSize="25" Foreground="Goldenrod" Text="{Binding Path=ImageName}" />
                <Label Content="{Binding Path=ImageRating,Converter={StaticResource RatingConverter}}" />
            </StackPanel>
        </StackPanel>
    </Border>
</DataTemplate>
 
<DataTemplate x:Key="SimpleTemplate">
    <Border BorderBrush="Blue" Margin="3" Padding="3" BorderThickness="2" CornerRadius="5" Background="Beige">
        <StackPanel HorizontalAlignment="Center">
            <Image Margin="10" Width="250" Height="200" Stretch="Fill" Source="{Binding Path=ImageHref}">
                <Image.BitmapEffect>
                    <DropShadowBitmapEffect />
                </Image.BitmapEffect>
            </Image>
        </StackPanel>
    </Border>
</DataTemplate>
You can set the default Data Template of the list box directly in XAML using its ItemTemplate property and referencing the desired template from resources identified by its key [in this case we are starting out with the summary template defined by the SimpleTemplate key in resources]:
<ListBox x:Name="lbResults" Grid.Row="1" Grid.Column="0" Height="240" 
    HorizontalContentAlignment="Stretch" ItemsSource="{StaticResource FavoriteImages}"  
    ItemTemplate="{StaticResource SimpleTemplate}" />
Next we'll add buttons to the screen so that the user can toggle the Summary and Detail view of the data being contained by the List Box.
<Button Content="Details" Margin="5,0,0,0" x:Name="btnDetail" Click="btnDetail_Click" />
<Button Content="Summary" Margin="5,0,0,0" x:Name="btnSummary" Click="btnSummary_Click" />
In the code-behind, by reacting to user actions, we can now change the Data Template of the list box by pulling the desired Data Template programmatically from resources and changing the ItemTemplate property value:
private void btnDetail_Click(object sender, RoutedEventArgs e)
{
    //pull the detailed template from resources, identified by the DetailedTemplate key
    DataTemplate detail = this.FindResource("DetailedTemplate") as DataTemplate;
    lbResults.ItemTemplate = detail;
}
 
private void btnSummary_Click(object sender, RoutedEventArgs e)
{
    //pull the summary template from resources, identified by the SimpleTemplate key
    DataTemplate summary = this.FindResource("SimpleTemplate") as DataTemplate;
    lbResults.ItemTemplate = summary;
}

Summary Template:

image

Detailed Template:

image

Full source code for this sample is available here.

November 02

WPF Data Templates Part 2 - Value Converters

Sometimes the value you want to display needs to be transformed from the original data before being bound to a XAML element. For instance, formatting a telephone number, or adding brackets to a negative balance on an account.  To accomplish this task a Value Converter is used.  A Value Converter is simply a class that implements the IValueConverter interface (located in the System.Windows.Data namespace).  This interface contains two methods, Convert, and ConvertBack.  It is not necessary to implement the ConvertBack method unless you are persisting data back to a data source and need the reverse transformation from the visual back to the persisted value.  It is important to remember that Value Converters can take in any value and return any type of object, for instance, an integer can be transformed into a string, and a string can be turned into a user control.   In the example provided below, an integer value is converted to a User Control that has the ability to display a 5 star rating.  Here is the XAML of the FiveStarRating user control which is made up of 5 paths in the shape of a star:

<UserControl x:Class="ValueConverter.FiveStarRating"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
        <StackPanel Orientation="Horizontal">
            <Path x:Name="Star1" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2"  StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
            <Path x:Name="Star2" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2"  StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
            <Path x:Name="Star3" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2"  StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
            <Path x:Name="Star4" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2"  StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
            <Path x:Name="Star5" Margin="0,15,5,0" Stroke="Black" StrokeThickness="2"  StrokeLineJoin="Round" Data="M 0,0 l 10,0 l 5,-10 l 5,10 l 10,0 l -7,10 l 2,10 l -10,-5 l -10,5 l 2,-10 Z" />
        </StackPanel>
    </Canvas>
</UserControl>

Source code for the FiveStarRating user control is as follows, contains the logic to fill the correct number of stars dependent on the Rating property of the instance:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace ValueConverter
{
    /// <summary>
    /// Interaction logic for FiveStarRating.xaml
    /// </summary>
    public partial class FiveStarRating : UserControl
    {
        public int Rating
        {
            get { return (int)this.GetValue(RatingProperty); }
            set {
                if (value > 5)
                    value = 5;
                if (value < 0)
                    value = 0;
 
                this.SetValue(RatingProperty, value); }
        }
 
        public static readonly DependencyProperty RatingProperty = DependencyProperty.Register("Rating", 
                    typeof(int),typeof(FiveStarRating),
                    new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(RatingValueChanged)
                    ));
 
        public FiveStarRating()
        {
            InitializeComponent();
            
            
        }
 
        private static void RatingValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
 
            FiveStarRating x = sender as FiveStarRating;
            int ratingValue = (int)e.NewValue;
 
            for (int i = 1; i <= 5; i++)
            {
                Path clearStar = (Path)x.FindName("Star" + i);
                clearStar.Fill = new SolidColorBrush(Colors.Gray);
            }
            for (int i = 1; i <= ratingValue; i++)
            {
              
               Path starFilled = (Path)x.FindName("Star" + i);
               starFilled.Fill = new SolidColorBrush(Colors.Goldenrod);
                
            }
         
        }
    }
   
}

The Rating property of the FiveStarRating user control has been defined as a DependencyProperty, so it is possible to bind directly to it, but in this example, we will be using this user control as the result of a value converter transformation instead.  The class definition and data being bound is defined in this previous blog post.  The value converter we will implement will convert the integer ImageRating value and transform it into a bound FiveStarRating user control.  The listing for the value converter is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
 
namespace ValueConverter
{
    public class RatingConverter : IValueConverter
    {
        #region IValueConverter Members
 
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            //in order to handle design time problems, handle null value case
            if (value == null)
                return new FiveStarRating();
           
            
            FiveStarRating uc = new FiveStarRating();
            uc.Rating = (int)value;
 
            return uc;
             
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
 
        #endregion
    }
}

In order to utilize this Value Converter during data binding, it must first declare the namespace in the window tag, in this case the namespace is ValueConverter which resides in the current project assembly:

xmlns:local="clr-namespace:ValueConverter"

Then define an instance of the value converter class in Resources:

<local:RatingConverter x:Key="RatingConverter" />
This instance of the RatingConverter can now be used in the data template.  In order to utilize the rating converter, identify the resource (identified by the key in Resources, in this case RatingConverter) and assign it to the converter property of the binding expression.
<Label Content="{Binding Path=ImageRating, Converter={StaticResource RatingConverter}}" />

This binding expression passes in the value stored in the ImageRating property of the object being bound into the Convert method of the RatingConverter value converter.  The value converter then instantiates a new FiveStarRating user control, assigns the integer value to its Rating property, and returns the user control instead of the original integer ImageRating value.  The user control is then rendered in the Label control.

image

Complete source code for this sample is available here.

November 01

WPF Data Templates Part 1 - Introduction to WPF Data Templates

Look Ma!  WPF can look like Windows Forms too!

WPF contains many similar controls that you will find in your Windows Forms toolbox.  If you really wanted your app to look and feel like Windows Forms, it is quite possible... but why?  There are other tools and techniques to provide users (and developers for that matter) with a much richer experience.  Use the best tools for the job, and for Rich Internet or Desktop applications today the answer is WPF.

Over the years, many great applications were developed using tools that were available in the day of it's imagecreation.  Cavemen used charred sticks and clay to display great masterpieces on cave walls in order to tell their story, which is in essence a way to display the data they wished to preserve.  For the tools they had at their disposal at this time, these displays of data are impressive.  The same analogy goes for Windows Forms, I am the first to agree that there are a number of very impressive forms applications out there, for the tools available in Windows Forms these applications are genius! Fast forward to today, while still prominent, Windows Forms isn't going anywhere soon, but there is a bigger player on the block, which allows designers/developers to work together to display data in a more intuitive fashion and enables a richer user experience above and beyond decoration and animation which is for some reason has been used synonymously when defining WPF.  To me the beauty of WPF lies in a very rich data binding model used in conjunction with endless possibilities in data presentation.

Concentrating on the presentation of data, this is the first post in a series that will show some of the power and flexibility behind WPF data binding and Data Templates.  Data Templates are used to define the way an object's data is to be displayed and presented to the user.  They can be composed of any number of XAML elements and give you unprecedented power in displaying your data.  The posts will contain examples of using a list box control, please keep in mind that the principles shown carry to other data display controls like the combo box and tree view controls.

Displaying a Forms-like list box in WPF

If you require a control that looks and behaves similar to a list box control in Windows Forms, it can be translated simply into using a list box control in WPF without a Data Template.  The difference being the DisplayMember property is now the DisplayMemberPath in the WPF list box control and the ValueMember property is now the SelectedValuePath.  In the sample below, ImageName and ImageId are both properties of objects being bound to the control. The data being pulled for this sample and all subsequent ones is defined in my previous blog post .

<ListBox Background="AliceBlue" x:Name="lbResults" 
  DisplayMemberPath="ImageName" 
  SelectedValuePath="ImageId" 
  ItemsSource="{StaticResource FavoriteImages}" />

image

Source code for this plain list box is available here.

A Simple Data Template

While functional, a list box displaying a simple value is not ideal.  When binding objects to a control in this way, it leaves it up to the user to deduce whether or not the selection they are choosing is in fact the object they are looking for.  In the past, sometimes the Text value is manipulated by appending values of several of the objects properties in order to give the end-user a more informed view of the object they are selecting.  A richer experience is required, and this is where Data Templates come into play.  Here is a simple example of creating a Data Template which displays multiple properties from the objects being bound, again the data being used in defined in this blog post.  Data Templates can be made up of any number of XAML elements, below you see the use of a border around the item, as well as a series of labels in a stack panel whose content (in this case will be rendered as text) is bound to individual object properties of the ImageFavorite class.  Think of Data Templates being similar to a repeater control, the template will be repeated for each item(object) being bound:

<ListBox x:Name="lbResults" HorizontalContentAlignment="Stretch" 
        ItemsSource="{StaticResource FavoriteImages}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="Blue" Margin="3" Padding="3" 
                    BorderThickness="2" CornerRadius="5">
                <StackPanel>
                    <Label Content="{Binding ImageName}" FontWeight="Bold" />
                    <Label Content="{Binding ImageRating}" />
                    <Label Content="{Binding ImageHref}" />
                </StackPanel>
            </Border>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

image

This list box clearly shows multiple properties from the objects being listed and visually separating each item with a rounded blue border.  Presenting data in this way gives the user more information about the item that they are selecting.  Here is another example of a Simple Data Template that demonstrates the use of some other XAML elements in a Data Template (If you are running the demo code, change the StartupUri window from Window1.xaml to FancierTemplate.xaml in the App.xaml file).  The template defined below defines the display of ImageFavorite data using an Image element with a drop shadow, as well as using a Text block and label control.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="60" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
 
    <DockPanel Height="35" Margin="10" Grid.Column="0" Grid.Row="0">
        <Border DockPanel.Dock="Top" Background="DarkBlue"  CornerRadius="5" >
            <TextBlock Text="My Favorite Images" Foreground="White" FontSize="20" 
                HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Border>
    </DockPanel>
 
    <ListBox x:Name="lbResults" Grid.Row="1" Grid.Column="0" Height="240" 
            HorizontalContentAlignment="Stretch" 
            ItemsSource="{StaticResource FavoriteImages}" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Border BorderBrush="Blue" Margin="3" Padding="3"
                     BorderThickness="2" CornerRadius="5" Background="Beige">
                    <StackPanel Orientation="Horizontal">
                        <Image Margin="10" Width="250" Height="200" Stretch="Fill" 
                                Source="{Binding Path=ImageHref}">
                            <Image.BitmapEffect>
                                <DropShadowBitmapEffect />
                            </Image.BitmapEffect>
                        </Image>
                        <StackPanel Orientation="Vertical" 
                                VerticalAlignment="Center">
                            <TextBlock FontSize="25" Foreground="Goldenrod" 
                                 Text="{Binding Path=ImageName}" />
                            <Label FontSize="18" 
                                 Content="{Binding Path=ImageRating}" />
                        </StackPanel>
                    </StackPanel>
                </Border>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

image

Sample code for the previous two examples is available here.

 

Blankenthoughts

Loading...Loading...