Silverlight TreeView with Crud
Introduction
This is a code sample for a Silverlight TreeView
Control which supports CRUD ( Create, Read, Update, Delete ) operations. In addition it supports Drag & Drop of items. Our final output will look something like this.
Background
This post assumes that you have atleast a nodding acquaintance with Silverlight and Data Binding
Using the code
Data
First lets us have a look at the data structure which is bound to theTreeView
control. Node
is the class, whose instance is bound to each TreeViewItem
. Text
represents the data at a node. Children
represents the childs of a node. Notice that the Node inherits
System.ComponentModel.INotifyPropertyChanged
class inorder to keep the UI in sync. Read this article to better understand this functionality. Also notice the hepler functions
Add
and Delete
which add and delete a child node respectively.
/* File : Node.cs */
using System;
using System.ComponentModel;
using System.Collections.ObjectModel;
public class Node : INotifyPropertyChanged
{
private String text;
private ObservableCollection<node /> children;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<node /> Children
{
get { return children; }
set { children = value; }
}
public String Text
{
get { return text; }
set { text = value; }
}
public Node(String text)
{
Children = new ObservableCollection<node />();
Text = text;
}
public void Add(Node node)
{
children.Add(node);
NotifyPropertyChanged("Children");
}
public void Delete(Node node)
{
children.Remove(node);
NotifyPropertyChanged("Children");
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
UserControl XAML
Now let us see the XAML definition for the user control.
First thing, I have implemented a Context Menu to facilitate CRUD operations. You can read this blog to learn how one can be implemented.
Next, notice the two HierarchicalDataTemplate
. One is for the TreeViewItem
in Read Mode ( hence a TextBlock
) and the other in Edit mode ( hence a TextBox
). The TextBox
and the TextBlock
are bound to the Text
property of the Node.
I am using the TreeViewDragDropTarget
control from Silverlight Toolkit to enable Drag-And-Drop of TreeViewItems
among parent nodes.
<!-- File : CSSLTreeViewCrudDragDrop.xaml -->
<UserControl
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
x:Class="CSSLTreeViewCRUDDragDrop.TreeViewCrudDragDrop"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:mswindows="clr-namespace:Microsoft.Windows;assembly=System.Windows.Controls.Toolkit">
<UserControl.Resources>
<!-- Template for Edit mode of TreeViewItem -->
<sdk:HierarchicalDataTemplate x:Key="TreeViewMainEditTemplate" ItemsSource="{Binding Children}">
<TextBox Text="{Binding Text,Mode=TwoWay}" >
</TextBox>
</sdk:HierarchicalDataTemplate>
<!-- Template for Read mode for TreeViewItem -->
<sdk:HierarchicalDataTemplate x:Key="TreeViewMainReadTemplate"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Text,Mode=TwoWay}"
MouseRightButtonDown="TreeViewMain_MouseRightButtonDown"
MouseRightButtonUp="TreeViewMain_MouseRightButtonUp"
MouseLeftButtonDown="TreeViewMain_MouseLeftButtonDown" >
</TextBlock>
</sdk:HierarchicalDataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<!-- TreeViewDragDropTarget from Toolkit to add DragAndDrop feature -->
<toolkit:TreeViewDragDropTarget AllowDrop="True">
<!-- Custom TreeView -->
<sdk:TreeView Name="TreeViewMain"
ItemTemplate="{StaticResource TreeViewMainReadTemplate}"
MouseRightButtonDown="TreeViewMain_MouseRightButtonDown"
MouseRightButtonUp="TreeViewMain_MouseRightButtonUp"
MouseLeftButtonDown="TreeViewMain_MouseLeftButtonDown"
Width="400" Height="400" >
</sdk:TreeView>
</toolkit:TreeViewDragDropTarget>
<!-- Context Menu -->
<Canvas>
<Popup Name="ContextMenu" Visibility="Collapsed">
<Border BorderThickness="1" BorderBrush="Black" Background="White">
<StackPanel>
<HyperlinkButton Content="Add" Name="AddButton" Click="AddButton_Click" />
<HyperlinkButton Content="Edit" Name="EditButton" Click="EditButton_Click"/>
<HyperlinkButton Content="Delete" Name="DeleteButton" Click="DeleteButton_Click"/>
</StackPanel>
</Border>
</Popup>
</Canvas>
</Grid>
</UserControl>
Code Behind
Now let us have a sneak peek at the code behind for our UserControl.First, the Mouse Event Handlers. The
MouseRightButtonUp
event for a TreeViewItem
does two things. It assigns that particular TreeViewItem’s
Data Context as the selectedNode
. Second, it shows up the ContextMenu. The selectedNode
information is necessary as that is used as a reference to Edit the TreeViewItem
, Add Children to the TreeViewItem
or delete the TreeViewItem
. The
AddButton_Click
event handler, creates a new Node
and adds it as a children of the selecteNode
. The
EditButton_Click
event handler, changes the Template of the selected TreeViewItem
to Edit mode. The
DeleteButton_Click
event handler, first identifies the TreeViewItem
associated with the selectedNode
, finds its parent, and deletes the selectedNode from the Parent.
/* File : CSSLTreeViewCrudDragDrop.cs */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Collections.ObjectModel;
namespace CSSLTreeViewCRUDDragDrop
{
public partial class TreeViewCrudDragDrop : UserControl
{
ObservableCollection<Node> objectTree;
Node selectedNode;
public List<Node> Items
{
get
{
return objectTree.ToList<Node>();
}
set
{
objectTree = new ObservableCollection<Node>(value);
TreeViewMain.ItemsSource = objectTree;
}
}
public TreeViewCrudDragDrop()
{
InitializeComponent();
objectTree = new ObservableCollection<Node>();
TreeViewMain.ItemsSource = objectTree;
}
private void TreeViewMain_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
DisableEditForSelectedItem();
e.Handled = true;
}
private void TreeViewMain_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
DisableEditForSelectedItem();
if (sender is TextBlock)
{
selectedNode = (Node)((sender as TextBlock).DataContext);
}
else
{
selectedNode = null;
}
ShowContextMenu(e);
}
private void TreeViewMain_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DisableEditForSelectedItem();
HideContextMenu();
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
Node newNode = new Node("New Node");
if (selectedNode != null)
{
selectedNode.Add(newNode);
}
else
{
if (objectTree != null)
{
objectTree.Add(newNode);
}
else
{
objectTree = new ObservableCollection<Node>();
objectTree.Add(newNode);
}
}
HideContextMenu();
}
private void EditButton_Click(object sender, RoutedEventArgs e)
{
EnalbleEditForSelectedItem();
TreeViewItem selectedTreeViewItem =
TreeViewExtensions.GetContainerFromItem(TreeViewMain, selectedNode);
HideContextMenu();
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
TreeViewItem selectedTreeViewItem =
TreeViewExtensions.GetContainerFromItem(TreeViewMain, selectedNode);
if (selectedTreeViewItem != null)
{
TreeViewItem selectedTreeViewItemParent =
TreeViewExtensions.GetParentTreeViewItem(selectedTreeViewItem);
if (selectedTreeViewItemParent != null)
{
Node seleactedParentNode = (Node)selectedTreeViewItemParent.DataContext;
seleactedParentNode.Delete(selectedNode);
}
else
{
objectTree.Remove(selectedNode);
}
}
HideContextMenu();
}
private void ShowContextMenu(MouseButtonEventArgs e)
{
e.Handled = true;
Point p = e.GetPosition(this);
ContextMenu.Visibility = Visibility.Visible;
ContextMenu.IsOpen = true;
ContextMenu.SetValue(Canvas.LeftProperty, (double)p.X);
ContextMenu.SetValue(Canvas.TopProperty, (double)p.Y);
}
private void HideContextMenu()
{
ContextMenu.Visibility = Visibility.Collapsed;
ContextMenu.IsOpen = false;
}
private void EnalbleEditForSelectedItem()
{
if (selectedNode != null)
{
SetTemplateForSelectedItem("TreeViewMainEditTemplate");
}
}
private void DisableEditForSelectedItem()
{
if (selectedNode != null)
{
SetTemplateForSelectedItem("TreeViewMainReadTemplate");
selectedNode = null;
}
}
private void SetTemplateForSelectedItem(String templateName)
{
HierarchicalDataTemplate hdt = (HierarchicalDataTemplate)Resources[templateName];
TreeViewItem selectedTreeViewItem =
TreeViewExtensions.GetContainerFromItem(TreeViewMain, selectedNode);
if (selectedTreeViewItem != null)
selectedTreeViewItem.HeaderTemplate = hdt;
}
}
}
So that completes it
References
MichaelSnow : Silverlight Tip of the Day #3 – Mouse Right Clicks
MSDN : DataBinding Silverlight
Codeplex : Silverlight Toolkit
MSDN : INotifyPropertyChanged Interface