Silverlight 3 – CollectionViewSource and Navigation

I am killing two birds with one stone in this post, covering both CollectionViewSource and Navigation framework in Silverlight 3.

To create new Silverlight application that uses Navigation framework, simply create new project in Visual Studio, and you will find Silverlight Navigation Application project template under Silverlight node.  Navigation framework in Silverlight three allows developers to create navigation structure that mimics the one of ASP.NET (or any web allocation for that matter).   In other words, this framework provide for URI driven navigation, using Uri routing feature similar to ASP.NET MVC framework.  Of course, this is really from end user perspective, not developer.  Obviously, Silverlight application cannot navigate from one to another XAML page using browser natively.  As a result, navigation framework utilizes browser bookmark feature in order to provide the user with Next/Previous navigation using actual browser buttons.  Routing engine enables the mapping between short Uri and actual XAML page that is supposed to handle the navigation event to this Uri.  Here is what this routing looks like:

            <navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"

                             Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">

                <navigation:Frame.UriMapper>

                    <uriMapper:UriMapper>

                        <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>

                        <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>

                        <uriMapper:UriMapping   

                           Uri="ProductDetail/{ProductId}"   

                           MappedUri="/Views/ProductDetail.xaml?ProductId={ProductId}"/>

                    </uriMapper:UriMapper>

                </navigation:Frame.UriMapper>

            </navigation:Frame>

Here is how we read this code.  The core part of navigation is Frame control.  It handles browser interaction and provide Uri mapping.  If we look at the mapper XAML, we will see that short Uri, such as “” (blank) maps to Home.Xaml.  Home.Xaml is not User Conrol, it inherits from Page control, another part of navigation framework.  Any XAML page that is supposed to handle navigation must inherit from Page.  The mapper supports tokenization as well, that is how {pageName} is mapped to /Views/{pageName}.xaml.  So, if we navigate to Uri “Home”, /Views/Home.xaml will be loaded into the frame. For example, we have HyperLinkButton as follows:

                    <HyperlinkButton x:Name="Link2" Style="{StaticResource LinkStyle}"

                                    NavigateUri="/About" TargetName="ContentFrame" Content="about"/>

In this case when user clicks on this button, he/she will be navigated to /Views/About.xaml that will be shown in Frame called ContentFrame.

Navigation framework also supports parameters, and incidentally it took me an hour to figure those out.  In the example above our ProductDetail mapping supports parameter called PrductId.  So, far pretty easy.  The hard part is to figure out how to set that Url.  In my case, I have a product object, and I am setting the Url in it to match my mapping.  Here is full source code for Product class and collection of products:

using System;

 

namespace SilverlightNavigationApp

{

    public class Product

    {

        public string ProductId { get; set; }

        public string ProductName { get; set; }

        public string ProductDescription { get; set; }

        public Uri Url { get; set; }

    }

}

using System;

using System.Collections.ObjectModel;

 

namespace SilverlightNavigationApp

{

    public class ProductList : ObservableCollection<Product>

    {

        public ProductList()

        {

            Add(new Product() { ProductId = "1", ProductName = "Bread", ProductDescription = "Wheat bread.", Url = new Uri("ProductDetail/1", UriKind.Relative) });

            Add(new Product() { ProductId = "2", ProductName = "Milk", ProductDescription = "Whole milk.", Url = new Uri("ProductDetail/2", UriKind.Relative) });

            Add(new Product() { ProductId = "3", ProductName = "Potatoes", ProductDescription = "Red potatoes.", Url = new Uri("ProductDetail/3", UriKind.Relative) });

            Add(new Product() { ProductId = "4", ProductName = "Water", ProductDescription = "Bottled water.", Url = new Uri("ProductDetail/4", UriKind.Relative) });

            Add(new Product() { ProductId = "5", ProductName = "Cake", ProductDescription = "Chocolate cake.", Url = new Uri("ProductDetail/5", UriKind.Relative) });

        }

    }

}

So far so good.  You can see how Url must be formatted to match product Id. I am showing product list inside a page using DataGrid.

