Blog
Books
Books I tech reviewed and recommend
January 22, 2010
You saw in my last post how you can filter data-virtualized items by delegating the filtering operation to the server. In this post, I will show you how you can sort data-virtualized items on the server by interacting with the DataGrid UI. The code in this post extends the code in the filtering solution from my other post, so make sure you read that first.
As a reminder, the solution I showed in my earlier post exposes a stored procedure called “GetSortedFilteredCustomers” which allows us to sort and filter a subset of customers (we use indices to specify the subset). We can indicate the sorting we want by passing the SQL sorting syntax as a string to the data provider:
string sortString = "CustomerSince DESC";
customerProvider = new CustomerProvider(this.CustomerSinceDatePicker.DateFrom, this.CustomerSinceDatePicker.DateTo, sortString);
AsyncVirtualizingCollection<Customer> customerList = new AsyncVirtualizingCollection<Customer>(customerProvider, pageSize, timePageInMemory);
this.DataContext = customerList;
In this post I will show how you can construct a sort string that reflects the user’s interactions with the DataGrid UI. Here are the behavior requirements for this app:
- Initially, the data is sorted by “CustomerSince”, in descending order. To inform the user of this fact, the “CustomerSince” DataGridColumn should display a triangle pointing down.
- If the user clicks on another column, the data should be re-queried to sort based on the clicked column in ascending order. The DataGrid UI should reflect that by displaying an upward pointing triangle in the appropriate column.
- If the user clicks on that column again, the data should now be sorted in descending order.
- If the user presses “Shift” and clicks on several columns, the items should be ordered by all those columns, in the order they were clicked.
With these requirements in mind, I decided that I was going to keep a list of sort descriptions in my app. For each column the user clicks, I want to keep the property name associated with that column, the sort direction (ascending or descending), and a pointer to the actual DataGridColumn.
private List<CustomSortDescription> sortDescriptions;
public class CustomSortDescription
{
public string PropertyName { get; set; }
public ListSortDirection Direction { get; set; }
public DataGridColumn Column { get; set; }
}
Now we need to be notified whenever the user clicks on a column for sorting. This can easily be done by listening to the “Sorting” DataGrid event.
<DataGrid x:Name="CustomersDataGrid"
Sorting="Customers_Sorting"
…
>
In the handler for this event, we will add our custom sorting logic. We will also mark it as handled to make sure that the default client-side sorting behavior of DataGrid doesn’t kick in.
private void Customers_Sorting(object sender, DataGridSortingEventArgs e)
{
this.ApplySortColumn(e.Column);
e.Handled = true;
}
Within ApplySortColumn, we modify the sortDescriptions list to reflect the user’s column clicks. If the column clicked was not in the sortDescriptions list we add it, and if it was we flip its direction. If the user is pressing Shift we keep all existing sortDescriptions, and if he’s not we remove all except the one the user just clicked on.
private void ApplySortColumn(DataGridColumn column)
{
// If column was not sorted, we sort it ascending. If it was already sorted, we flip the sort direction.
string sortColumn = this.GetColumnSortMemberPath(column);
CustomSortDescription existingSortDescription = this.sortDescriptions.SingleOrDefault(sd => sd.PropertyName == sortColumn);
if (existingSortDescription == null)
{
existingSortDescription = new CustomSortDescription
{
PropertyName = sortColumn,
Direction = ListSortDirection.Ascending,
Column = column
};
this.sortDescriptions.Add(existingSortDescription);
}
else
{
existingSortDescription.Direction = (existingSortDescription.Direction == ListSortDirection.Ascending) ? ListSortDirection.Descending : ListSortDirection.Ascending;
}
// If user is not pressing Shift, we remove all SortDescriptions except the current one.
bool isShiftPressed = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
if (!isShiftPressed)
{
for (int i = this.sortDescriptions.Count – 1; i >= 0; i–)
{
CustomSortDescription csd = this.sortDescriptions[i];
if (csd.PropertyName != sortColumn)
{
this.sortDescriptions.RemoveAt(i);
}
}
}
this.RefreshData();
}
The GetColumnSortMemberPath method returns the path of the binding associated with a particular DataGridColumn. The code (shown below) relies on the GetSortMemberPath method in DataGridHelper, which was taken from an earlier blog post by Vincent Sibal. Because each of our actual items is held in a DataWrapper instance (for purposes of data virtualization), all customer properties are accessed through the Data property of DataWrapper. Since the SQL server doesn’t have data wrappers, we don’t want the “Data.” portion of the property path when we build the SQL sorting query; we remove it from the path here.
private string GetColumnSortMemberPath(DataGridColumn column)
{
string prefixToRemove = "Data.";
string fullSortColumn = DataGridHelper.GetSortMemberPath(column);
string sortColumn = fullSortColumn.Substring(prefixToRemove.Length);
return sortColumn;
}
Notice that after changing the sortDescriptions list in the ApplySortColumn method, we call the RefreshData method. RefreshData gets the portion of the SQL query required for sorting based on the sortDescriptions list, and passes that to the CustomerProvider to be executed on the server. Then it updates the page’s data context to be the newly created CustomerProvider and ensures that the DataGrid columns display the triangles in each column that reflect the sort descriptions.
private void RefreshData()
{
string sortString = this.GetCurrentSortString();
customerProvider = new CustomerProvider(this.CustomerSinceDatePicker.DateFrom, this.CustomerSinceDatePicker.DateTo, sortString);
AsyncVirtualizingCollection<Customer> customerList = new AsyncVirtualizingCollection<Customer>(customerProvider, pageSize, timePageInMemory);
this.DataContext = customerList;
this.UpdateSortingVisualFeedback();
this.CustomersDataGrid.SelectedIndex = 0;
}
As you can see below, GetCurrentSortString enumerates through the sortDescriptions list and creates a string with the SQL syntax that reflects that sorting information. For example, if I click on “First Name” twice, and then shift-click on “Local Calls”, the string returned by this method would be “FirstName DESC, LocalCalls”.
private string GetCurrentSortString()
{
// The result string is created, taking into account all sorted columns in the order they were sorted.
StringBuilder result = new StringBuilder();
string separator = String.Empty;
foreach (CustomSortDescription sd in this.sortDescriptions)
{
result.Append(separator);
result.Append(sd.PropertyName);
if (sd.Direction == ListSortDirection.Descending)
{
result = result.Append(" DESC");
}
separator = ", ";
}
return result.ToString();
}
Updating the UI to reflect the sortDescriptions list is very easy:
private void UpdateSortingVisualFeedback()
{
foreach (CustomSortDescription csd in this.sortDescriptions)
{
csd.Column.SortDirection = csd.Direction;
}
}
Finally, we need to think of the initial sorting state of the app. We would like the customers to be displayed in descending order of their “CustomerSince” dates. To make that happen, we can add the following code to the window’s constructor:
public MainWindow()
{
…
string defaultSortColumnName = "CustomerSince";
DataGridColumn defaultSortColumn = this.CustomersDataGrid.Columns.Single(dgc => this.GetColumnSortMemberPath(dgc) == defaultSortColumnName);
this.sortDescriptions = new List<CustomSortDescription>
{
new CustomSortDescription
{
PropertyName = defaultSortColumnName,
Direction = ListSortDirection.Descending,
Column = defaultSortColumn
}
};
this.RefreshData();
}
With this code in place, clicking on columns in the DataGrid now causes the corresponding sorting operations to be performed on the server side. This works well in combination with data virtualization. You can see a screenshot of this app below. Notice the triangles in the columns.

If you’re a SQL wizard, I’d love to get your feedback on the stored procedures used in this sample, in particular GetSortedFilteredCustomers. I am by no means a SQL expert and would like to have the current syntax validated or simplified if possible.
Download the database initialization project where you can find the stored procedures (built with .NET 4.0 Beta 2).
Download the data virtualization project with filtering and sorting (built with .NET 4.0 Beta 2).
Posted by Bea under WPF | Comments (2)
December 2, 2009

