WPF TreeView-XAML ContextMenu可以是基于属性的条件菜单吗



我使用TreeView显示对象层次结构,使用Philipp Sumi的方法使用转换器组织异构数据。这是有效的。

但是,我现在想在XAML中添加ContextMenus,它们通常特定于用户单击的对象类型。由于使用FolderItem表示多个类,多个对象类型可以共享同一个<ContextMenu>定义。

下面的示例显示了一个基本的TreeView。猫和狗有相同的<ContextMenu>定义。我可以更具体地以CCD_ 7为目标;遛狗"菜单只在用户点击"时出现;Dogs";,但不是";猫"?

我正在寻找以FolderItemName属性为目标的东西(即逻辑为[display context menu] if Name == "Dogs"(

当然,我可以通过右键单击事件使用代码隐藏来实现这一功能,到目前为止我已经做到了。只是以良好实践的名义尝试在XAML中做更多的工作。

<Window x:Class="TestTreeView.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:TestTreeView"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">

<Window.Resources>
<local:SimpleFolderConverter x:Key="folderConverter" />
<HierarchicalDataTemplate DataType="{x:Type local:Pets}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource folderConverter}" 
ConverterParameter="Cats, Dogs">
<Binding Path="cats" />
<Binding Path="dogs" />
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text="{Binding Path=description}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Cat}">
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:Dog}">
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
<!-- data template for FolderItem instances -->
<HierarchicalDataTemplate DataType="{x:Type local:FolderItem}" ItemsSource="{Binding Path=Items}">
<StackPanel Orientation="Horizontal">

<StackPanel.ContextMenu>
<ContextMenu> <!-- This applies to more than one type of underlying object -->
<MenuItem Header="Walk all dogs"/>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView x:Name="treeView"/>
</Grid>
</Window>

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Pets pets = new Pets();
pets.cats.Add(new Cat());
pets.dogs.Add(new Dog());
treeView.ItemsSource = new ObservableCollection<Pets>() { pets };
}
}
public class Pets {
public string description { get; set; } = "Pets";
public ObservableCollection<Cat> cats { get; set; } = new ObservableCollection<Cat>();
public ObservableCollection<Dog> dogs { get; set; } = new ObservableCollection<Dog>();
public IEnumerable<Pets> CollectionOfSelf
{
get { yield return this; }
}
}
public class Cat {
public string Name { get; set; } = "Socks";
}
public class Dog {
public string Name { get; set; } = "Fido";
}
public class FolderItem
{
#region Name
/// <summary>
/// The name that can be displayed or used as an ID to perform more complex styling.
/// </summary>
private string name;

/// <summary>
/// The name that can be displayed or used as an ID to perform more complex styling.
/// </summary>
public string Name
{
get { return name; }
set
{
//ignore if values are equal
if (value == name) return;
name = value;
OnPropertyChanged("Name");
}
}
private void OnPropertyChanged(string v)
{
//
}
#endregion
#region Items
/// <summary>
/// The child items of the folder.
/// </summary>
private IEnumerable items;

/// <summary>
/// The child items of the folder.
/// </summary>
public IEnumerable Items
{
get { return items; }
set
{
//ignore if values are equal
if (value == items) return;
items = value;
OnPropertyChanged("Items");
}
}
#endregion
public FolderItem()
{
}
/// <summary>
/// This method is invoked by WPF to render the object if
/// no data template is available.
/// </summary>
/// <returns>Returns the value of the <see cref="Name"/>
/// property.</returns>
public override string ToString()
{
return string.Format("{0}: {1}", GetType().Name, Name);
}
}
public class SimpleFolderConverter : IMultiValueConverter
{
/// <summary>
/// 
/// </summary>
/// <param name="values"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//get folder name listing...
string folder = parameter as string ?? "";
var folders = folder.Split(',').Select(f => f.Trim()).ToList();
//...and make sure there are no missing entries
while (values.Length > folders.Count) folders.Add(String.Empty);
//this is the collection that gets all top level items
List<object> items = new List<object>();
for (int i = 0; i < values.Length; i++)
{
//make sure were working with collections from here...
IEnumerable childs = values[i] as IEnumerable ?? new List<object> { values[i] };
string folderName = folders[i];
if (folderName != String.Empty)
{
//create folder item and assign children
FolderItem folderItem = new FolderItem { Name = folderName, Items = childs };
items.Add(folderItem);
}
else
{
//if no folder name was specified, move the item directly to the root item
foreach (var child in childs) { items.Add(child); }
}
}
return items;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Cannot perform reverse-conversion");
}
}

如果我正确理解您的问题,您可以使用带有DataTrigger:的Style基于FolderItemName应用不同的ContextMenu

<HierarchicalDataTemplate DataType="{x:Type local:FolderItem}" ItemsSource="{Binding Path=Items}">
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="Dogs">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Walk all dogs"/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Name}" Value="Cats">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Walk all cats"/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>

我做过这样的事情。当用户选择一个项目时,我为所选项目创建了一个名为SelectedItem的属性。然后,我可以使用基于SelectedItem属性的触发器来个性化ContextMenuMenuItems。特别是,我使用SelectedItem的属性来确定哪个菜单项被禁用,但您也可以控制每个菜单项的可见性。或者,尽管我还没有尝试过,但应该可以使用设置ContextMenu属性的触发器。

最新更新