| Carey's profilemoved to www.codingbandi...PhotosBlogLists | Help |
|
|
moved to www.codingbandit.com/blogNovember 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; elsedataTemplate = pres.FindResource("SimpleTemplate") as DataTemplate; return dataTemplate;} } } xmlns:local="clr-namespace:TemplateSelector" <local:RatedImageTemplateSelector x:Key="RatedImageTemplateSelector" /> <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: 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> <ListBox x:Name="lbResults" Grid.Row="1" Grid.Column="0" Height="240" HorizontalContentAlignment="Stretch" ItemsSource="{StaticResource FavoriteImages}" ItemTemplate="{StaticResource SimpleTemplate}" /> <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" /> private void btnDetail_Click(object sender, RoutedEventArgs e) { //pull the detailed template from resources, identified by the DetailedTemplate keyDataTemplate 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 keyDataTemplate summary = this.FindResource("SimpleTemplate") as DataTemplate; lbResults.ItemTemplate = summary; } Summary Template: Detailed Template: Full source code for this sample is available here. November 02 WPF Data Templates Part 2 - Value ConvertersSometimes 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 Memberspublic object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { //in order to handle design time problems, handle null value caseif (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" /> <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. Complete source code for this sample is available here. November 01 WPF Data Templates Part 1 - Introduction to WPF Data TemplatesLook 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 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 WPFIf 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}" /> Source code for this plain list box is available here. A Simple Data TemplateWhile 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> 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> Sample code for the previous two examples is available here. |
|
|||
|
|