Zag Studio’s web site is now online! I plan to keep expanding the site with information that my clients and prospective clients find useful, so if there’s anything you would like to see there, let me know.
The Silverlight banner in the main page was a fun side project I did during the refreshingly inspiring Siggraph conference earlier this year in New Orleans. You can replay the animation by clicking on the banner again.
The idea was to create a Silverlight animation that included fractal geometry. A fractal is an object that is similar to itself at all scales, like the branches in my animated trees. Each tree is a user control with properties to specify the thickness of the tree trunk, the length of each branch, the number of branching levels, and the number of levels bearing fruit. From those parameters, the user control creates a branching pattern with random variations. There’s also a property that specifies a seed for the random number generation, so that the trees look consistent every time the application runs.
For inspiration on generating pictures using grammars, I found this book a good read: Grammatical Picture Generation: A Tree-Based Approach
.
Posted by Bea under General | Comments (5)
November 27, 2009
A few months ago, I wrote about a data virtualization solution that combines many of the advantages of two other data virtualization solutions (Paul’s and Vincent’s). Today’s post extends the data virtualization solution in my earlier post by adding the ability to filter the virtualized data. My next post will extend today’s solution further by adding the ability to sort the data, in a way that is seamlessly integrated with the DataGrid UI.
Filtering data virtualized items
WPF offers users the ability to filter data (one way to do that is by using CollectionViewSource, which I discussed in another post in the context of WPF). However, when you filter using the client-side solutions that ship in WPF, all data items need to be accessed. This is fine if you don’t have a lot of items, but for large data sets, it will negate any benefits of virtualization. The trick to get around this problem is to delegate all filtering operations to the server. Today’s sample will show you how to do that.
The scenario for the sample is a list of customers that needs to be filtered by two dates. The list of customers is displayed in a DataGrid, and among its many columns, it displays the date the customer joined a particular mobile phone plan. The user is able to enter two dates, thereby applying a filter that displays only the customers that joined between those two dates.
Run the sample on your machine
Today’s post requires you to have SQL server (or SQL Express) installed. I used VS 2010 Beta 2 to create this project, so you will need to have that installed to open the project. The only new features of WPF 4.0 Beta 2 that are used here were available previously in the toolkit (DataGrid and DatePicker), so you can easily port this project to an older version of VS and .NET if needed. There is no need to have any sample database installed, since we will build our own.
You can download the WPF project that I typically use to create sample databases (for presentations and demo code). Open it in VS 2010, search for the “connectionString” variable in MainWindow.xaml.cs, and replace the name of my sql server instance (SQLEXPRESS) with your own. Then run it and click on the “Initialize Database” button to create the database. This will take a while, so please be patient. When the initialization is complete, you should see “Database has been initialized” displayed in the UI. You can confirm the database’s creation by opening “SQL Server Management Studio” and making sure that the “Customers” database is listed under “Databases”.
You are now ready to open today’s app in VS 2010. Search the solution for “SQLEXPRESS” to find all connection strings and replace them with your own. You should find these in the Customers.dbml, Properties\Settings.settings, and Properties\Settings.Designer.cs pages. You should now be able to press F5 and run the sample.
Data virtualization implementation
In “SQL Server Management Studio”, open the “Customers” database. You will notice that it creates one table – “Customers” – and two stored procedures – “GetCount” and “GetSortedFilteredCustomers” (look under Programmability \ Stored Procedures). “GetCount” returns the number of customers that joined between two specified dates. “GetSortedFilteredCustomers” returns a sorted subset of customers that joined between two specified dates (the subset is defined by specifying the begin and end indices of the desired items). Even though this post won’t focus on sorting, you will see how to pass a simple sorting string to this stored procedure (my next post will cover sorting in depth).
Once the database is in place, the next step is to query it from the client app, which I decided to do using Linq to SQL. If you want to recreate the project, add a new item to the project of type “Linq to SQL classes” and call it “Customers.dbml”. Then open “Server Explorer”, right click “Data connections”, specify your server name, and select the “Customers” database from the drop down. Then expand the data connection until you find the “Customer” table and drag it to the left section of the Linq To SQL’s designer. Similarly, expand the stored procedures section and drag both stored procedures to the right section of the designer.
Once you get to this point, build and inspect the Customers.designer.cs class. You will notice that dragging the Customers table caused a “Customer” class to be generated containing a property for each column in the corresponding table. You will also notice that dragging the stored procedures caused two methods with the same names to be generated. The “GetCount” method has a return type of ISingleResult<GetCountResult> – at the bottom of that file you can see that the GetCountResult class contains one single “Count” property of type int. The “GetSortedFilteredCustomers” method has a return type of int. This stored procedure selects a list of customers, so the return type is not quite right. This is because this stored procedure uses an EXEC to run a SELECT statement created at runtime (more on that in my next post). Replacing the int return type with ISingleResult<Customer> corrects the problem; after that, you need to rebuild the project.
At this point, you are able to execute the stored procedures added to the database from the client app. It’s time to include the data virtualization files from my previous post in this project. You can find the data independent and reusable virtualization files in a separate dll called “DataVirtualization.dll”. The next step is to implement the class that derives from IItemsProvider (which I decided to call CustomerProvider in this project). As a reminder, IItemsProvider requires the implementation of two methods: FetchCount and FetchRange. FetchCount calls the “GetCount” Linq method and stored procedure:
public int FetchCount()
{
CustomersDataContext customersDataContext = new CustomersDataContext();
count = customersDataContext.GetCount(dateFrom, dateTo).ToList().First().Count.Value;
return count;
}
And FetchRange calls the GetSortedFilteredCustomers Linq method and stored procedure:
public IList<Customer> FetchRange(int startIndex, int pageCount, out int overallCount)
{
CustomersDataContext customersDataContext = new CustomersDataContext();
IList<Customer> customersResult;
startIndex = startIndex + 1; // SQL index starts at 1.
int endIndex = startIndex + pageCount – 1; // GetCustomers returns items with indices startIndex through endIndex, inclusive.
overallCount = count; // In this case it’s ok not to get the count again because we’re assuming the data in the database is not changing.
customersResult = customersDataContext.GetSortedFilteredCustomers(startIndex, endIndex, this.dateFrom, this.dateTo, this.sortField).ToList();
return customersResult;
}
The data virtualization files are all complete. Now we can implement the user interface that uses them.
User interface – DataGrid and DateRangePicker
The first step is to add a DataGrid to MainWindow.xaml. I modified the styles of the actual DataGrid and of each DataGridRow to display feedback to the user when items are being accessed from the database (similar to what I explained in the “IsInitializing + IsLoading” section of my previous post). The XAML for the DataGrid is simple and there is plenty of information online explaining the different properties, so I won’t go into detail about it here.
The next step is to implement the UI for the user to specify the dates to filter by. I decided to group this functionality in a UserControl which I called “DateRangePicker”. In this UserControl, I added two DatePicker controls, one to enter the “from” date and another one to enter the “to” date.
<TextBlock Text="From:" />
<controls:DatePicker x:Name="DatePickerFrom" />
<TextBlock Text="To:" />
<controls:DatePicker x:Name="DatePickerTo" />
If the user specifies both dates, I have the information I need to find customers with “CustomerSince” date between those dates. I also want to allow finding customers that joined after a certain date or before a certain date, so I need to handle the scenario where only one date is specified. And if the user doesn’t specify any filter dates, all items should be returned.
With these requirements in mind, I decided to expose two properties in DateRangePicker, “DateFrom” and “DateTo”, of type Nullable<DateTime> (null meaning that the customer did not pick a date for that field). Then I two-way data bound these properties and the selected dates of the DatePickers added to the content of this control:
this.DatePickerFrom.SetBinding(DatePicker.SelectedDateProperty, new Binding("DateFrom") { Source = this, Mode = BindingMode.TwoWay });
this.DatePickerTo.SetBinding(DatePicker.SelectedDateProperty, new Binding("DateTo") { Source = this, Mode = BindingMode.TwoWay });
Since my app requires both dates to be in the past, I decided to help the user by preventing selection of dates in the future, which can be done with the following code:
this.DatePickerFrom.BlackoutDates.Add(new CalendarDateRange(DateTime.Today.AddDays(1), DateTime.MaxValue));
this.DatePickerTo.BlackoutDates.Add(new CalendarDateRange(DateTime.Today.AddDays(1), DateTime.MaxValue));
I also decided to expose events that are fired when the “from” and “to” dates are changed:
public event EventHandler FromDateChanged;
public event EventHandler ToDateChanged;
Then I added change handlers for the “from” and “to” properties that I use to fire the events. In addition, if the “from” date is set to be after the “to” date, I clear the “to” date, since this is not a valid state.
private static void DateFrom_PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
DateRangePicker dateRangePicker = (DateRangePicker)obj;
dateRangePicker.DateFromChanged();
}
private void DateFromChanged()
{
// This updates the blackout dates for DatePickerTo.
this.DatePickerTo.BlackoutDates.Clear();
if (this.DateFrom.HasValue)
{
DateTime dateFrom = this.DateFrom.Value;
if (this.DateTo.HasValue)
{
DateTime dateTo = this.DateTo.Value;
if (dateTo <= dateFrom)
{
this.DateTo = null;
}
}
this.DatePickerTo.BlackoutDates.Add(new CalendarDateRange(DateTime.MinValue, dateFrom));
}
this.DatePickerTo.BlackoutDates.Add(new CalendarDateRange(DateTime.Today.AddDays(1), DateTime.MaxValue));
this.OnDateChanged(FromDateChanged);
}
private static void DateTo_PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
DateRangePicker dateRangePicker = (DateRangePicker)obj;
dateRangePicker.OnDateChanged(dateRangePicker.ToDateChanged);
}
With this, the code for the DateRangePicker control is complete. Switching back to the main app, I added that control to the page, right above the DataGrid:
<reusableControls:DateRangePicker x:Name="CustomerSinceDatePicker" />
Hooking up the UI to the data
At this point we have all the data virtualization infrastructure, plus an adequate UI for the user to specify the filter parameters and for us to display the customer list. The only thing that’s missing is to hook up the UI to the data.
When the user picks a new date in the DateRangePicker UserControl, we want to re-query the database with the new “from” and “to” date parameters, and display that data in the DataGrid. I did that by adding a handler for the “FromDateChanged” and “ToDateChanged” events of DateRangePicker. Within that handler, I create a new CustomerProvider with the right date parameters, instantiate the data virtualization classes, and re-set the DataContext:
<reusableControls:DateRangePicker x:Name="CustomerSinceDatePicker" FromDateChanged="CustomerSinceDatePicker_DateChanged" ToDateChanged="CustomerSinceDatePicker_DateChanged"/>
private void CustomerSinceDatePicker_DateChanged(object sender, EventArgs e)
{
this.RefreshData();
}
private void RefreshData()
{
string sortString = "CustomerSince DESC";
customerProvider = new CustomerProvider(this.CustomerSinceDatePicker.DateFrom, this.CustomerSinceDatePicker.DateTo, sortString);
AsyncVirtualizingCollection<Customer> customerList = new AsyncVirtualizingCollection<Customer>(customerProvider, pageSize, timePageInMemory);
this.DataContext = customerList;
this.CustomersDataGrid.SelectedIndex = 0;
}
You may have noticed in the code above that I am passing a sort string to the server – “CustomerSince DESC”. With the current implementation, customers are always displayed in this order. If you have a few pre-defined common sort expressions, you have here all the pieces needed to send those sort descriptions to the server. But what if you want clicking on DataGrid columns to cause sorting in the database? My next post will show how you can do that.

