WPF TreeView - XAML ContextMenu 是否可以基于 属性 有条件?
WPF TreeView - Can a XAML ContextMenu be conditional based on a property?
我正在使用 TreeView
显示对象层次结构,使用 Philipp Sumi's method 使用转换器组织异构数据。这行得通。
但是,我现在想在 XAML 中添加 ContextMenus
,这通常特定于用户单击的对象类型。由于使用 FolderItem
表示多个 class,因此多个对象类型可以共享相同的 <ContextMenu>
定义。
下面的例子显示了一个基本的 TreeView
。猫和狗共享相同的 <ContextMenu>
定义。我可以更具体地定位 ContextMenu
以便“遛狗”菜单只在用户点击“狗”而不是“猫”时出现吗?
我正在寻找针对 FolderItem
的 Name
属性 的内容(即逻辑是 [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");
}
}
我做过这样的事情。当用户选择一个项目时,我有一个名为 SelectedItem
的所选项目的 属性。然后,我可以使用基于 SelectedItem
属性的触发器来个性化 ContextMenu
的 MenuItems
。特别是,我使用 SelectedItem
的属性来确定哪个菜单项被禁用,但您也可以控制每个菜单项的可见性。
或者,虽然我没有尝试过,但应该可以使用设置 ContextMenu
属性.
的触发器
如果我正确理解你的问题,你可以根据 FolderItem
的 Name
使用 Style
和 DataTrigger
应用不同的 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>
我正在使用 TreeView
显示对象层次结构,使用 Philipp Sumi's method 使用转换器组织异构数据。这行得通。
但是,我现在想在 XAML 中添加 ContextMenus
,这通常特定于用户单击的对象类型。由于使用 FolderItem
表示多个 class,因此多个对象类型可以共享相同的 <ContextMenu>
定义。
下面的例子显示了一个基本的 TreeView
。猫和狗共享相同的 <ContextMenu>
定义。我可以更具体地定位 ContextMenu
以便“遛狗”菜单只在用户点击“狗”而不是“猫”时出现吗?
我正在寻找针对 FolderItem
的 Name
属性 的内容(即逻辑是 [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");
}
}
我做过这样的事情。当用户选择一个项目时,我有一个名为 SelectedItem
的所选项目的 属性。然后,我可以使用基于 SelectedItem
属性的触发器来个性化 ContextMenu
的 MenuItems
。特别是,我使用 SelectedItem
的属性来确定哪个菜单项被禁用,但您也可以控制每个菜单项的可见性。
或者,虽然我没有尝试过,但应该可以使用设置 ContextMenu
属性.
如果我正确理解你的问题,你可以根据 FolderItem
的 Name
使用 Style
和 DataTrigger
应用不同的 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>