这是一个递归 XAML 模板的示例,它使用您的 ApplicationMenuItem 类,完全按照您的定义(除了我将它放在名为 Wobbles 的命名空间中)。这还没有完成,可发布的代码。但它展示了一个递归的DataTemplate,以及一些额外的好处,比如显示弹出窗口。您可以将IsEnabled 属性添加到您的菜单项类,并在XAML 中使用设置颜色的附加触发器和驱动SubmenuPopup.IsOpen 的多触发器中的附加条件来实现它。如果你想支持水平分隔符,你可以添加一个属性bool ApplicationMenuItem.IsSeparator 并给模板一个触发器,当该属性为True 时,它会用水平线替换下面的网格内容。
递归模板.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wbl="clr-namespace:Wobbles"
>
<DataTemplate DataType="{x:Type wbl:ApplicationMenuItem}">
<Grid
Name="RootGrid"
Background="BlanchedAlmond"
Height="Auto"
UseLayoutRounding="True"
SnapsToDevicePixels="True"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="24" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Source="{Binding Image}"
/>
<Label
Grid.Column="1"
Content="{Binding Text}"
/>
<Border
Name="PopupGlyphBorder"
Grid.Column="2"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="{Binding ElementName=RootGrid, Path=Background}"
>
<Path
Height="10"
Width="5"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Data="M 0,0 L 5,5 L 0,10 Z"
Fill="Black"
/>
</Border>
<Popup
Name="SubmenuPopup"
PlacementTarget="{Binding ElementName=PopupGlyphBorder}"
Placement="Right"
StaysOpen="True"
>
<Border
BorderBrush="DarkGoldenrod"
BorderThickness="1"
>
<ItemsControl
Name="SubmenuItems"
ItemsSource="{Binding Items}"
/>
</Border>
</Popup>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="RootGrid" Property="Background" Value="Wheat" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition SourceName="SubmenuItems" Property="HasItems" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="SubmenuPopup" Property="IsOpen" Value="True" />
</MultiTrigger>
<Trigger SourceName="SubmenuItems" Property="HasItems" Value="False">
<Setter TargetName="PopupGlyphBorder" Property="Visibility" Value="Hidden" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ResourceDictionary>
MainWindow.xaml
<Window
x:Class="RecursiveTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wbl="clr-namespace:Wobbles"
Title="MainWindow"
Height="350"
Width="525"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="RecursiveTemplate.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Window.DataContext>
<wbl:TestViewModel />
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<ContentControl
Content="{Binding Menu}"
Width="100"
Height="24"
/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
ViewModel.cs
namespace Wobbles
{
public class TestViewModel
{
public TestViewModel()
{
Menu = CreateMenu();
}
public Wobbles.ApplicationMenuItem Menu { get; protected set; }
protected Wobbles.ApplicationMenuItem CreateMenu()
{
var m = new Wobbles.ApplicationMenuItem("Menu");
var msub = new Wobbles.ApplicationMenuItem("Submenu");
msub.Items.Add(new Wobbles.ApplicationMenuItem("Sub Sub 1"));
msub.Items.Add(new Wobbles.ApplicationMenuItem("Sub Sub 2"));
// LOL
msub.Items.Add(msub);
m.Items.Add(msub);
m.Items.Add(new Wobbles.ApplicationMenuItem("Foo"));
m.Items.Add(new Wobbles.ApplicationMenuItem("Bar"));
m.Items.Add(new Wobbles.ApplicationMenuItem("Baz"));
return m;
}
}
}
Nits、Cavils、Kvetches 和简短的讲道
使用 XAML,我建议练习使用 ObservableCollection<T> 而不是 List<T>。如果 UI 构建后集合中的项目发生变化,ObservableCollection<T> 将导致 UI 相应更新。出于同样的原因,您将希望 ApplicationMenuItem 实现 INotifyPropertyChanged。我还希望支持ICommand Command 属性以及Click 事件,并且我会根据标准XAML 实践进一步将Click 事件命名为Click。
“XAML 会做什么?”如果您尽最大努力编写可能被误认为是您正在工作的环境中附带的标准库的代码,那么您几乎永远不会出错。