Download the database initialization project (built with .NET 4.0 Beta 2).
Download the data virtualization project with filtering (built with .NET 4.0 Beta 2).
Posted by Bea under WPF | Comments (5)
November 20, 2009

I got back from the Oredev conference in Malmo (Sweden) a few days ago, where I spoke on advanced line-of-business features of WPF and Silverlight. I presented at Oredev last year and became a big fan of this conference – I had to go back! Just like last year, I was highly impressed by:
The meticulous organization of the conference. Only in Sweden could there be so much attention to detail in such a big conference. I could go on about this…
High technical level and interest of the attendees. All attendees I interacted with were smart, experienced, and highly engaged. It was a pleasure to speak to such an audience.
Interactions I had with other speakers. Other than seeing old Microsoft friends, I had a chance to interact with people from others parts of the industry and get a broader understanding of software trends. I also had the chance to meet some very inspiring women, as you can see in this interview with Amanda, Kerry and me.

In other news, I was excited to find out recently that I was nominated for the Computer Weekly IT Blog Awards 2009, in the Individual IT Professional Female category. It would have been even better if Computer Weekly had contacted me and voting weren’t closed already. Still I am very honored to have been listed as one of the 10 nominees for this award – so thanks everyone for reading my blog and making this possible!
Posted by Bea under General | Comments (0)
November 16, 2009
In my last post, I explained the reasoning behind adding CollectionViewSource to WPF. In this post I will talk about CollectionViewSource in Silverlight (introduced in Silverlight 3) and compare it with its WPF counterpart.
If you recall from my last post, CollectionViewSource was added to WPF mainly to permit view-related scenarios to be expressed in XAML (so they could be tooled), and also to aid in remembering the current item of previously displayed collections. The reasons for adding CollectionViewSource to Silverlight were very different. Before the introduction of CollectionViewSource, Silverlight collections were not automatically wrapped by views – in fact the concept of “views” didn’t even exist in Silverlight 2. There was no way to sort or filter a collection (other than modifying the collection itself), or to build a master-detail scenario based on currency (although you could create a master-detail scenario based on selection). The introduction of CollectionViewSource enabled all of those scenarios in Silverlight, while improving compatibility with WPF.
Just like in WPF, a class is considered a “view” if it implements ICollectionView. All the views that we’re used to interacting with derive from CollectionView, which in turn implements ICollectionView. Silverlight provides implementations only for EnumerableCollectionView and ListCollectionView, which means that it is able to generate views only for collections that implement IEnumerable or IList. These are by far the two most common scenarios. Unlike WPF, Silverlight’s CollectionView, EnumerableCollectionView and ListCollectionView classes are all internal.
In addition to these, Silverlight also contains a PagedCollectionView class (this is unique to Silverlight). You can manually wrap your collection with this view to add filtering, sorting, grouping, currency tracking and paging to your collection. Tim Heuer shows an example of its usage. Silverlight’s CollectionViewSource, on the other hand, provides the ability to filter, sort, and track currency, but it does not offer the ability to group data.
Unlike WPF, Silverlight only wraps collections with a view when CollectionViewSource is used as an intermediary. If you simply bind an ItemsControl to a collection, no “default view” will be created internally for you. Also, since the ItemCollection class doesn’t implement ICollectionView, it’s not possible to sort or filter non-data bound items that have been added to an ItemsControl.
Just like in WPF, Silverlight’s CollectionViewSource creates a view to wrap a collection when its Source property points to a new collection. In both platforms, it is not necessary to specify “Path=View” when binding to the CollectionViewSource – the binding does that automatically. Here’s the syntax you use to bind to a CollectionViewSource:
<UserControl.Resources>
<CollectionViewSource x:Key="cvs" />
</UserControl.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource cvs}}" />
Notice that I didn’t have to specify “{Binding Source={StaticResource cvs}, Path=View}” to bind to the view exposed by the CollectionViewSource. Both syntaxes are equivalent, but the second is unnecessary – the binding engine knows to drill into the View property when given a CollectionViewSource.
In WPF, when the CollectionViewSource points to several collections throughout its life, it creates a view for each of them and remembers those views. My last post explains how this feature enables a common scenario that would be a bit of work to implement without CollectionViewSource. I’m very glad to say that this feature has also been implemented in Silverlight, so the selection behavior of my previous post’s WPF sample works the same way in Silverlight.
In fact, porting that sample to Silverlight was straightforward. The only difference was that in WPF, Bindings within the resources inherit the Window’s DataContext. This enabled me to write the following code/XAML to bind the CollectionViewSource’s Source to the Window’s DataContext:
this.DataContext = new Mountains();
<CollectionViewSource Source="{Binding}" x:Key="cvs1"/>
This same feature is not present in Silverlight 3. Here’s one equivalent way of implementing that behavior in Silverlight:
<local:Mountains x:Key="mountains" />
<CollectionViewSource Source="{Binding Source={StaticResource mountains}}" x:Key="cvs1"/>
If you have Silverlight 3 installed, you can see a Silverlight version of my previous post’s WPF sample in its own page or embedded below:
Download the Silverlight project (built with Silverlight 3).
Posted by Bea under Silverlight | Comments (4)
October 31, 2009
CollectionViewSource has existed for a long time in WPF and was recently introduced in Silverlight 3. My next post will cover CollectionViewSource in the context of Silverlight. But before covering that topic, I’ve decided to provide some background about why we introduced this class in WPF.
Views in WPF
When a user binds a WPF property to a collection of data, WPF automatically creates a view to wrap the collection, and binds the property to the view, not the raw collection. This behavior always happens, and is independent of CollectionViewSource.
Views provide four types of functionality: sorting, filtering, grouping and tracking the current item. The scenarios you can implement with these four simple features are endless!
The type of view created by WPF depends on the collection type. There are essentially 3 types of views automatically generated by WPF, all deriving from the CollectionView base class:
- ListCollectionView -> Created when the collection implements IList.
- BindingListCollectionView -> Created when the collection implements IBindingList.
- EnumerableCollectionView -> Created when the collection implements nothing but IEnumerable. This class is internal to WPF.
Before CollectionViewSource was introduced in WPF, you could manipulate the automatically generated view (called the “default view”) by obtaining the ItemCollection returned by the ItemsControl.Items property. ItemCollection is a “hybrid” class – it’s both a collection (implementing ICollection, IList, and IEnumerable) containing the list of items in the ItemsControl, and a view (deriving from CollectionView) that exposes properties to manipulate the default view. Here’s an example:
this.MyItemsControl.Items.SortDescriptions.Add(new SortDescription(…));
ItemCollection doesn’t contain the implementation for any “view” related functionality though – all of its “view” methods are delegated. If the ItemsControl has items added directly to it in XAML (not data bound), ItemCollection delegates view operations to a private property of type “InnerItemCollectionView” (you can find it in Reflector – it’s appropriately called “_internalView”). If the ItemsControl is data bound, the ItemCollection delegates all view operations to the automatically generated default view that wraps the collection.
There is one more view in WPF that you may come across: CompositeCollectionView. Naturally, this view is automatically created when the property is data bound to a CompositeCollection. CompositeCollections have also existed in WPF for a long time. They allow merging several individual collections and individual items into one single collection in XAML.
<ItemsControl.ItemsSource>
<CompositeCollection>
<ListBoxItem><TextBlock Text="Hello" /></ListBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource Collection1}}"/>
<CollectionContainer Collection="{Binding Source={StaticResource Collection2}}"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
Instead of using the default view, the user can also create his own view to wrap the collection. This could be a custom view (implementing ICollectionView) or one of the existing public views – typically ListCollectionView or BindingListCollectionView. For example:
ListCollectionView lcv = new ListCollectionView(myList);
this.MyItemsControl.ItemsSource = lcv;
lcv.SortDescriptions.Add(new SortDescription(…));
In this scenario, WPF will not wrap this view in another view, it simply uses the one it’s given, as expected. The user can then use his ListCollectionView object to perform view-related operations (such as adding sort descriptions).
CollectionViewSource in WPF
All of these features already existed in WPF before we decided to add CollectionViewSource. We could already do filtering, sorting, grouping, we could track and set the current item, we could retrieve the default view created by WPF and manipulate it, and we could manually create multiple views of the same collection. So, why did we add CollectionViewSource?
The main reason was to enable those view-related operations in XAML – previously they could only be done in code. Without XAML support, tools like Blend can not provide a good tooling experience for these features. The most common uses of CollectionViewSource are to specify sorting and grouping directly in XAML, or to use XAML to hook up a filter handler defined in code. I’ve shown several samples of this syntax before (this post shows filtering, this post shows grouping, and this post shows sorting and grouping combined), so I won’t go into that here.
CollectionViewSource is NOT a view, unlike the classes I described above. If you look in Reflector, you will notice that it doesn’t even implement ICollectionView – a requirement for a class to be considered a “view”. CollectionViewSource is simply a class that once given a collection (by setting its Source property) creates and exposes the corresponding view (through the View property), and that allows adding sorting and grouping (unfortunately not filtering) directly in XAML.
CollectionViewSource also provides several other methods useful to obtain and manipulate views. Among them is yet another way to retrieve the view that wraps a collection:
ListCollectionView lcv = CollectionViewSource.GetDefaultView(myCollection) as ListCollectionView;
lcv.SortDescriptions.Add(new SortDescription(…));
This is my favorite way of getting a view for a collection because 1) it doesn’t require a handle to the ItemsControl like when using ItemCollection and 2) I don’t need to create the view myself. This method completes the list of ways to retrieve the view for a particular collection.
CollectionViewSource also enables another interesting scenario. If a particular CollectionViewSource points to different collections at different times, it remembers all the views that it created to wrap those collections. If a source that has already been set in the past is set again, CVS recognizes it and reuses the view it created originally. This behavior is useful in hierarchical binding scenarios. To illustrate this point, I created a very simple three-level master-detail scenario with the following XAML:
<Window.Resources>
<CollectionViewSource Source="{Binding}" x:Key="cvs1"/>
<CollectionViewSource Source="{Binding Source={StaticResource cvs1}, Path=Lifts}" x:Key="cvs2"/>
<CollectionViewSource Source="{Binding Source={StaticResource cvs2}, Path=Runs}" x:Key="cvs3"/>
</Window.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource cvs1}}" DisplayMemberPath="Name"/>
<ListBox ItemsSource="{Binding Source={StaticResource cvs2}}" DisplayMemberPath="Name" />
<ListBox ItemsSource="{Binding Source={StaticResource cvs3}}" />
The DataContext for this window is set to a data source with the following structure:
public class Mountains : ObservableCollection<Mountain>
{
…
}
public class Mountain
{
public ObservableCollection<Lift> Lifts { get; set; }
…
}
public class Lift
{
public ObservableCollection<string> Runs { get; set; }
…
}
Here is a screenshot:

In this sample, cvs1 points to the mountains collection and creates a ListCollectionView to wrap it; cvs2 holds one of three collections: the Lifts collection for the first, second or third mountain; and cvs3 holds one of eight collections of Runs. Now imagine that you pick the first mountain (Whistler) and the second lift from that mountain (Garbanzo Express), then you switch to the second mountain (Stevens Pass) and back to the first mountain again (Whistler). At this point, you would expect the second lift (Garbanzo Express) to still be selected. And it is. Because the second CollectionViewSource stores the view last used to wrap that particular Lifts collection, reusing it next time it needs to display that same collection, the current item is preserved.
If you made it so far, you know more about CollectionViewSource than you will ever need! My next post will include a similar discussion in the context of Silverlight.
Download the WPF project (built with .NET 4.0 Beta 1).
Posted by Bea under WPF | Comments (14)
October 19, 2009
WPF and Silverlight both offer the ability to derive a Style from another Style through the “BasedOn” property. This feature enables developers to organize their styles using a hierarchy similar to class inheritance. Consider the following styles:
<Style TargetType="Button" x:Key="BaseButtonStyle">
<Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="Button" x:Key="RedButtonStyle" BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Foreground" Value="Red" />
</Style>
With this syntax, a Button that uses RedButtonStyle will have its Foreground property set to Red and its Margin property set to 10.
This feature has been around in WPF for a long time, and it’s new in Silverlight 3.
What if you want to set more than one style on an element? Neither WPF nor Silverlight provide a solution for this problem out of the box. Fortunately there are ways to implement this behavior in WPF, which I will discuss in this blog post.
WPF and Silverlight use markup extensions to provide properties with values that require some logic to obtain. Markup extensions are easily recognizable by the presence of curly brackets surrounding them in XAML. For example, the {Binding} markup extension contains logic to fetch a value from a data source and update it when changes occur; the {StaticResource} markup extension contains logic to grab a value from a resource dictionary based on a key. Fortunately for us, WPF allows users to write their own custom markup extensions. This feature is not yet present in Silverlight, so the solution in this blog is only applicable to WPF.
Others have written great solutions to merge two styles using markup extensions. However, I wanted a solution that provided the ability to merge an unlimited number of styles, which is a little bit trickier.
Writing a markup extension is straightforward. The first step is to create a class that derives from MarkupExtension, and use the MarkupExtensionReturnType attribute to indicate that you intend the value returned from your markup extension to be of type Style.
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}
Specifying inputs to the markup extension
We’d like to give users of our markup extension a simple way to specify the styles to be merged. There are essentially two ways in which the user can specify inputs to a markup extension. The user can set properties or pass parameters to the constructor. Since in this scenario the user needs the ability to specify an unlimited number of styles, my first approach was to create a constructor that takes any number of strings using the “params” keyword:
public MultiStyleExtension(params string[] inputResourceKeys)
{
}
My goal was to be able to write the inputs as follows:
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
Notice the comma separating the different style keys. Unfortunately, custom markup extensions don’t support an unlimited number of constructor parameters, so this approach results in a compile error. If I knew in advance how many styles I wanted to merge, I could have used the same XAML syntax with a constructor taking the desired number of strings:
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}
As a workaround, I decided to have the constructor parameter take a single string that specifies the style names separated by spaces. The syntax isn’t too bad:
<Button Style="{local:MultiStyle BigButtonStyle GreenButtonStyle}" … />
private string[] resourceKeys;
public MultiStyleExtension(string inputResourceKeys)
{
if (inputResourceKeys == null)
{
throw new ArgumentNullException("inputResourceKeys");
}
this.resourceKeys = inputResourceKeys.Split(new char[] { ‘ ‘ }, StringSplitOptions.RemoveEmptyEntries);
if (this.resourceKeys.Length == 0)
{
throw new ArgumentException("No input resource keys specified.");
}
}
Calculating the output of the markup extension
To calculate the output of a markup extension, we need to override a method from MarkupExtension called “ProvideValue”. The value returned from this method will be set in the target of the markup extension.
I started by creating an extension method for Style that knows how to merge two styles. The code for this method is quite simple:
public static void Merge(this Style style1, Style style2)
{
if (style1 == null)
{
throw new ArgumentNullException("style1");
}
if (style2 == null)
{
throw new ArgumentNullException("style2");
}
if (style1.TargetType.IsAssignableFrom(style2.TargetType))
{
style1.TargetType = style2.TargetType;
}
if (style2.BasedOn != null)
{
Merge(style1, style2.BasedOn);
}
foreach (SetterBase currentSetter in style2.Setters)
{
style1.Setters.Add(currentSetter);
}
foreach (TriggerBase currentTrigger in style2.Triggers)
{
style1.Triggers.Add(currentTrigger);
}
// This code is only needed when using DynamicResources.
foreach (object key in style2.Resources.Keys)
{
style1.Resources[key] = style2.Resources[key];
}
}
With the logic above, the first style is modified to include all information from the second. If there are conflicts (e.g. both styles have a setter for the same property), the second style wins. Notice that aside from copying styles and triggers, I also took into account the TargetType and BasedOn values as well as any resources the second style may have. For the TargetType of the merged style, I used whichever type is more derived. If the second style has a BasedOn style, I merge its hierarchy of styles recursively. If it has resources, I copy them over to the first style. If those resources are referred to using {StaticResource}, they’re statically resolved before this merge code executes, and therefore it isn’t necessary to move them. I added this code in case we’re using DynamicResources.
The extension method shown above enables the following syntax:
style1.Merge(style2);
This syntax is useful provided that I have instances of both styles within ProvideValue. Well, I don’t. All I get from the constructor is a list of string keys for those styles. If there was support for params in the constructor parameters, I could have used the following syntax to get the actual style instances:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}
But that doesn’t work. And even if the params limitation didn’t exist, we would probably hit another limitation of markup extensions, where we would have to use property-element syntax instead of attribute syntax to specify the static resources, which is verbose and cumbersome (I explain this bug better in a previous blog post). And even if both those limitations didn’t exist, I would still rather write the list of styles using just their names – it is shorter and simpler to read than a StaticResource for each one.
The solution is to create a StaticResourceExtension using code. Given a style key of type string and a service provider, I can use StaticResourceExtension to retrieve the actual style instance. Here is the syntax:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
Now we have all the pieces needed to write the ProvideValue method:
public override object ProvideValue(IServiceProvider serviceProvider)
{
Style resultStyle = new Style();
foreach (string currentResourceKey in resourceKeys)
{
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
if (currentStyle == null)
{
throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
}
resultStyle.Merge(currentStyle);
}
return resultStyle;
}
Here is a complete example of the usage of the MultiStyle markup extension:
<Window.Resources>
<Style TargetType="Button" x:Key="SmallButtonStyle">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="25" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style TargetType="Button" x:Key="GreenButtonStyle">
<Setter Property="Foreground" Value="Green" />
</Style>
<Style TargetType="Button" x:Key="BoldButtonStyle">
<Setter Property="FontWeight" Value="Bold" />
</Style>
</Window.Resources>
<Button Style="{local:MultiStyle SmallButtonStyle GreenButtonStyle BoldButtonStyle}" Content="Small, green, bold" />

