OK - here is a programmatic solution to the problem. I hope this might save someone else 3 or 4 days of experimentation.
1. The first thing is to set up a class to represent the data that is going to be viewed - the fact that this control is called a TreeView is, I think, no accident. It is quite different to a Win32/WinForms control. As my original question concerned email
let's stick with that. Here is the class that we'll use.
// this will be displayed in the Silverlight TreeView
public class EmailItem
{
// constructor takes various arguments as one might expect
public EmailItem(string Title, string From,DateTime Date)
{
// by default a root item so no parent
this.Parent = null;
// the observable collection is very important here, it means that an instance of this class
// will get notified if the collection changes,
// as well as the container getting notified of changes - see the Add() code later.
this.Items = new ObservableCollection<EmailItem>();
// this is for sanity checking and general proof of understanding
this.Items.CollectionChanged +=
new NotifyCollectionChangedEventHandler(Items_CollectionChanged);
// assign the constructor parameters
this.Title = Title;
this.From = From;
this.Date = Date;
}
// the collection has changed - just spew a debug message for the moment
void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (sender != null)
{
Debug.WriteLine("Items_CollectionChanged: " + sender.ToString() + "(" + e.Action.ToString() + ")");
}
}
// our email properties.
public string Title { get; set; }
public string From { get; set; }
public DateTime Date { get; set; }
public EmailItem Parent { get; set;} // the parent emailItem - enables us to work back up through the hierarchy
// this is special - this collection will contain all of the
// emails which are children in the hierachy. Imagine them to be
// the collected replies to a specific email. The depth of the
// hierarchy is bounded only by systems limits.
public ObservableCollection<EmailItem> Items { get; set; }
}
2. Now we need to tell the tree view what is it going to display. The XAML here totallly dictates the appearance of the item in the view. This XAML snippet places a tree view on the layout and determines the content. Somewhere you will need to have added
a namespace to the XAML referencing the Silverlight toolkit (SLTK)
In English then, we've got a TreeView called trvEmails with an event handler called ctlTree_SelectedItemChanged that is invoked when the selection changes in the tree.
Next comes the clever/tricky bit - the ItemTemplate. This contains a HierarchicalDataTemplate declaration which has as it source binding a property called Items. This is bound directly to the Items property in the EmailItem class. Importantly Items is not
directly viewable - the bits of the email message we wish to display is specified by the StackPanel. Note I've chosen to orientate this horizontally - this means we get a columnar layout for the EmailItem with the content described by the 3 labels that follow.
Note that each label is bound to a separate property of the EmailItem class - in turn Title, From and Date. Just to show we are in control, the date is drawn as greyed text.
That, as they say, is that. Let's move on to the code.
3. Now if you have added the C# code and the XAML markup to a project, the tree, at runtime, will be empty. Here's some code to populate the tree:
// I've added this code to the page constructor
public Page()
{
InitializeComponent();
// add the first new email item to the root collection
EmailItem emi = new EmailItem("Email 1","Root", DateTime.Now);
trvEmails.Items.Add(emi);
// add a 'reply' to the first email
emi.Items.Add(new EmailItem("Email 1.1", "abc", DateTime.Now));
// add another root item
emi = new EmailItem("Email 2", "Root", DateTime.Now);
// insert it into the view collection
trvEmails.Items.Add(emi);
// and add a reply to the second item
emi.Items.Add(new EmailItem("Email 2.1", "abc",DateTime.Now));
// finally a third root item
trvEmails.Items.Add(new EmailItem("Email 3", "def", DateTime.Now));
}
This should be pretty self explanatory - what you get then is a tree that initially looks like this - note the arrows indicating that the item has children. Magic!
and here is the tree with the items expanded:
4. Neat. Let's code up the event handler that is called when the tree selection changes:
private void ctlTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
Debug.WriteLine("ctlTree_SelectedItemChanged");
EmailItem oldItem = e.OldValue as EmailItem;
EmailItem newItem = e.NewValue as EmailItem;
// _selectedEmailItem is declared within the class as:
// private EmailItem _selectedEmailItem;
_selectedEmailItem = newItem;
// see next paragraph
btnAdd.IsEnabled = (_selectedEmailItem != null);
if (oldItem != null)
{
Debug.WriteLine("Old: " + oldItem.Title);
}
if (newItem != null)
{
EmailItem emi = newItem;
while (emi != null)
{
Debug.WriteLine("Walking back to root: " + emi.Title);
emi = emi.Parent;
}
}
}
This handler updates an EmailItem instance (_selectedEmailItem) that is declared within the page code. This makes life a bit simpler in a moment,as I hope to show. Once we are sure we've updated the _selectedEmailItem we do a little bit more work to ensure
we've actually tamed the beast and it is working as we expect. Caveat: I'm not sure this is 100% neccessary. There may be a more generic navigation mechanism built into the TreeViewItem but I've not found it yet. What we do when the selection changes is to
walk back up the tree until the item under examination has a null parent - at each stage we print out the email title.
5. Now, in the case of the items we have inserted so far, we have not set the Parent property. I've skipped over it entirely This might be useful if you want to refer back to the email in your reply for example. Assume a button called btnAdd has been added
to the XAML and an suitable handler added to the C# code. Here's what the handler will do:
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
// invariant - cannot be null if button enabled
if (_selectedEmailItem != null)
{
// this new email is from jerry
EmailItem emi = new EmailItem("re:" + _selectedEmailItem.Title,"Jerry", DateTime.Now);
// and its parent is the current selection
emi.Parent = _selectedEmailItem;
// add the new child to the collection in the parent
_selectedEmailItem.Items.Add(emi);
}
}
This should be adequately commented - we add a new item to the tree as a child of the current selection. What is pretty clever is that at no point is the TreeView or any TreeViewItems involved. But, despite this absence of an explicit link, the action of
adding the email will ensure the view is updated, with the modified item now displaying the 'i've got child items' arrow. See green callouts.
6. Finally. That covers the essentials and answers my initial question. Hope you find this useful. Please feel free to comment/query/criticise as you see fit. I'd appreciate it if the latter was constructive in nature!
Je8
Member
54 Points
50 Posts
Re: Toolkit treeview questions
Nov 01, 2008 06:23 PM | LINK
OK - here is a programmatic solution to the problem. I hope this might save someone else 3 or 4 days of experimentation.
1. The first thing is to set up a class to represent the data that is going to be viewed - the fact that this control is called a TreeView is, I think, no accident. It is quite different to a Win32/WinForms control. As my original question concerned email let's stick with that. Here is the class that we'll use.
// this will be displayed in the Silverlight TreeView
public class EmailItem
{
// constructor takes various arguments as one might expect
public EmailItem(string Title, string From,DateTime Date)
{
// by default a root item so no parent
this.Parent = null;
// the observable collection is very important here, it means that an instance of this class
// will get notified if the collection changes,
// as well as the container getting notified of changes - see the Add() code later.
this.Items = new ObservableCollection<EmailItem>();
// this is for sanity checking and general proof of understanding
this.Items.CollectionChanged +=
new NotifyCollectionChangedEventHandler(Items_CollectionChanged);
// assign the constructor parameters
this.Title = Title;
this.From = From;
this.Date = Date;
}
// the collection has changed - just spew a debug message for the moment
void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (sender != null)
{
Debug.WriteLine("Items_CollectionChanged: " + sender.ToString() + "(" + e.Action.ToString() + ")");
}
}
// our email properties.
public string Title { get; set; }
public string From { get; set; }
public DateTime Date { get; set; }
public EmailItem Parent { get; set;} // the parent emailItem - enables us to work back up through the hierarchy
// this is special - this collection will contain all of the
// emails which are children in the hierachy. Imagine them to be
// the collected replies to a specific email. The depth of the
// hierarchy is bounded only by systems limits.
public ObservableCollection<EmailItem> Items { get; set; }
}
2. Now we need to tell the tree view what is it going to display. The XAML here totallly dictates the appearance of the item in the view. This XAML snippet places a tree view on the layout and determines the content. Somewhere you will need to have added a namespace to the XAML referencing the Silverlight toolkit (SLTK)
xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
So any SLTK controls are prefixed with a controls: namespace qualifier
<controls:TreeView Margin="8,80,102,78" x:Name="trvEmails" SelectedItemChanged="ctlTree_SelectedItemChanged">
<controls:TreeView.ItemTemplate>
<controls:HierarchicalDataTemplate x:Name="EmailItemTemplate" ItemsSource="{Binding Items}">
<StackPanel Orientation="Horizontal">
<controls:Label Width="80" Content="{Binding Title}"/>
<controls:Label Width="80" Content="{Binding From}"/>
<controls:Label Foreground="Gray" Content="{Binding Date}"/>
</StackPanel>
</controls:HierarchicalDataTemplate>
</controls:TreeView.ItemTemplate>
In English then, we've got a TreeView called trvEmails with an event handler called ctlTree_SelectedItemChanged that is invoked when the selection changes in the tree.
Next comes the clever/tricky bit - the ItemTemplate. This contains a HierarchicalDataTemplate declaration which has as it source binding a property called Items. This is bound directly to the Items property in the EmailItem class. Importantly Items is not directly viewable - the bits of the email message we wish to display is specified by the StackPanel. Note I've chosen to orientate this horizontally - this means we get a columnar layout for the EmailItem with the content described by the 3 labels that follow. Note that each label is bound to a separate property of the EmailItem class - in turn Title, From and Date. Just to show we are in control, the date is drawn as greyed text.
That, as they say, is that. Let's move on to the code.
3. Now if you have added the C# code and the XAML markup to a project, the tree, at runtime, will be empty. Here's some code to populate the tree:
// I've added this code to the page constructor
public Page()
{
InitializeComponent();
// add the first new email item to the root collection
EmailItem emi = new EmailItem("Email 1","Root", DateTime.Now);
trvEmails.Items.Add(emi);
// add a 'reply' to the first email
emi.Items.Add(new EmailItem("Email 1.1", "abc", DateTime.Now));
// add another root item
emi = new EmailItem("Email 2", "Root", DateTime.Now);
// insert it into the view collection
trvEmails.Items.Add(emi);
// and add a reply to the second item
emi.Items.Add(new EmailItem("Email 2.1", "abc",DateTime.Now));
// finally a third root item
trvEmails.Items.Add(new EmailItem("Email 3", "def", DateTime.Now));
}
This should be pretty self explanatory - what you get then is a tree that initially looks like this - note the arrows indicating that the item has children. Magic!
and here is the tree with the items expanded:
4. Neat. Let's code up the event handler that is called when the tree selection changes:
private void ctlTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
Debug.WriteLine("ctlTree_SelectedItemChanged");
EmailItem oldItem = e.OldValue as EmailItem;
EmailItem newItem = e.NewValue as EmailItem;
// _selectedEmailItem is declared within the class as:
// private EmailItem _selectedEmailItem;
_selectedEmailItem = newItem;
// see next paragraph
btnAdd.IsEnabled = (_selectedEmailItem != null);
if (oldItem != null)
{
Debug.WriteLine("Old: " + oldItem.Title);
}
if (newItem != null)
{
EmailItem emi = newItem;
while (emi != null)
{
Debug.WriteLine("Walking back to root: " + emi.Title);
emi = emi.Parent;
}
}
}
This handler updates an EmailItem instance (_selectedEmailItem) that is declared within the page code. This makes life a bit simpler in a moment,as I hope to show. Once we are sure we've updated the _selectedEmailItem we do a little bit more work to ensure we've actually tamed the beast and it is working as we expect. Caveat: I'm not sure this is 100% neccessary. There may be a more generic navigation mechanism built into the TreeViewItem but I've not found it yet. What we do when the selection changes is to walk back up the tree until the item under examination has a null parent - at each stage we print out the email title.
5. Now, in the case of the items we have inserted so far, we have not set the Parent property. I've skipped over it entirely This might be useful if you want to refer back to the email in your reply for example. Assume a button called btnAdd has been added to the XAML and an suitable handler added to the C# code. Here's what the handler will do:
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
// invariant - cannot be null if button enabled
if (_selectedEmailItem != null)
{
// this new email is from jerry
EmailItem emi = new EmailItem("re:" + _selectedEmailItem.Title,"Jerry", DateTime.Now);
// and its parent is the current selection
emi.Parent = _selectedEmailItem;
// add the new child to the collection in the parent
_selectedEmailItem.Items.Add(emi);
}
}
This should be adequately commented - we add a new item to the tree as a child of the current selection. What is pretty clever is that at no point is the TreeView or any TreeViewItems involved. But, despite this absence of an explicit link, the action of adding the email will ensure the view is updated, with the modified item now displaying the 'i've got child items' arrow. See green callouts.
6. Finally. That covers the essentials and answers my initial question. Hope you find this useful. Please feel free to comment/query/criticise as you see fit. I'd appreciate it if the latter was constructive in nature!
Jerry
tree view c# cod insert runtime