Introduction
The aim of this article is to serve as a short introduction to the new ClassifiedCabinet control published on codeplex. I tried to give you everything that is necessary to start using the control, without going into too much detail. I’ll be building a very simple project from scratch that should demonstrate the basic usage of the control. Of course, the source code of the sample is available for download. By the way here is a live demo of what we want to achieve:
View sample live demo
Download sample source code
Set Up the Project
OK, so let’s get started. First we need to create a new Silverlight project. I’ve called it “ClassifiedCabinetDemo”. Then you need to add a reference to the CompletIT.Windows.Controls.ClassifiedCabinet.dll. To keep things as simple as possible, I just copied the file to the “Debug” folder of the demo project and added the reference from there. Now you can just go to the MainPage.xaml, add the CompletIT.Windows.Controls namespace from the CompletIT.Windows.Controls.ClassifiedCabinet assembly and start using the control right away.
<UserControl x:Class="ClassifiedCabinetDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:completITControls="clr-namespace:CompletIT.Windows.Controls;assembly=CompletIT.Windows.Controls.ClassifiedCabinet"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="900">
<Grid x:Name="LayoutRoot"
Background="White">
<completITControls:ClassifiedCabinet Width="900"
Height="600" />
</Grid>
</UserControl>
This is the code of the MainPage.xaml so far. I’ve added the xml namespace and the ClassifiedCabinet control itself. However, this is what it looks like:
It seems we’ll need some extra work to make it do something useful, since right now the control is just notifying the user that there is nothing to display. It’s not very impressive, isn’t it? Well, let’s give it some actual data to display and see what happens.
Adding the Categories
I have created an object to hold all the data we need and set it as the page’s data context, so we can just bind the necessary properties. First you need to set the CategoriesItemsSource. As its name suggests, it is a collection containing the categories. After giving the the ClassifiedCabinet control its categories, you’ll need to tell it how to display them. Basically, you have the standard means to do so – you have an option to set the path to a member to display using CategoriesDisplayMemberPath or if you need something more complicated you can change the whole DataTemplate using the CategoriesItemTemplate property. And just a quick note - as it is the case with other such controls, you can’t use both at the same time as it makes no sense, so just choose whatever seems more appropriate. There are other properties that allow you to fine-tune the look-and-feel of the categories section, but I shall look at these in some of the next articles, so we can keep this quick start really quick. For now it is enough to say that they’re all prefixed with “Categories” and have pretty intuitive names, so if you want you can go ahead and play with the control a little to discover them. Actually, we are going to use one more property now – CategoriesWidth, but I think you can all figure out what it does. Let’s see what we have got so far:
<completITControls:ClassifiedCabinet Width="900"
Height="600"
CategoriesItemsSource="{Binding Categories}"
CategoriesDisplayMemberPath="Name"
CategoriesWidth="250" />
As you can see the main page’s data context has a property called Categories which is basically a collection of Category objects. Right now every category has just a name and that’s what we are using to display it. Let’s see what we have done:
The categories section is ready and it looks kind of nice, doesn’t it? However, the items section is still empty – no shelves yet. OK, let’s see what we can do about it.
Adding the Items
Well, no surprises here. We have an ItemsSource property that we can use to give the ClassifiedCabinet its items. Of course, we’ll need to define an appropriate data template to tell it, how we want it to visualize them, so there is an ItemTemplate property as well. I have bound the ItemsSource property to a collection of Book objects in my data context. The Book object is very simple – it has just a Title, Author, PublishDate and Description properties that are all of type string for simplicity. My DataTemplate for this book is just a border and a TextBlock with the book’s title – nothing fancy. Here it is:
<DataTemplate x:Key="ClassifiedCabinetItemTemplate">
<Border Background="DeepSkyBlue"
BorderBrush="DodgerBlue"
BorderThickness="3"
Width="150"
Height="200">
<TextBlock Text="{Binding Title}"
TextAlignment="Center"
VerticalAlignment="Center"
FontFamily="Verdana"
Foreground="White"
FontSize="14"
Margin="10"
TextWrapping="Wrap" />
</Border>
</DataTemplate>
And that’s all. You can now run the project and you’ll have all the shelves populated and the ClassifiedCabinet has even added appropriate labels to every shelf. You can also zoom and move around with the mouse – isn’t it cool?
However, we are not done, yet. There are few more things we need to take care of. You may have noticed that the books are not associated with particular categories, but instead the cabinet seems to have placed all the books on every shelf. Well, that is perfectly normal, since we haven’t yet told it how we want it to “classify” them. However, we’ll do this in the next section, while now we’ll look at something else that appears to be missing. If you click on an item in the cabinet, you get a fancy dialog that contains …nothing. Luckily, there is a property for that. It’s called ItemDetailsTemplate and it is another DataTemplate for the book that’s expected to give more details about it and is used to present the item in the dialog. You may have noticed that the book data class has four properties, but we are using only one of them. Well, now we’ll be adding the rest three. So the idea is that the DataContext for both DataTemplates is the same – an object from the collection you bind the cabinet’s ItemsSource to (in our case a Book), but you define two data templates – one to be used while the item is on the shelf and another, when the user clicked on the item and opened the dialog to see something more about a particular item. By the way, while the author and publish date are correct, the description of the book is always the same lorem-ipsum – I didn’t want to have too much sample data in the project. Finally, here’s our template for the dialog:
<DataTemplate x:Key="ClassifiedCabinetItemDetailsTemplate">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontSize="18"
TextWrapping="Wrap"
Text="{Binding Title}" />
<TextBlock Grid.Row="1"
FontSize="12"
FontStyle="Italic"
Text="{Binding Author, StringFormat='By \{0}'}" />
<TextBlock Grid.Row="2"
FontSize="12"
FontStyle="Italic"
Text="{Binding PublishDate, StringFormat='Released on \{0}'}" />
<TextBlock Grid.Row="3"
FontSize="12"
Margin="15"
TextWrapping="Wrap"
Text="{Binding Description}" />
</Grid>
</DataTemplate>
Here’s how it looks in action:
OK, so that’s all you need to add the items to the cabinet. However, they are not classified as we have already mentioned. We wanted different items in the different categories, right? Let’s see how it is done.
Classifying the Items
Basically, it’s really easy - you only have to set a property called ClassifierFunc, which is a function that controls the whole process. The property is of type Func
public IEnumerable Classify(object category, IEnumerable items);
Now the ClassifiedCabinet will call the function every time you change its ItemsSource or CategoriesItemsSource changes, to classify the items. The first argument is the category object and you can safely cast it to the type of your category. The second is an enumeration containing the items that have to be classified. Note that it doesn’t necessary contain all the items. The function has to return another enumeration that contains just these items that belong to the specified category. In a sense, it filters the items according to provided category. Here’s an example:
public IEnumerable Classify( object category, IEnumerable books )
{
return books.Cast<Book>().Where( book => book.Category == category );
}
It’s quite simple, isn’t? I added a property Category to the Book class and I just return the books that say they belong to the category in question. In reality, you may want to associate a book with more than one category. This is easy as well, but for the sake of simplicity we’ll stick to the one-category version in this example. Having the function defined like this, now you need a property that wraps the function for use in binding. The type of the property, of course has to be Func
So what’s left? Well, not much actually, we are almost done. The only thing that still doesn’t work is the filter that’s located on top of the categories.
Filtering the items
The filtering is actually quite similar to the classifying. In fact, the only difference is that the filtering function receives the current input from the user instead of the category as a first argument and should return the items that pass this filter according to its custom logic. In our case we want to return all books that contain the user input, either in the title or in the author fields. We are performing a case-insensitive search. Here’s the code of the function, along with its property wrapper required for data-binding:
public Func<string, IEnumerable, IEnumerable> FilterFunc
{
get
{
return this.Filter;
}
}
private IEnumerable Filter( string userInput, IEnumerable books )
{
string uppercaseUserInput = userInput.ToUpper( CultureInfo.CurrentUICulture );
return books.Cast<Book>().Where( book =>
book.Title.ToUpper( CultureInfo.CurrentUICulture ).Contains( uppercaseUserInput ) ||
book.Author.ToUpper( CultureInfo.CurrentUICulture ).Contains( uppercaseUserInput ) );
}
After you bind the actual property of the control, you can run the project and test the filtering functionality of the ClassifiedCabinet. For example I have typed ‘game’ and here’s what I got:
As you can see there are only three books on this topic right now. Also the shelves that don’t have any books related to the search have disappeared and their respective categories in the categories menu are now disabled. Just in case, let’s add post the final version of the xaml:
<completITControls:ClassifiedCabinet Width="900"
Height="600"
CategoriesItemsSource="{Binding Categories}"
CategoriesDisplayMemberPath="Name"
CategoriesWidth="250"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource ClassifiedCabinetItemTemplate}"
ItemDetailsTemplate="{StaticResource ClassifiedCabinetItemDetailsTemplate}"
ClassifierFunc="{Binding ClassifierFunc}"
FilterFunc="{Binding FilterFunc}" />
Conclusion
Well, that is all you need to start using the ClassifiedCabinet control. As you can imagine, it’s quite a huge control that has a lot of options, supports a lot of different scenarios and with little imagination can be used in a lot of different situations.
Since this was intended as a short introductory article, there is a lot of things left and I’ll be writing a few more articles to cover some of the more advanced usages of the classified cabinet, but I hope that this’ll be enough to get you all started.
Also I hope you like the control. I think it’s really cool and can do a lot of things, but I ‘m really looking forward to getting some actual feedback and I’ll be interested to know what you think about it.