Download the WPF project (built with .NET 4.0 Beta 1).
Posted by Bea under WPF | Comments (9)
October 5, 2009
In a previous post, I compared two data virtualization techniques implemented by Paul McClean and Vincent Van Den Berghe for WPF. In this post, I describe a solution that combines some of the best features of both. I started with Paul’s solution, eliminated a few limitations, and incorporated some of Vincent’s ideas.
Selection
In Paul’s solution, a “collection reset” event is used to notify the UI each time a new page is loaded from the database. As a side effect, this notification unintentionally causes a ListBox to lose track of the selected item. This makes it impossible for a user to scroll through a long list using the down-arrow key; every time a new page is loaded, the ListBox selection jumps back to the beginning of the list. The troublesome code can be found in the following methods of AsyncVirtualizingCollection:
private void LoadPageCompleted(object args)
{
int pageIndex = (int)((object[]) args)[0];
IList<T> page = (IList<T>)((object[])args)[1];
PopulatePage(pageIndex, page);
IsLoading = false;
FireCollectionReset();
}
private void FireCollectionReset()
{
NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(e);
}
One possible solution to this problem is to provide more fine-grained add and remove notifications for the new items, instead of a collection reset. Implementing this is not as straightforward as it seems, though, because of the combination of the following two behaviors : 1) When WPF receives a collection change notification for a newly added item, ListCollectionView accesses that item using the collection’s indexer, even if the item is not visible in the UI. 2) When an item is accessed, Paul’s caching heuristics load its page into memory, as well as the previous or next page depending on whether the item belongs to the first of second half of its page.
With this information, you can probably guess what happens when we provide fine-grained collection notifications. When a page is loaded, we notify WPF that a few items were added to the collection, ListCollectionView accesses each one of those items one by one, triggering a load of the subsequent page, which in turn notifies WPF that a more items were added to the collection, which causes the ListCollectionView to access each one, and so on. Eventually, the whole collection gets loaded, which is exactly what we’re trying to avoid.
We could use fine-grained notifications with either of two possible approaches: 1) change the caching heuristics so that neighboring pages are no longer loaded; or 2) implement our own view (as a replacement for ListCollectionView) that doesn’t call the collection indexer to access each newly added item. Either approach would fix the problem, but they would not fix another related problem. If we happen to select an item that is not yet loaded, selection would be lost when the item finishes loading. This would happen because selection is tracked based on the actual data item – not its index within the ListBox. If I press the down-arrow key until I select an item that hasn’t yet been loaded, when its data item changes at load time (from null to the actual data), the ListBox’s selected item is no longer referring to that same item.
This train of thought made it clear that Vincent’s technique of wrapping each data item could solve all these selection issues. When using data wrappers, the data items associated with each ListBoxItem don’t ever change – they’re the wrappers themselves. The data wrappers are not replaced when data loads, and therefore WPF doesn’t lose track of the selected item. What changes is the data within the wrapper, which means we can now raise property change notifications to update the UI, instead of collection change notifications. This is good news, since property change notifications are very fine-grained, and they work across threads.
My data wrapper class is called DataWrapper and among other properties it contains a reference to the actual data.
public class DataWrapper<T> : INotifyPropertyChanged where T : class
{
private T data;
…
public T Data
{
get { return this.data; }
internal set
{
this.data = value;
this.OnPropertyChanged("Data");
…
}
}
…
}
Adding wrappers required some changes in the collection code base. In Paul’s code, requesting a page would add a new entry in the page dictionary with value null, and populating a page would set that value to the actual page:
protected virtual void RequestPage(int pageIndex)
{
if (!_pages.ContainsKey(pageIndex))
{
_pages.Add(pageIndex, null);
_pageTouchTimes.Add(pageIndex, DateTime.Now);
LoadPage(pageIndex);
}
else
{
_pageTouchTimes[pageIndex] = DateTime.Now;
}
}
protected virtual void PopulatePage(int pageIndex, IList<T> page)
{
if ( _pages.ContainsKey(pageIndex) )
_pages[pageIndex] = page;
}
To support data wrappers, I changed the code so that a request for a new page results in the immediate creation of a page full of empty data wrappers . This page is added to the dictionary right away. Later, when the actual data gets loaded, populating the page just fills in the data part of the wrappers.
protected virtual void RequestPage(int pageIndex)
{
if (!_pages.ContainsKey(pageIndex))
{
int pageLength = Math.Min(this.PageSize, this.Count – pageIndex * this.PageSize);
DataPage<T> page = new DataPage<T>(pageIndex * this.PageSize, pageLength);
_pages.Add(pageIndex, page);
LoadPage(pageIndex, pageLength);
}
else
{
_pages[pageIndex].TouchTime = DateTime.Now;
}
}
protected virtual void PopulatePage(int pageIndex, IList<T> dataItems)
{
DataPage<T> page;
if (_pages.TryGetValue(pageIndex, out page))
{
page.Populate(dataItems);
}
}
Contains and IndexOf
In Paul’s data virtualization solution, VirtualizingCollection does not include an implementation for the Contains and IndexOf methods:
public bool Contains(T item)
{
return false;
}
public int IndexOf(T item)
{
return -1;
}
As a result, the CurrentItem property of WPF’s collection view doesn’t track the current item correctly, and therefore we can’t implement the Master-Detail scenario by simply binding both a ListBox and a ContentControl to the collection. There are other scenarios equally affected by this.
Providing an implementation for these methods was relatively straightforward:
public bool Contains(DataWrapper<T> item)
{
foreach (DataPage<T> page in _pages.Values)
{
if (page.Items.Contains(item))
{
return true;
}
}
return false;
}
public int IndexOf(DataWrapper<T> item)
{
foreach (KeyValuePair<int, DataPage<T>> keyValuePair in _pages)
{
int indexWithinPage = keyValuePair.Value.Items.IndexOf(item);
if (indexWithinPage != -1)
{
return PageSize * keyValuePair.Key + indexWithinPage;
}
}
return -1;
}
Currency
Providing an implementation for Contains and IndexOf enabled currency (CurrentItem), but there were still some corner cases that didn’t work correctly. For example, if I selected an item and then scrolled it off-screen, WPF knew not to virtualize the UI element for that item, but the data was still being virtualized. This also caused problems with currency.
I needed a way to prevent an item from virtualizing its data if its UI was still available. Adding data wrappers had the fortunate side effect of making the fix for this problem easier. I know that a data wrapper is being used if someone is listening to its property change event. So I was able to add an IsInUse property to the data wrapper with the following implementation:
public class DataWrapper<T> : INotifyPropertyChanged where T : class
{
…
public event PropertyChangedEventHandler PropertyChanged;
public bool IsInUse
{
get { return this.PropertyChanged != null; }
}
}
Similarly, I added a property that determines whether a page has at least one item in use:
public class DataPage<T> where T : class
{
…
public bool IsInUse
{
get { return this.Items.Any(wrapper => wrapper.IsInUse); }
}
}
Then I used that property to avoid cleaning up pages that are still in use, within VirtualizingCollection:
public void CleanUpPages()
{
int[] keys = _pages.Keys.ToArray();
foreach (int key in keys)
{
// page 0 is a special case, since WPF ItemsControl access the first item frequently
if (key != 0 && (DateTime.Now – _pages[key].TouchTime).TotalMilliseconds > PageTimeout)
{
bool removePage = true;
DataPage<T> page;
if (_pages.TryGetValue(key, out page))
{
removePage = !page.IsInUse;
}
if (removePage)
{
_pages.Remove(key);
}
}
}
}
IsInitializing + IsLoading
Paul’s AsyncVirtualizingCollection has an “IsLoading” property that is set to true when the collection is either counting its items or fetching a page. This is useful so that we can provide visual feedback when we’re querying data from the database. On the other hand, it’s a bit limiting to have only one property indicating that work is in progress. We don’t want to prevent the user from interacting with other items in the ListBox just because scrolling causes a few items to start downloading. Ideally, we would get more fine-grained status information.
To solve this problem, I added an “IsInitializing” property that is true when we’re fetching the count, and changed “IsLoading” slightly to inform us when the collection is fetching a new page. The “IsInitializing” property is defined at the collection level, and the “IsLoading” property is defined in the data wrapper.
When the collection count is being fetched (that is, when IsInitializing is true), I display a message in the middle of the empty ListBox and switch to the “Wait” cursor, making it obvious that it’s not yet ready for user interaction:
<ControlTemplate TargetType="{x:Type ListView}">
<Grid>
<theme:ListBoxChrome Name="Bd" … >
…
</theme:ListBoxChrome>
<Grid Background="White" Opacity="0.5" Name="InitializingGrid" Visibility="Collapsed">
<TextBlock Text="Initializing…" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Grid>
<ControlTemplate.Triggers>
…
<DataTrigger Binding="{Binding Path=IsInitializing}" Value="True">
<Setter Property="Cursor" Value="Wait" TargetName="InitializingGrid"/>
<Setter Property="Visibility" Value="Visible" TargetName="InitializingGrid"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
When an item is being fetched from the database (that is, when IsLoading is true), I display a message and “Wait” cursor just within the corresponding ListViewItem:
<ControlTemplate TargetType="{x:Type ListViewItem}">
…
<Grid>
…
<GridViewRowPresenter …>
<StackPanel Name="Loading" Orientation="Horizontal" Grid.RowSpan="2" Visibility="Collapsed">
<TextBlock Text="Loading item " />
<TextBlock Text="{Binding ItemNumber}" />
<TextBlock Text="…" />
</StackPanel>
</Grid>
…
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsLoading}" Value="True">
<Setter TargetName="Loading" Property="Visibility" Value="Visible"/>
<Setter Property="Cursor" Value="Wait" />
…
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
This is the point where I would normally hand the problem over to a visual designer or an interaction designer. Now that we can get fine-grained information about which data items are loading and which are available, a designer could come up with a variety of ways to display this information to the user.
Still missing…
Paul’s solution assumes the collection is read-only, and my code doesn’t really fix that limitation. Although my AsyncVirtualizingCollection will notice if its count has changed when fetching a new page of data, it won’t notice at any other time. If you’re successful at extending this solution to support dynamic collection changes, I’d love to hear from you!
You can download the source for this project.
Posted by Bea under WPF | Comments (24)
September 20, 2009
Two posts ago I showed a possible solution to add labels to a WPF pie chart. In my last post, I explained some implementation details of that solution. In this blog post, I will show the steps I took to port the labeled pie chart to Silverlight.
The Silverlight and WPF teams do their best to ensure that porting a Silverlight application to WPF is a smooth experience. This is expected, since Silverlight is (for the most part) a subset of WPF. Porting WPF applications to Silverlight, on the other hand, can be pretty tricky. The Silverlight and WPF teams are not specifically supporting this scenario, but the reality is that in the real world, many people need to do just that. I decided to port my WPF labeled pie chart to Silverlight to see how many issues I would come across and to document the workarounds.
MeasureOverride is sealed in Silverlight
The first issue I encountered was the fact that Canvas’ MeasureOverride is sealed in Silverlight, so I couldn’t override it. I was overriding it in the PieChartLabelArea, where I had to know whether any of the child labels are associated with small arcs to help with the Auto display mode.
My workaround was to derive from Panel instead, since Panel’s MeasureOverride is not sealed. Since Panel doesn’t do automatic arranging, I had to also implement ArrangeOverride:
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement child in this.Children)
{
child.Arrange(new Rect(new Point(0, 0), child.DesiredSize));
}
return finalSize;
}
In this scenario it was OK to replace the Canvas with a Panel because the WPF implementation wasn’t using any of Canvas’ functionality other than the automatic arranging. The contents of the labels are positioned inside a different Canvas that’s part of the ControlTemplate for PieChartLabel. When PieChartLabels are added to the PieChartLabelArea, the canvases in their templates are all placed at the origin of the label area, and each label is positioned correctly within its own canvas.
OverrideMetadata doesn’t exist in Silverlight
In WPF I was using the following code in the LabeledPieChart’s static constructor to set its default style key:
static LabeledPieChart()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LabeledPieChart), new FrameworkPropertyMetadata(typeof(LabeledPieChart)));
}
Since OverrideMetadata doesn’t exist in Silverlight, I used the instance constructor instead to set the DefaultStyleKey property:
public LabeledPieChart()
{
this.DefaultStyleKey = typeof(LabeledPieChart);
}
AddOwner doesn’t exist in Silverlight
In WPF, I was registering some DPs with the convenient AddOwner method. For example, the following line of code indicates that the Title property of Chart can be applied to LabeledPieChart as well:
public static readonly DependencyProperty TitleProperty = Chart.TitleProperty.AddOwner(typeof(LabeledPieChart), null);
The workaround is to register a new DP with the same name in LabeledPieChart. The syntax is not much longer:
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(object), typeof(LabeledPieChart), new PropertyMetadata(String.Empty));
Vector doesn’t exist in Silverlight
The WPF version of this code makes use of the Vector class in several places. We rely on Vector in PieChartHelper to calculate the midpoint of a wedge’s arc, and in PieChartLabel to determine the three points needed in Connected mode.
As a workaround, I decided to bring a version of WPF’s Vector class into the Silverlight project. I simply copied this code from Reflector and tweaked it a bit.
FrameworkPropertyMetadataOptions.AffectsArrange doesn’t exist in Silverlight
In WPF, I specified that the DisplayMode property should invalidate arrange when set, which in turn was causing the label to be repositioned.
public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register("DisplayMode", typeof(DisplayMode), typeof(PieChartLabel), new FrameworkPropertyMetadata(DisplayMode.ArcMidpoint, FrameworkPropertyMetadataOptions.AffectsArrange));
FrameworkPropertyMetadataOptions.AffectsArrange doesn’t exist in Silverlight, but we can work around it by invalidating arrange in the change handler for the DP. Here’s the corresponding code in Silverlight:
public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register("DisplayMode", typeof(DisplayMode), typeof(PieChartLabel), new PropertyMetadata(DisplayMode.ArcMidpoint, DisplayModePropertyChanged));
private static void DisplayModePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
PieChartLabel label = obj as PieChartLabel;
if (label != null)
{
label.InvalidateArrange();
}
}
FrameworkPropertyMetadataOptions.AffectsParentMeasure doesn’t exist in Silverlight
In WPF, I specified that changing the Geometry property of PieChartLabel should affect the parent’s measure when I registered the DP:
public static readonly DependencyProperty GeometryProperty = PieDataPoint.GeometryProperty.AddOwner(typeof(PieChartLabel), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, GeometryPropertyChanged));
The equivalent behavior in Silverlight can be done by adding a change handler for this DP that invalidates measure on the element’s parent:
public static readonly DependencyProperty GeometryProperty = DependencyProperty.Register("Geometry", typeof(Geometry), typeof(PieChartLabel), new PropertyMetadata(null, GeometryPropertyChanged));
private static void GeometryPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
PieChartLabel label = obj as PieChartLabel;
if (label != null)
{
…
FrameworkElement fe = label.Parent as FrameworkElement;
if (fe != null)
{
fe.InvalidateMeasure();
}
}
}
Template.FindName doesn’t exist in Silverlight
WPF has a useful method that allows us to find an element with a specified name within a template:
PieChartLabelArea labelArea = chart.Template.FindName("LabelArea_PART", chart) as PieChartLabelArea;
Silverlight doesn’t include the FindName method. As a workaround, I wrote a helper method that searches the visual tree until it finds an element with the specified name. The following line of code returns the label area in Silverlight:
PieChartLabelArea labelArea = TreeHelper.FindDescendent(chart, "LabelArea_PART") as PieChartLabelArea;
FrameworkElement.Unloaded doesn’t exist in Silverlight
In the WPF version of this project, I ensure that the labels are removed when the PieDataPoints are unloaded with the following code:
pieDataPoint.Unloaded += delegate
{
labelArea.Children.Remove(label);
};
However, in Silverlight, FrameworkElement does not have an Unloaded event. As a workaround, I check whether the PieDataPoint is still in the tree every time layout is updated. When I discover that the data point is no longer in the tree, I remove the corresponding label from the tree.
pieDataPoint.LayoutUpdated += delegate
{
if (!pieDataPoint.IsInTree())
{
labelArea.Children.Remove(label);
}
};
And here’s how I implemented the IsInTree helper method:
public static bool IsInTree(this FrameworkElement element)
{
var rootElement = Application.Current.RootVisual as FrameworkElement;
while (element != null)
{
if (element == rootElement)
{
return true;
}
element = VisualTreeHelper.GetParent(element) as FrameworkElement;
}
return false;
}
SnapsToDevicePixels doesn’t exist in Silverlight
I set the inherited property SnapsToDevicePixels to true on the label area in WPF to make sure all the labels have crisp borders – I don’t want antialiasing to blur any edges that fall between pixel boundaries. There’s no SnapsToDevicePixels property in Silverlight, but the workaround is easy: just leave it out! Silverlight introduces a new inherited property, UseLayoutRounding, which is true by default. The difference between the two properties is subtle (UseLayoutRounding affects layout sizes, while SnapsToDevicePixels doesn’t), but the effect is the same: both keep single-pixel borders sharp. Note that UseLayoutRounding was recently added to version 4 of WPF, but it’s false by default to maintain backward compatibility.
{x:Type …} syntax is not supported in Silverlight
The following syntax is supported only in WPF:
<Style TargetType="{x:Type local:PieChartLabel}">
The syntax below is equivalent and is supported both in WPF and Silverlight:
<Style TargetType="local:PieChartLabel">
Binding to AncestorType is not supported in Silverlight
Below you can see the control and data templates for the PieChartLabel in WPF:
<Style TargetType="{x:Type local:PieChartLabel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:PieChartLabel}">
<Canvas Name="Canvas_PART">
<ContentPresenter Name="Content_PART"/>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Background="LightGray">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:PieChartLabel}}, Path=FormattedRatio}" VerticalAlignment="Center" Margin="5,0,5,0" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Within the DataTemplate, I use an AncestorType binding to display the FormattedRatio property defined on PieChartLabel. I couldn’t use a TemplateBinding because that would refer to properties of the ContentPresenter.
Silverlight doesn’t support AncestorType bindings. One possible solution to work around this issue could have been to use a binding to Self with a Converter, and within the converter walk up the visual tree until the PieChartLabel is found. However, the binding is evaluated before the PieChartLabel is added to the tree, so this approach doesn’t really help.
As a workaround, I merged the data template into the control template so that I could use a TemplateBinding to access the FormattedRatio.
<Style TargetType="local:PieChartLabel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:PieChartLabel">
<Canvas Name="Canvas_PART">
<Polyline Name="Polyline_PART" StrokeThickness="{TemplateBinding LineStrokeThickness}" Stroke="{TemplateBinding LineStroke}" StrokeLineJoin="Round" />
<StackPanel Background="LightGray" Name="Content_PART">
<TextBlock Text="{TemplateBinding FormattedRatio}" VerticalAlignment="Center" Margin="5,0,5,0" />
</StackPanel>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
WPF’s Polyline rendering bug is not present in Silverlight
To end on a high note, I was able to remove the workaround for the Polyline rendering bug I struggled with in the WPF version of this code. As I explained in my previous post, if I placed a Polyline in the label’s template and modified its points whenever the label position changed, WPF would occasionally render the Polyline incorrectly. To work around the issue, I had to create a new Polyline every time the label was repositioned, which is not as efficient.
I was glad to see that this bug is not present in Silverlight. So I added the Polyline to the PieChartLabel’s template and simply changed its points in code. You can see the XAML containing the Polyline in the Silverlight Style in the previous section. The code that adds the points to the Polyline instead of creating a new Polyline can be found in the PositionConnected() method of PieChartLabel. This is a straightforward change, so I won’t show it here.
If you have Silverlight 3 installed, you can see a running version of this example in its own page or embedded below:
Download the Silverlight project (built with Silverlight 3).
Posted by Bea under Silverlight | Comments (12)
September 7, 2009
In my last blog post, I showed how you can use a custom control to add labels to a WPF pie chart. In this post I will discuss its implementation.
I started by thinking about what I wanted the usage syntax to look like. Ideally, I would like to create a LabeledPieSeries class that derives from PieSeries, and that exposes a LabelStyle property. This would make using a pie series with labels as easy as using any other chart series, which WPF chart users are already familiar with (the Codeplex charting overview shows what that syntax would look like). Unfortunately, PieSeries is currently sealed, so I had to search for another alternative.
Often, when faced with this design constraint, using a behavior is a good alternative. A behavior is simply an attached property that provides access to the element it’s attached to, and therefore to the whole tree it belongs to. I used a similar technique in my drag and drop blog post. More recently, Blend 3 has embraced this technique by providing support for applying behaviors at design time, and has generated a community around the creation of reusable behaviors. You can read more about Blend’s support for behaviors in Christian’s blog.
Behavior
The implementation of this behavior consists of an attached DependencyProperty “IsLabeled,” defined on a static class called PieLabelBehavior. This DP is intended to be attached to a PieDataPoint. When the DP is attached, the behavior creates a new label (instantiates a PieChartLabel), sets many of its properties, and walks the tree to add it to the desired location. Attaching the DP to the corresponding PieDataPoint provides an easy and reliable way to be notified when the PieDataPoints are created and added to the tree, and therefore ready to have the corresponding labels generated. Attaching the DP to PieSeries instead would have simplified setting the DP, since there would only be one place to set it, but it would have made it harder to know exactly when the PieDataPoints are added to the tree. Here is how the behavior is attached to each PieDataPoint:
<Style x:Key="LabeledPieDataPointStyle" TargetType="charting:PieDataPoint">
<Setter Property="local:PieLabelBehavior.IsLabeled" Value="True"/>
</Style>
<datavis:StylePalette x:Key="LabeledPieChartStylePalette">
<!–Blue–>
<Style TargetType="charting:PieDataPoint" BasedOn="{StaticResource LabeledPieDataPointStyle}">
…
</Style>
<!–Red–>
<Style TargetType="charting:PieDataPoint" BasedOn="{StaticResource LabeledPieDataPointStyle}">
…
</Style>
</datavis:StylePalette>
To avoid having neighboring wedges overlap a pie label, all labels had to be positioned in their own Canvas, which has a higher z-order than the PieDataPoint’s Canvas. My first attempt was to add the label’s Canvas to the PieSeries’ template. I added a Grid which had as its children the already existing PlotArea Canvas and my newly created Canvas that would contain the labels. The problem with this approach was that PieSeries clips itself, causing longer labels to be cut off. I attempted to have the actual pie chart occupy a smaller percentage of the PieSeries’ area, but unfortunately charting source code is setting the chart to take 95% of PieSerie’s space. Since this is percentage is not configurable, I had to find another solution. So, instead, I moved the label’s canvas to the Chart template. This fixed the clipping issue.
At this point, I have a way to inject code in a PieDataPoint when it’s added to the tree and I have a location to add my labels. The missing piece is the easiest part: when the PieDataPoint is added to the tree, I walk up the visual tree to find the label area Canvas, create a PieChartLabel and add it to the Canvas. This was done in the change handler for the IsLabeled attached DP (this handler was attached during the DP’s registration, as part of its PropertyMetadata):
private static void IsLabeledPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
bool isLabeled = (bool)e.NewValue;
if (isLabeled == true)
{
PieDataPoint pieDataPoint = obj as PieDataPoint;
if (pieDataPoint != null)
{
Chart chart = TreeHelper.FindAncestor<Chart>(pieDataPoint.Parent as DependencyObject);
if (chart != null)
{
Canvas labelArea = chart.Template.FindName("LabelArea_PART", chart) as Canvas;
if (labelArea != null)
{
AddLabel(pieDataPoint, labelArea);
}
}
}
}
}
The AddLabel method creates a new PieChartLabel, sets its properties – many of them reflecting information found in the corresponding PieDataPoint, which we have a handle to – and adds it as a child of the label area Canvas passed as a parameter. AddLabel also contains code that removes the label when the PieDataPoint is unloaded:
pieDataPoint.Unloaded += delegate
{
labelArea.Children.Remove(label);
};
The next step was to figure out how the labels were going to be positioned in the Canvas. I decided to have four “display modes”, which I explain in my previous blog post. Picking the desired display mode can be done through a DisplayMode property on PieChartLabel. Below I discuss the implementation of each mode.
ArcMidpoint mode

In order to implement this mode, I first needed to calculate the midpoint of the PieDataPoint’s arc. To get that information, I created a PieChartHelper static class containing a GetPieChartInfo method with the following signature:
public static bool GetPieChartInfo(Geometry geometry, out Point center, out Point arcMidpoint, out bool isArcSmall)
The “geometry” parameter is the wedge geometry of the corresponding PieDataPoint. This method returns three values: “center” which we’ll use in the Connected mode, “arcMidPoint” which is the midpoint of the arc needed for this mode, and “isArcSmall” which we’ll use in the AutoMixed mode. More on the Connected and AutoMixed modes later.
I won’t go into the implementation details for this method here. It requires a bit of understanding of math, and very little of WPF. If there’s interest, I’ll write a separate blog post for it – leave a comment or send me email if this interests you.
PieDataPoint exposes its Geometry through a DP, which is handy in this scenario. When I create a new PieChartLabel (in the behavior), I data bind PieChartLabel’s Geometry DP to the PieDataPoint’s Geometry. This ensures that a change in the PieDataPoint’s Geometry causes the PieChartLabel’s Geometry to change too. With this in mind, the change handler for PieChartLabel’s Geometry is a good location to call GetPieChartInfo:
private Point center;
private Point arcMidpoint;
public bool IsArcSmall { get; set; }
…
private static void GeometryPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
PieChartLabel label = obj as PieChartLabel;
if (label != null)
{
bool isArcSmall;
PieChartHelper.GetPieChartInfo(e.NewValue as Geometry, out label.center, out label.arcMidpoint, out isArcSmall);
label.IsArcSmall = isArcSmall;
}
}
Now that I have the arc midpoint, the next step is to position the label where I want it when the DisplayMode is set to “ArcMidpoint”. In the registration for the DisplayMode property, I specify that setting this property affects arrange, and ensured that the labels get repositioned during arrange. In the explanation for Auto mode it will become clear why I decided to reposition labels on arrange, instead of simply calling the PositionLabel method from within the DisplayMode’s change handler.
protected override Size ArrangeOverride(Size arrangeBounds)
{
this.PositionLabel();
return base.ArrangeOverride(arrangeBounds);
}
private void PositionLabel()
{
switch (this.DisplayMode)
{
case DisplayMode.ArcMidpoint:
this.PositionArcMidpoint();
break;
case DisplayMode.Connected:
this.PositionConnected();
break;
case DisplayMode.AutoMixed:
this.PositionAutoMixed();
break;
case DisplayMode.Auto:
this.PositionAuto();
break;
}
}
With this code in place, the method that actually centers the label on the arc midpoint is very simple:
private void PositionArcMidpoint()
{
this.RemovePolyline();
if (this.contentPart != null)
{
Canvas.SetTop(this.contentPart, this.arcMidpoint.Y – 0.5 * this.contentPart.DesiredSize.Height);
Canvas.SetLeft(this.contentPart, this.arcMidpoint.X – 0.5 * this.contentPart.DesiredSize.Width);
}
}
I will explain in the next section why I call RemovePolyline() at the beginning of this method.
Connected mode

