krrishna krrishna - 2 months ago 50
C# Question

Different Context Menu for each treeviewitem type in wpf MVVM

I was able to implement the TreeView successfully and I am using an Interface as the template to display the data for Project/Folder/File just like SolutionExplorer of Visual Studio. I want to add contextmenu to my TreeViewItem based on the item type as described in my xaml file below. But I was not able to map the contextmenu based on the TreeViewItem. I tried to see if I can use Triggers which works but I don't know how to assign the ContextMenu based on the TreeViewItem types(e.g. Folder/File/Project in my case).

<!--<Trigger Property="IsSelected" Value="Folder">
<Setter Property="ContextMenu" Value="{StaticResource FolderMenu}" />
</Trigger>-->

<Window x:Class="SimpleTreeWpfApplication1.MainWindow"
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:local="clr-namespace:SimpleTreeWpfApplication1"
mc:Ignorable="d"

Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:TreeViewModel/>
</Window.DataContext>
<Grid>
<TextBlock Text="Hierarchical root binding" Foreground="Red" Margin="10,20,0,0"/>
<TreeView ItemsSource="{Binding TreeData}" Margin="10" Height="200">
<TreeView.Resources>
<!-- Begin Context Menu -->
<ContextMenu x:Key="ProjectMenu" >
<MenuItem Command="{Binding AddFolder}" Header="Add Folder"/>
<MenuItem Command="{Binding EditProject}" Header="Edit"/>
</ContextMenu>
<ContextMenu x:Key="FolderMenu" >
<MenuItem Command="{Binding AddFolder}" Header="Add Folder"/>
<MenuItem Command="{Binding AddFile}" Header="Add File"/>
</ContextMenu>
<ContextMenu x:Key="FileMenu" >
<MenuItem Command="{Binding EditFile}" Header="Edit"/>
</ContextMenu>
</TreeView.Resources>
<TreeView.ItemContainerStyle>

<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource FolderMenu}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>

</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type local:INode}">
<TreeViewItem Header="{Binding Label}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

</Grid>




The interface and models look like below:

public interface INode
{
string FullPath { get; }
string Label { get; }
List<INode> Children { get; }
}


public class Folder : INode
{
public List<INode> Children { get; set; }
public string Label { get; set; }
public string FullPath { get; set; }
//custom values
public Dictionary<string,string> MyFolderProperties { get; set; }

//initialize default values
public Folder()
{
Children = new List<INode>();
}
}


public class File : INode
{
public List<INode> Children { get; set; }
public string Label { get; set; }
public string FullPath { get; set; }

//custom values
public Dictionary<string,string> MyFileProperties { get; set; }
}


public class Project: INode
{
public List<INode> Children { get; set; }
public string Label { get; set; }
public string FullPath { get; set; }

public List<String> ProjectTypeValuesDb { get; set; }

//initialize default values
public Project()
{
Children = new List<INode>();
}
}



//Viewmodel code
public class TreeViewModel : INotifyPropertyChanged
{
public TreeViewModel()
{

//initialize and add
m_folders = new List<INode>();
TreeData = m_folders;
//add Root items
TreeData.Add(new Folder { Label = "Folder1", FullPath = @"C:\dummy1" });
TreeData.Add(new Folder { Label = "Folder2", FullPath = @"C:\dummy2" });
TreeData.Add(new Folder { Label = "Folder3", FullPath = @"C:\dummy3" });
TreeData.Add(new Folder { Label = "Folder4", FullPath = @"C:\dummy4" });
//Folders.Add(new File { Label = "File1.txt", FullPath = @"C:\File1.txt" });

//add sub items
TreeData[0].Children.Add(new Folder { Label = "Folder11", FullPath = @"C:\dummy11" });
TreeData[0].Children.Add(new Folder { Label = "Folder12", FullPath = @"C:\dummy12" });
TreeData[0].Children.Add(new Folder { Label = "Folder13", FullPath = @"C:\dummy13" });
TreeData[0].Children.Add(new Folder { Label = "Folder14", FullPath = @"C:\dummy14" });

TreeData[0].Children.Add(new File { Label = "File1.txt", FullPath = @"C:\File1.txt" });
TreeData[0].Children.Add(new File { Label = "File2.txt", FullPath = @"C:\File1.txt" });
TreeData[0].Children.Add(new File { Label = "File3.txt", FullPath = @"C:\File1.txt" });

}



bool _isExpanded = false;
bool _isSelected = false;
#region IsExpanded
/// <summary>
/// Gets/sets whether the TreeViewItem
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.NotifiyPropertyChanged("IsExpanded");
}

// Expand all the way up to the root.
//if (_isExpanded && _parent != null)
// _parent.IsExpanded = true;
}
}

#endregion // IsExpanded

#region IsSelected

/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
/// </summary>
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.NotifiyPropertyChanged("IsSelected");
}
}
}

#endregion // IsSelected

private List<INode> m_folders;
public List<INode> TreeData
{
get { return m_folders; }
set
{
m_folders = value;
NotifiyPropertyChanged("Folders");
}
}

void NotifiyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}

public event PropertyChangedEventHandler PropertyChanged;
}

Answer

That's a pretty simple HierarchicalDataTemplate. Just clone it for DataType="{x:Type local:Folder}", DataType="{x:Type local:File}", etc., and provide the appropriate ContextMenu to the TreeViewItem in each template. You'd define the templates in TreeView.Resources instead of in the ItemTemplate property.