Recommended

Skip Navigation LinksHome / Articles / View Article

Tall skinny data columns using improved WrapPanel for Silverlight

+ Add to SilverlightShow Favorites
0 comments   /   posted by Matt Perdeck on Jul 23, 2008
(0 votes)
Categories: Products , Controls , Samples , Misc

Note: This article is submitted by Matt Perdeck for Silverlight Contest: Write and Win.Thanks a lot, Matt! Hello All, Please drop a comment if you like it.

Reusable code to do narrow data columns with navigation buttons, such as address lists.

Download Source code

Contents

  • Introduction
  • Requirements
  • Installation
  • TallColumns Project
  • TallColumnsManager Project
  • Db Project
  • WrapPanel Project
  • GroupHeader Project

Introduction

When it comes to presenting data, there is a lot of emphasis on grids that present data in a single wide column, showing lots of fields horizontally.

However, some data tend to be presented in narrow columns, such as address lists and book indices. Instead of complex navigation, they have letter buttons, so you can quickly get to everybody whose name starts with "K".

And, now that lots of people use monitors that are much wider then they are high, it makes sense to put the column into a vertical WrapPanel, so it takes all the available monitor width. Less scrolling and less wasted screen real estate.

The solution I came up with has a nice (I think) page transition effect. It supports paging at the data level, to minimize network traffic. Although people have already come up with WrapPanel implementations for Silverlight, I decided to do an improved version for this project (details). I also did a very simple reusable GroupHeader element that makes it easy to insert group headers in the list of records (the red "A", "B", etc., you see in the image above).

Requirements

  • To compile the code, you need Visual Studio 2008.
  • You also need to have Microsoft Silverlight Tools Beta 2 for Visual Studio 2008 installed.

To run the code, you need to have the .NET 3.5 framework installed.

Installation

  1. Download the zip file with the source code, and unzip in a directory.
  2. Open the TallColumns.sln file in Visual Studio.
  3. The main project within the solution is TallColumns. Project TallColumns2 is a version of TallColumns that uses scroll bars. The other projects are described further below.

How to add the GroupHeader element and the improved WrapPanel to the toolbox

  1. Build the solution.
  2. Right click in the toolbox and choose "Choose Items". This opens the "Choose Toolbox Items" window.
  3. Click the Silverlight tab.
  4. Click the Browse button, and double click the WrapPanel.dll in the WrapPanel/Bin directory.
  5. Now, do the same with the GroupHeader.dll in the GroupHeader/Bin directory.
  6. Click OK to add the controls to the toolbox.

TallColumns Project

If you download the source file and open the solution, you'll find it contains a number of projects.

The TallColumns and TallColumns2 projects contain the actual XAML. TallColumns2 is the same as TallColumns, except that it uses a ScrollViewer to show that the WrapPanel used here can be used with a ScrollViewer.

By way of test data, a list of companies and their addresses is used.

Page.xaml

The Page.xaml file starts with the two Storyboards that create the page transition effect when you move from page to page with the "Next" button, "Previous" button, etc. One blurs the page, and the other makes it visible again. The code actually updates the page contents after the first storyboard has completed but before starting the second storyboard.

After the storyboard definitions comes a horizontal StackPanel with the navigation buttons. The letter buttons sit in their own ItemsControl. The letters are actually loaded from the same database object as the main records. That makes it easy to ensure that if there are no companies starting with "X", there is no "X" button either.

Finally, there is the ItemsControl that shows the actual company records. If you look at its ItemTemplate, you'll see that on top of the TextBlocks for the record fields is a GroupHeader element that at the appropriate time shows the first letter of the company name (how this works). The ItemsControl's ItemsPanel meanwhile specifies that a WrapPanel is being used to show the records (details WrapPanel).

Page.xaml.cs

When you look at Page.xaml.cs, you'll find that all the code that makes the tall columns work is in a separate class TallColumnsManager, defined in the project TallColumnsManager. This to facilitate code reuse.

TallColumnsManager project

The TallColumnsManager class contains all the code needed to manage tall columns. When you look in the TallColumnsManager.cs file, you'll see it is extensively documented.

Db Project

This project acts as the Data Access Layer. It has the Customer class that defines a customer, with company name, address, etc. The CustomerGroupCaption class defines the letters for the letter buttons. And, the CustomerAccess class exposes the methods used by the TallColumnsManager object (passed in via delegate parameters to the TallColumnsManager constructor) to retrieve the data. The methods are well documented in the source file.

Obviously, this project is just a dummy, with the data hard coded (old timers will recognize the Northwind list of customers). However, it would be easy to change the implementation so it retrieves data over the network, minimizing traffic by only retrieving a page load of records instead of all available records. This wouldn't affect the class' interface or the rest of the tall columns code.

WrapPanel Project

Improvements