<navigation:Page x:Class="SilverlightNavigationApp.Views.ProductList"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:local="clr-namespace:SilverlightNavigationApp"

   xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

   xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"

   >

    <UserControl.Resources>

 

        <CollectionViewSource Source="{StaticResource ProductList}" x:Key="ViewSource" Filter="MyFilter"/>

    </UserControl.Resources>

    <Grid x:Name="LayoutRoot">

        <Grid.RowDefinitions>

            <RowDefinition Height="Auto"/>

            <RowDefinition Height="*"/>

        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="Auto"/>

            <ColumnDefinition Width="*"/>

        </Grid.ColumnDefinitions>

        <TextBlock Text="Fitler:"/>

        <TextBox Grid.Column="1" TextChanged="TextBox_TextChanged" x:Name="FilterBox" Text=""/>

        <data:DataGrid

           ItemsSource="{Binding Source={StaticResource ViewSource}}"

           AutoGenerateColumns="False"

           Grid.Row="1"

           Grid.ColumnSpan="2">

            <data:DataGrid.Columns>

                <data:DataGridTextColumn Binding="{Binding ProductId}" Header="Product ID"/>

                <data:DataGridTextColumn Binding="{Binding ProductName}" Header="Product Name"/>

                <data:DataGridTextColumn Binding="{Binding ProductDescription}" Header="Description"/>

                <data:DataGridTemplateColumn>

                    <data:DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <Button Content="Details" Tag="{Binding Url}" Click="Button_Click"/>

                        </DataTemplate>

                    </data:DataGridTemplateColumn.CellTemplate>

                </data:DataGridTemplateColumn>

            </data:DataGrid.Columns>

        </data:DataGrid>

    </Grid>

</navigation:Page>

You can see that I am setting the tag for the button to Url property of the Product object.  Here is how I am processing this information in click event:

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            NavigationService.Navigate(((Button)sender).Tag as Uri);

        }

Here is what we can do with this in the Details page:

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)

        {

            string productId = this.NavigationContext.QueryString["ProductId"].ToString();

            SilverlightNavigationApp.ProductList list = (SilverlightNavigationApp.ProductList)App.Current.Resources["ProductList"];

            Product product = (from one in list

                               where one.ProductId == productId

                               select one).FirstOrDefault();

            this.DataContext = product;

        }

Alternatively, I can use propertied of HyperLinkButton to do the same if I do not need to use dynamic parameters.  Look above for About navigation.

Now, let’s talk about ColllectionViewSource.  It suports grouping, sorting and filtering.  In the example I will use filtering technique.  The filtering is pretty easy – we just subscribe to filter event, evaluate each object, and decide whether or not show it:

    <UserControl.Resources>

        <CollectionViewSource Source="{StaticResource ProductList}" x:Key="ViewSource" Filter="MyFilter"/>

    </UserControl.Resources>

        private void MyFilter(object sender, FilterEventArgs e)

        {

            Product product = e.Item as Product;

            if (FilterBox != null)

            {

                e.Accepted = product.ProductName.ToUpper().Contains(FilterBox.Text.ToUpper()) ||

                    product.ProductDescription.ToUpper().Contains(FilterBox.Text.ToUpper());

            }

            else

                e.Accepted = true;

        }

All that is left is to call refresh when we want to response to use actions:

        <TextBox Grid.Column="1" TextChanged="TextBox_TextChanged" x:Name="FilterBox" Text=""/>

        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)

        {

            CollectionViewSource source = this.Resources["ViewSource"] as CollectionViewSource;

            source.View.Refresh();

        }

Pretty simple.  You can download full example here.

6 Comments

  1. Place breakpoints in ProductList.xaml.cs at
    NavigationService.Navigate(((Button)sender).Tag as Uri);
    and
    Product product = e.Item as Product;

    To get to the details screen I need to click the Details button twice (usually)

    Can you explain why this is? A timing thing I assume…

  2. I just wanted to add a footer in this how can I add? Please send me ASAP.

    And I want a layout with –
    1. Header.
    2. Left Navigation (Collapsible Menu)
    3. Content Page. (It should change as per the click on Collapsible Menu item).
    4. Footer (with Current Content Page Name).
    5. One Full Screen Button.
    Can you please send me the above functionality ASAP. I have an urgent requirement. All should be in Silverlight Application only. Please Please …..

  3. I am not sure I follow where exactly you need this. I am thinking you would just need to partition your page using a grid with 3 rows and two columns. You menu would span all rows and be in column zero. Header and footer would span all columns, but be in their own rows. Content page would be in column 2 row 1.
    Thanks.

  4. Hi,

    how can we acheive Binding with CollectionViewSource?, currently your example shows up with staticresource, but i wanted to assign one property to CollectionViewsource Source property with viewModel[“CustomerList”], here viewModel[“CustomerList”] contains observable colleciton , and i need to bind this to Collectionviewsource : Source property……

    No Code behind…

Leave a Reply

Your email address will not be published. Required fields are marked *