This is the first post in a series with a simple, yet specific goal: to add editing capabilities to Silverlight Toolkit’s TreeView control and build a MVVM (Model-View-ViewModel) pattern application with it.
This introductory post will deal with really basic stuff. Nothing new and fancy, we’re just going to set up the grounds for a simple application that displays a hierarchical collection of some help topics in a TreeView.
We’ll begin with finding the suitable hierarchical structure to put in the TreeView. For the purpose of this sample, I copied some content from Silverlight SDK documentation:
First, we’ll need a data generator that will create the data structure. In a real world scenario, you would call a web service to get such data, but we’ll get to that a bit later. For now, this dummy data generator will have to do:
public static class DataGenerator
{
public ObservableCollection<HelpTopic> GetHelpTopics()
{
return new ObservableCollection<HelpTopic>()
{
new HelpTopic() { Name = "Debugging, Error Handling, and Exceptions", SubTopics =
new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Debugging Overview", SubTopics =
new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Walkthrough: Setting Up Remote Debugging on the Macintosh"}
}},
new HelpTopic() {Name = "Error Handling"},
new HelpTopic() {Name = "Exceptions", SubTopics=new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Exception Class and Properties"},
new HelpTopic() {Name = "Exception Hierarchy"},
new HelpTopic() {Name = "Exception Handling"},
new HelpTopic() {Name = "Best Practices for Handling Exceptions"}
}}
}},
new HelpTopic() { Name = "Deployment and Localization", SubTopics =
new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Creating Globally Aware Applications"},
new HelpTopic() {Name = "Localizing Silverlight Applications"}
}},
new HelpTopic() { Name = "Performance", SubTopics = new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Performance Tips"},
new HelpTopic() {Name = "How To: Use a Background Worker"},
new HelpTopic() {Name = "Threading", SubTopics = new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Managed Threading Overview"},
new HelpTopic() {Name = "Best Practices for Managed Threading"},
new HelpTopic() {Name = "Threading Objects and Features"}
}},
}}
};
}
}
Looks ugly, but there is really just one entity class involved – here is the HelpTopics class:
public class HelpTopic
{
public string Name { get; set; }
public ObservableCollection<HelpTopic> SubTopics { get; set; }
public HelpTopic()
{
}
}
The name property will hold the text to be displayed in a tree, and SubTopics is a collection of child items. ObservableCollection plays a significant role in MVVM applications because of its ability to inform potential listeners that the collection has changed in some way (item was added, changed, or list was cleared, etc.). With MVVM, one potential listener is the User Interface (visual elements in your application), which can react to any changes, made in data which it’s bound to.
The HelpTopic class currently doesn’t hold any unique Id field. I intentionally left that out for the moment; it will be added when needed.
OK, we have our Model. What we need next is the ViewModel. A ViewModel is a bridge between the View and the Model and acts like a Model to the View. Hence the name :)
Since it’s going to serve the main page, we’ll call it – the PageViewModel:
public class PageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<HelpTopic> helpTopics;
public ObservableCollection<HelpTopic> HelpTopics
{
get { return helpTopics; }
private set
{
if (helpTopics == value)
{
return;
}
helpTopics = value;
OnPropertyChanged("HelpTopics");
}
}
public PageViewModel()
{
HelpTopics = DataGenerator.GetHelpTopics();
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The ViewModel only has one property [HelpTopics] because that’s currently the only one we need. Notice its private setter – we’re allowing setting this property from within the ViewModel only. Also, until we implement a better method to retrieve our data, we’re initializing the HelpTopics data collection directly in ViewModel’s constructor.
The ViewModel implements the INotifyPropertyChanged interface to notify its listeners when one of its properties has changed. Similar to what ObservableCollection does, but this one’s for a single class.
To finish this, all we have left is to compose a View. We’ll populate it with a TreeView control, set it to up to display our topics and create an instance of our PageViewModel for the DataContext:
<Grid x:Name="LayoutRoot">
<Grid.DataContext>
<local:PageViewModel />
</Grid.DataContext>
<slt:TreeView VerticalAlignment="Stretch" Margin="20" Width="300" ItemsSource="{Binding HelpTopics}">
<slt:TreeView.ItemTemplate>
<slt:HierarchicalDataTemplate ItemsSource="{Binding SubTopics}">
<TextBlock Text="{Binding Name}" />
</slt:HierarchicalDataTemplate>
</slt:TreeView.ItemTemplate>
</slt:TreeView>
</Grid>
The PageViewModel instance is set as a DataContext of the topmost page element (Grid), which means it will serve all page’s elements. One of its consumers is the TreeView control, which takes whatever is in its HelpTopics property for its items source. The last thing we had to do is set up a HierarchicalDataTemplate, specifying the property holding the subitems, and the visual of the tree item – a simple TextBlock with its Text property bound to the HelpTopic class’ Name property will suffice.
This is what our TreeView control looks now. Far from being editable or anything yet, but except coding the dummy data generator and a class with one property, we hardly did any coding; And that’s the power of the MVVM pattern.
Next in the series: modifying the data structure.
The source code for this sample is available:
0013c54a-b08e-462f-a402-eb4ad7562cb9|10|4.6|27604f05-86ad-47ef-9e05-950bb762570c