In earlier displays of these students, I used the property of Student called FullName
to display the student’s name. You may have noticed that the
students.xml file was actually sorted by this property, and that’s the
order in which the students appeared on the screen. Popular email
programs display your contacts sorted by first name, so I figured it
wasn’t entirely a bad thing.
But in the most recent DataTemplate, I switched to using the LastName, FirstName, and MiddleName properties, and the unsorted display now looks very strange and just plain wrong.
How can this be fixed?
One approach is through code. It’s possible for the StudentBodyPresenter
class to re-sort the data after it’s been downloaded. But you might
prefer a more flexible approach. Perhaps your application needs to
display data using different sort criteria at different times.
You can do that—and you can do it entirely in XAML—using a class called CollectionViewSource defined in the System.Windows.Data namespace. You’ll use this class in conjunction with a SortDescription class defined in the System.ComponentModel
namespace. Besides the reference and XML namespace declaration for the
ElPasoHighSchool library, you’ll need an XML namespace declaration for System.ComponentModel:
xmlns:componentmodel="clr-namespace:System.ComponentModel;assembly=System.Windows"
The whole CollectionViewSource can go in the Resources collection:
<phone:PhoneApplicationPage.Resources>
<elpaso:StudentBodyPresenter x:Key="studentBodyPresenter" />
<CollectionViewSource x:Key="sortedStudents"
Source="{Binding Source={StaticResource studentBodyPresenter},
Path=StudentBody.Students}">
<CollectionViewSource.SortDescriptions>
<componentmodel:SortDescription PropertyName="LastName"
Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</phone:PhoneApplicationPage.Resources>
Notice how the Source property of the CollectionViewSource now references the Students property of the StudentBody property of the StudentBodyPresenter. This Students property is of type ObservableCollection<Student>. The Source of CollectionViewSource must be a collection.
The SortDescription object indicates that we want to sort by the LastName property in an ascending order. Since this LastName property is of type string, no additional code need be provided to support sorting.
The Binding can now be removed from the DataContext of the Grid, and the Source property of ItemsControl can now reference the CollectionViewSource resource:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Source={StaticResource sortedStudents}}">
. . .
</ItemsControl>
</ScrollViewer>
</Grid>
And now the display looks more alphabetically comforting:
You can have multiple SortDescription objects in CollectionViewSource. Try this:
<CollectionViewSource x:Key="sortedStudents"
Source="{Binding Source={StaticResource studentBodyPresenter},
Path=StudentBody.Students}">
<CollectionViewSource.SortDescriptions>
<componentmodel:SortDescription PropertyName="Sex"
Direction="Ascending" />
<componentmodel:SortDescription PropertyName="LastName"
Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
Now all the women are first, followed by the men.
Or alternatively, perhaps you want to display the names of the male students in PowderBlue and the female students in Pink.
It’s a rather antiquated convention, to be sure, but we are dealing
with students who attended high school nearly 100 years ago! Regardless
of the propriety of pink and blue, how would you do it?
Fortunately, the Student class has a property named Sex, which is set to a text string, either “Male” or “Female.” Since we’re dealing with data bindings in the DataTemplate, the obvious solution is a data converter, and fortunately the Petzold.Phone.Silverlight library has one that seems ideal:
Example 1. Silverlight Project: Petzold.Phone.Silverlight File: SexToBrushConverter.cs
using System; using System.Globalization; using System.Windows.Data; using System.Windows.Media;
namespace Petzold.Phone.Silverlight { public class SexToBrushConverter : IValueConverter { public Brush MaleBrush { get; set; } public Brush FemaleBrush { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { string sex = value as string;
switch (sex) { case "Male": return MaleBrush; case "Female": return FemaleBrush; }
return null; }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } } }
|
Like all data converters, it derives from IValueConverter and has two methods named Convert and ConvertBack. This converter also defines two properties named MaleBrush and FemaleBrush. These properties let us avoid hard-coding brushes in the code. The Convert method is the only one that’s implemented: If the value coming in is “Male” it returns MaleBrush and if “Female” it returns FemaleBrush.
Let’s put everything into one project. The StudentBodyItemsControl project has a reference to the Petzold.Phone.Silverlight library as well as ElPasoHighSchool. The Resources section instantiates the StudentBodyPresenter, the CollectionViewSource for sorting, and the SexToBrushConverter:
Example 2. Silverlight Project: StudentBodyItemsControl File: MainPage.xaml (excerpt)
<phone:PhoneApplicationPage.Resources> <elpaso:StudentBodyPresenter x:Key="studentBodyPresenter" />
<CollectionViewSource x:Key="sortedStudents" Source="{Binding Source={StaticResource studentBodyPresenter}, Path=StudentBody.Students}"> <CollectionViewSource.SortDescriptions> <componentmodel:SortDescription PropertyName="LastName" Direction="Ascending" /> </CollectionViewSource.SortDescriptions> </CollectionViewSource>
<petzold:SexToBrushConverter x:Key="sexToBrushConverter" FemaleBrush="Pink" MaleBrush="PowderBlue" /> </phone:PhoneApplicationPage.Resources>
|
In the markup below I use five TextBlock elements to display the student’s name—LastName, FirstName, and MiddleName with a comma and a space—and at least four of them need bindings targeting the Foreground property from the Sex property of the Student object, using this SexToBrushConverter. This same binding needs to be repeated four times.
Or, perhaps we can simplify the markup just a bit by enclosing all five TextBlock elements in a ContentControl. If the Foreground property on the ContentControl is set with a single binding, then the same property will be applied to each TextBlock based on property inheritance. That’s what’s done in the following DataTemplate, which is otherwise the same as the one you just saw:
Example 3. Silverlight Project: StudentBodyItemsControl File: MainPage.xaml (excerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <ScrollViewer> <ItemsControl ItemsSource="{Binding Source={StaticResource sortedStudents}}"> <ItemsControl.ItemTemplate>
<DataTemplate> <Border BorderBrush="{StaticResource PhoneAccentBrush}" BorderThickness="1" CornerRadius="12" Margin="2"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Source="{Binding PhotoFilename}" Height="120" Width="90" Margin="6" />
<ContentControl Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="{Binding Sex, Converter={StaticResource sexToBrushConverter}}">
<StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding LastName}" /> <TextBlock Text=", " /> <TextBlock Text="{Binding FirstName}" /> <TextBlock Text=", " /> <TextBlock Text="{Binding MiddleName}" /> </StackPanel> </ContentControl>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center"> <TextBlock Text="Grade Point Average = " /> <TextBlock Text="{Binding GradePointAverage}" /> </StackPanel> </Grid> </Border> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Grid>
|
Adding the color is worth the effort, I think: