Telerik blogs

This is part two of my Attaching a ContextMenu on TreeView blog post. I will show another approach for adding context menu on a treeview, that uses a single menu that is customized for each treeview item. One context menu per treeview should provide much better performance if the treeview is bound to a large data set, since there are much less visual elements that have to be created. I am also using an updated view model, where each item contains a reference to its parent. This will help me to rely entirely on the model for my application logic, which greatly simplifies the code-behind, because I don’t have to care about the visual tree.

The declaration of the TreeView with the ContextMenu is the following:

<telerikNavigation:RadTreeView x:Name="TreeView" ItemsSource="{Binding Items, Source={StaticResource TreeViewModel}}" ItemTemplate="{StaticResource TreeViewItemTemplate}" IsExpandOnSingleClickEnabled="True" HorizontalAlignment="Left"> <telerikNavigation:RadContextMenu.ContextMenu> <telerikNavigation:RadContextMenu x:Name="ContextMenu" 
 ItemClick="ContextMenuClick" Opened="ContextMenuOpened"> <telerikNavigation:RadMenuItem Header="New Child" /> <telerikNavigation:RadMenuItem Header="New Sibling" /> <telerikNavigation:RadMenuItem Header="Delete" /> </telerikNavigation:RadContextMenu> </telerikNavigation:RadContextMenu.ContextMenu> </telerikNavigation:RadTreeView>

Note that you will have to make your Silverlight plug-in windowless to get the right click context menu. Otherwise you will need to set EventName and/or ModifierKey properties to configure the control according your needs.

I want to mention one more thing that we added with Q1 2009 SP2: ItemClicked CLR event in RadMenu and RadContextMenu, raised every time a child item is clicked. It can be attached from XAML, so you don’t have to write strange code-behind for that anymore.

Since there is only one context menu for all treeview items, I need to somehow find the clicked treeview item when the menu is opened. The easiest way to do this is to call the VisualTreeHelper.FindElementsInHostCoordinates method with the position of the context menu. The following method searches for an element of the specified type at the given coordinates:

private T FindElementAt<T>(Point coordinates) where T : UIElement
{
    return (T)VisualTreeHelper.FindElementsInHostCoordinates(coordinates, this)
        .FirstOrDefault((element) => element is T);
}

RadContextMenu provides the coordinates of the mouse from the moment when it was opened in its MousePosition property (if you use assemblies from Q1 2009 SP2 or earlier, you may need to update them to the latest internal build to get this functionality):

private RadTreeViewItem ClickedTreeViewItem
{
    get
    {
        return this.FindElementAt<RadTreeViewItem>(this.ContextMenu.MousePosition);
    }
}

 

From now on my job is straightforward. In the context menu Opened event handler I customize the items, depending on the clicked treeview item:

private void ContextMenuOpened(object sender, RoutedEventArgs e)
{
    DataItem dataItem = clickedTreeViewItem.DataContext as DataItem;

    // We will disable the "New Sibling" and "Delete" context menu  
 // items if the clicked treeview item is a root item 
 bool isRootItem = dataItem.Parent == null;

    ((sender as RadContextMenu).Items[1] as RadMenuItem).IsEnabled = !isRootItem;
    ((sender as RadContextMenu).Items[2] as RadMenuItem).IsEnabled = !isRootItem;
}

 

In the ItemClicked event handler I implemented the context menu logic:

private void ContextMenuClick(object sender, Telerik.Windows.RadRoutedEventArgs e)
{
    DataItem item = this.ClickedTreeViewItem.DataContext as DataItem;

    string header = (e.OriginalSource as RadMenuItem).Header as string;
    switch (header)
    {
        case "New Child":
            item.Items.Add(new DataItem() { Text = "New Child" });
            item.IsExpanded = true; // Ensure that the new child is visible break;
        case "New Sibling":
            item.Parent.Items.Add(new DataItem() { Text = "New Sibling" });
            break;
        case "Delete":
            item.Parent.Items.Remove(item);
            break;
    }
}

 

You may notice that there is no Rename option. This is because RadTreeView still does not support editing in data-bound mode out of the box. I hope that this feature will make it for the official Q2 2009 release next month.

 

Here is the source code:


Comments

Comments are disabled in preview mode.