To create the line that connects the wedge to the label in this mode, we have to obtain the coordinates for each of its three points. We already have the first point – it’s the arc midpoint.
The second point should be placed on the line defined by the center of the chart and the arc midpoint. This is why I am returning the center of the chart from the GetPieChartInfo method. I decided that the second point would be 10 pixels away from the arc midpoint, in the outward direction. You may want to change this value or provide a DP to make it configurable.
The third point is placed 20 pixels away from the second point, on the X axis. If the wedge is on the right side of the chart the point is placed towards the right, otherwise it’s placed towards the left.
private void PositionConnected()
{
this.RemovePolyline();
if (this.contentPart != null)
{
PointCollection newPoints = new PointCollection();
// First point
newPoints.Add(this.SnapPoint(this.arcMidpoint));
// Second point
Vector radialDirection = this.arcMidpoint – this.center;
radialDirection.Normalize();
Point secondPoint = this.arcMidpoint + (radialDirection * 10);
newPoints.Add(this.SnapPoint(secondPoint));
// Third point
int sign = Math.Sign(radialDirection.X); // 1 if label is on the right side, -1 if it’s on the left.
Point thirdPoint = secondPoint + new Vector(sign * 20, 0);
newPoints.Add(this.SnapPoint(thirdPoint));
double contentX = (sign == 1) ? thirdPoint.X : thirdPoint.X – this.contentPart.DesiredSize.Width;
double contentY = thirdPoint.Y – 0.5 * this.contentPart.DesiredSize.Height;
Canvas.SetTop(this.contentPart, contentY);
Canvas.SetLeft(this.contentPart, contentX);
Polyline polyline = new Polyline();
polyline.Points = newPoints;
polyline.SetBinding(Polyline.StrokeThicknessProperty, new Binding("LineStrokeThickness") { Source = this });
polyline.SetBinding(Polyline.StrokeProperty, new Binding("LineStroke") { Source = this });
polyline.StrokeLineJoin = PenLineJoin.Round;
this.canvasPart.Children.Add(polyline);
}
}
You may be wondering why I am creating a new Polyline every time the line needs to be updated. It would be more efficient to add a Polyline to the PieChartLabel’s ControlTemplate, and simply replace its points in this method. Unfortunately, a WPF rendering bug prevents that solution from working. This is why we need to call the RemovePolyline() method every time we’re about to reposition a label.
AutoMixed mode