This WrapPanel has a few improvements compared with the WrapPanel published by lneir:

  • Can be used inside a ScrollViewer.
  • Allows you to only show complete child elements (leaving out elements at the edges that are partly cutoff).
  • Lets you get the number of elements shown within the WrapPanel. This is useful when you try to create a paged interface with elements of different sizes.

WrapPanel Members

WrapPanel derives from Panel. It adds the following methods and properties to those provided by Panel:

Methods

   1: static WrapPanel GetWrapPanel(string name)

Given the name of a WrapPanel, returns a reference to that WrapPanel. This may be the only way to get a reference to a WrapPanel that is used as the ItemsPanel of an ItemsControl.

Example:

   1: WrapPanel wp = WrapPanel.GetWrapPanel("MainWrapPanel");

Properties

Name Type Description
Orientation Orientation Either Orientation.Horizontal or Orientation.Vertical.
ShowIncompleteChildren bool

If true, you may see incomplete child elements at the right or bottom edge of the WrapPanel.

If false, child elements that cannot be completely shown within the WrapPanel are not shown at all.

ShownChildren int Number of child elements currently shown in the WrapPanel. Only valid after the WrapPanel has loaded (Loaded event has fired).

GroupHeader Project

This is a very simple element that makes it easy to insert single letter group headers in the list of records shown with an ItemsControl.

Say, you are showing the company name and address of a list of companies, sorted by company name, in an ItemsControl:

   1: <ItemsControl  ...>
   2:     <ItemsControl.ItemTemplate>
   3:         <DataTemplate>
   4:                  <StackPanel Orientation="Vertical" >
   5:                     <TextBlock FontWeight="Bold" Text="{Binding CompanyName}"   />
   6:                     <TextBlock Text="{Binding Address}"   />
   7:                 StackPanel>
   8:          DataTemplate>
   9:     ItemsControl.ItemTemplate>
  10: ItemsControl>

With the GroupHeader element, you can then insert a TextBlock each time a company name starting with a new letter is reached. The TextBlock would show that letter, like this:

   1: A
   2: Alfreds Futterkiste
   3: Ana Trujillo Emparedados y helados
   4: Antonio Moreno Taquería
   5:  
   6: B
   7: Berglunds snabbköp
   8: Blauer See Delikatessen
   9: ...

The GroupHeader element goes above the TextBlocks with the actual record fields:

   1: <ItemsControl  .....>
   2:     <ItemsControl.ItemTemplate>
   3:         <DataTemplate>
   4:                  <StackPanel Orientation="Vertical" >
   5:                     <my:GroupHeader GroupName="Main" GroupingValue="{Binding CompanyName}">
   6:                         <my:GroupHeader.Template>
   7:                             <ControlTemplate TargetType="my:GroupHeader">
   8:                                 <TextBlock x:Name="GroupHeaderText">TextBlock>
   9:                             ControlTemplate>
  10:                         my:GroupHeader.Template>
  11:                     my:GroupHeader>
  12:                     <TextBlock FontWeight="Bold" Text="{Binding CompanyName}"   />
  13:                     <TextBlock Text="{Binding Address}"   />
  14:                 StackPanel>
  15:          DataTemplate>
  16:     ItemsControl.ItemTemplate>
  17: ItemsControl>

The GroupHeader has a template containing the TextBlock that will show the letter. Add properties to make it bold, change its color, etc.

Make sure that the TextBlock in the template has the name "GroupHeaderText".

Instead of a TextBlock, you can also use a Button or HyperlinkButton, like this:

   1: <my:GroupHeader GroupName="Main" GroupingValue="{Binding CompanyName}">
   2:     <my:GroupHeader.Template>
   3:         <ControlTemplate TargetType="my:GroupHeader">
   4:             <Button x:Name="GroupHeaderText" Click="LetterButton_Click">Button>
   5:         ControlTemplate>
   6:     my:GroupHeader.Template>
   7: my:GroupHeader>

Bind the GroupingValue property of the GroupHeader to the record field whose first letter the GroupHeader needs to look at (CompanyName, in this case).

If you have more then one ItemsControl with a GroupHeader in your application, assign a unique name to the GroupName property of each GroupHeader.

When you reload an ItemsControl with a GroupHeader (for example, by assigning a new collection of records to the DataContext of the ItemsControl), use the static GroupHeader.ResetGroup(string groupName) method to reset the group. That way, the last record processed before the reload doesn't upset the group headers after the reload.

For example:

   1: // Reset group before assigning new collection to the ItemsControl
   2: GroupHeader.ResetGroup("Main");
   3: MainItemsControl.DataContext = newCollection;
Share


Comments

Comments RSS RSS
No comments

Add Comment

 
 

   
  
  
   
Please add 8 and 7 and type the answer here:

Watch a recording of Gill Cleeren's recent SilverlightShow webinar 'Data Binding in Action'.
Sign up for the next upcoming free SilverlightShow webinar event 'Sketchflow in Real Scenarios' with presenter Braulio Diez. (hide this)