The AutoMixed mode uses the size of the arc to decide how to position the label: if the arc is small, it uses the Connected mode, otherwise it uses the ArcMidpoint mode. The GetPieChartInfo helper method returns an “isArcSmall” boolean value indicating which mode to use:
private void PositionAutoMixed()
{
if (this.IsArcSmall)
{
this.PositionConnected();
}
else
{
this.PositionArcMidpoint();
}
}
Auto mode
In the Auto mode, if at least one wedge is small, all labels are positioned using the Connected mode. Otherwise, they are all positioned using the ArcMidpoint mode. This behavior is tricky to implement. The positioning mode decision can not be made at the label level because it requires knowledge of the arc size of all other PieDataPoints.
The canvas containing all the labels is a perfect place to decide how each label should be positioned because it knows about all labels. So, I replaced that canvas with a custom control deriving from Canvas which I called PieChartLabelArea. Then I added a property called HasSmallArc which is true if at least one wedge is small. This DP is set in the MeasureOverride method:
protected override Size MeasureOverride(Size constraint)
{
bool hasSmallArc = false;
foreach (PieChartLabel label in this.Children.OfType<PieChartLabel>())
{
if (label.IsArcSmall)
{
hasSmallArc = true;
break;
}
}
this.HasSmallArc = hasSmallArc;
return base.MeasureOverride(constraint);
}
When HasSmallArc changes, its change handler is called, which invalidates arrange for all labels.
private static void HasSmallArcPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
PieChartLabelArea labelArea = obj as PieChartLabelArea;
if (labelArea != null)
{
foreach (PieChartLabel label in labelArea.Children.OfType<PieChartLabel>())
{
label.InvalidateArrange();
}
}
}
We’ve already seen previously that PieChartLabel’s ArrangeOverride re-positions the label.
Let’s run through a common execution example. Imagine that during MeasureOverride the PieChartLabelArea control discovers that one of its labels has a small arc. The label area’s IsSmallArc is set to true, which causes its change handler to execute, which invalidates arrange on the labels, which causes all labels to be re-positioned using the Auto mode. At this point, label area’s HasSmallArc is set to true, and all labels can walk the tree to get to it. With this in mind, the following code of PieChartLabel should be easy to understand:
private void PositionAuto()
{
Chart chart = TreeHelper.FindAncestor<Chart>(this);
if (chart != null)
{
PieChartLabelArea labelArea = chart.Template.FindName("LabelArea_PART", chart) as PieChartLabelArea;
if (labelArea != null && labelArea.HasSmallArc)
{
this.PositionConnected();
}
else
{
this.PositionArcMidpoint();
}
}
}
The style for the Chart is identical to the default style, but with the PieChartLabelArea control added to it, on top of all other elements:
<Style x:Key="LabeledPieChartInnerChartStyle" TargetType="charting:Chart">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="charting:Chart">
…
<chartingprimitives:EdgePanel x:Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">
<Grid Canvas.ZIndex="-1" Style="{TemplateBinding PlotAreaStyle}" />
<Border Canvas.ZIndex="10" BorderBrush="#FF919191" BorderThickness="1" />
<local:PieChartLabelArea x:Name="LabelArea_PART" Canvas.ZIndex="11" Margin="10" SnapsToDevicePixels="True" />
</chartingprimitives:EdgePanel>
…
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The chart custom control
Most of the label functionality is working at this point, but I still don’t have a way to specify the Style for the labels. Ideally, I would derive from the Chart control, add a LabelStyle DP to my custom chart control, and provide the style above (with the PieChartLabelArea) as its default style. Unfortunately, Chart is also sealed, so I had to think of other alternatives.
I could have added another attached property containing the LabelStyle, to be applied to the Chart control. If I had done this, usage of a pie chart with labels would require: 1) creating a chart with a PieSeries in XAML as usual, 2) setting the Chart’s style to the Style that contains PieChartLabelArea, 3) setting PieSeries’ StylePalette to the style that attaches IsLabeled to each PieDataPoint (I show this Style in the Behavior section), and 4) setting the LabelStyle attached DP on the Chart itself. I wasn’t happy with the complexity of this option – I wanted to shield the developer from some of this complexity.
Instead, I opted to create a custom control (derived from Control) containing the Chart and PieSeries in its template. I called it LabeledPieChart:
<Style TargetType="{x:Type local:LabeledPieChart}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LabeledPieChart}">
<charting:Chart x:Name="Chart_PART" Title="{TemplateBinding Title}" Style="{StaticResource LabeledPieChartInnerChartStyle}" BorderBrush="{TemplateBinding BorderBrush}">
<charting:Chart.Series>
<charting:PieSeries x:Name="PieSeries_PART" ItemsSource="{TemplateBinding ItemsSource}" StylePalette="{StaticResource LabeledPieChartStylePalette}" IsSelectionEnabled="{TemplateBinding IsSelectionEnabled}" SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, Path=SelectedItem}" />
</charting:Chart.Series>
</charting:Chart>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
LabeledPieChart exposes a property called PieChartLabelStyle where the developer can specify the look for all labels. For added convenience, it also exposes a PieChartLabelItemTemplate DP to specify the DataTemplate. If these are not set I provide a reasonable default look.
The upside of implementing this custom control is that the developer doesn’t have to worry about attaching DPs and setting Styles to use a labeled pie chart. The downside is that all properties in Chart and PieSeries that would allow the developer to customize the chart are now hidden. One solution is to expose all of those properties in LabeledPieChart and template bind them to the ones in Chart and PieSeries. I exposed a few properties that I found most useful, as an example. As you use this control you may need to expose other properties.
Selection
Last, I wanted to enable selection for the PieChart, and I wanted to make clicking a label have the same effect as clicking the associated PieDataPoint.
I started by exposing the IsSelectionEnabled property of PieSeries in my LabeledPieChart control. Enabling selection is as easy as setting this DP on the control:
<customControls:LabeledPieChart IsSelectionEnabled="True" … />
I also had to expose the SelectedItem property, but with a slightly different behavior. For this property, I want changes in the LabeledPieChart’s SelectedItem to be reflected in the PieSeries’ SelectedItem and vice versa. If this property is set by the user the UI needs to reflect it, and if it’s set by internal code it needs to return the right value to the user. This is why I use a two-way binding to the templated parent, as you can see in the LabeledPieChart’s default Style above. With this in place, I only had to make sure that clicking on the label would change SelectedItem, which was done with the following code in PieChartLabel:
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
Chart chart = TreeHelper.FindAncestor<Chart>(this);
if (chart != null)
{
PieSeries pieSeries = chart.Series.OfType<PieSeries>().FirstOrDefault();
if (pieSeries != null)
{
pieSeries.SelectedItem = this.Content;
}
}
}
Summary
By helping you understand this implementation of pie charts labels, my goal is to inspire you to extend this code and adapt it to your particular scenario. And hopefully you learned some new tricks that will help you in other scenarios with similar technical problems.
After I wrote my last blog post, I fixed a couple of bugs in the code, so if you’re using the old code make sure you download it again.
In my next blog post, I will talk about the experience of porting this code to Silverlight.
You can see this sample running as an xbap or download its source.
Posted by Bea under WPF | Comments (7)