前言
在wpf開發中,經常會使用到Menu和ContentMenu。但是原生的樣式比較簡陋,對於比較追求界面美好的人來說是十分不友好的。那么,這就涉及到對Menu的樣式修改了。與此同時,我們還希望Menu自動Binding到視圖數據模型上,根據數據項自動展開MenuItem。接下來就對這些想法做一簡單實現。
視圖模型
假設我們的菜單項里有描述意圖的縮略圖和文字需要展示。那么我們需要有名字和存有圖片路徑的屬性。額外的,還需要一個Children集合來存放子項,以形成樹形數據。
視圖模型Class
public class CommonTreeModel
{
/// <summary>
/// 名字
/// </summary>
public string Name { get; set; }
/// <summary>
/// 圖片的路徑
/// </summary>
public string IconPath { get; set; }
/// <summary>
/// 子項
/// </summary>
public ObservableCollection<CommonTreeModel> Children { get; set; }
}
數據mock
我們使用Bogus
進行模擬數據的產生。使用nuget搜索Bogus添加即可。
/// <summary>
/// 菜單項數據集,前端將binding到該屬性上
/// </summary>
public ObservableCollection<CommonTreeModel> MenuTreeSource { get; set; }
private void InitData()
{
var general = new Bogus.Faker<CommonTreeModel>()
.RuleFor(t => t.Name, t => t.Commerce.Product())//名字:商業產品
.RuleFor(t => t.IconPath, t => t.Image.LoremFlickrUrl(32, 32));//圖片:使用LoremFlick網站的圖片
var rd = new Random(DateTime.Now.Millisecond);//隨機數
MenuTreeSource = GenerateTreeData(general, rd, 10, 3, 10);
}
private ObservableCollection<CommonTreeModel> GenerateTreeData(Faker<CommonTreeModel> faker, Random rd, int topCount, int subMin, int subMaxm, int level = 0, int levelLimit = 4)
{
var list = new ObservableCollection<CommonTreeModel>(faker.Generate(level == 0 ? topCount : rd.Next(subMin, subMaxm)));
level++;
if (level < levelLimit)
{
foreach (var item in list)
{
if (rd.Next() % 2 == 0)
{
item.Children = GenerateTreeData(faker, rd, topCount, subMin, subMaxm, level, levelLimit);
}
}
}
return list;
}
到這里,我們生成了最大層級可能為4的一棵樹:MenuTreeSource
。
WPF中的Menu
我們先寫前端Menu綁定到數據。
<Menu ItemsSource="{Binding MenuTreeSource}" HorizontalAlignment="Center">
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Label Content="{Binding Name}"></Label>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
原始的樣式展示效果如下,總共有四級,並且第一級和后三級不一致。
MenuItem樣式
我們只需重寫MenuItem的樣式,就能夠改變菜單的展示效果:
<Style TargetType="MenuItem">
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="Foreground" Value="#b8d00a"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Background" Value="#b8d00a"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#f46a56"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Width="32" Height="32" Margin="5" Source="{Binding IconPath}" />
<Label Content="{Binding Name}" Margin="10 0" Grid.Column="1" VerticalContentAlignment="Center"/>
<Label Name="MoreLbl" Content=">>" Grid.Column="2" VerticalContentAlignment="Center">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Children}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<Popup AllowsTransparency="True"
IsOpen="{Binding Path=IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
Placement="Right" x:Name="SubMenuPopup" Focusable="false">
<Border x:Name="SubMenuBorder" BorderThickness="1" BorderBrush="Black">
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/>
</Border>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
控件Popup
用於展示子級菜單項。其中StackPanel
上的IsItemsHost="True"
保證正確處理子級。
名為MoreLbl的Label控件用於提示是否有子級。這里簡略用>>標識一下,讀者可以圖片Image或者Path控件做一個漂亮的樣式。
最終效果如下,一級和次級依然不統一。
統一Menu的一級和次級
為了統一,我們需要對Menu的樣式加以調整。最簡單的方式是對子項承載容器進行替換,我們用StackPanel
控件,並把內容排序設置為垂直方向即可。
<Menu ItemsSource="{Binding MenuTreeSource}" HorizontalAlignment="Center">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"></StackPanel>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"></HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
最終效果如下:
WPF中的ContextMenu控件
ContextMenu與Menu不同,他可以作為很多控件的Popup形式的鼠標右鍵彈出菜單,比如作為ListBox,Label,Grid等的內容菜單都是可以的,我們只需鼠標右鍵就可以將其彈出。
我們用Label
簡單舉例:
<Label Content="鼠標右鍵彈出ContextMenu" HorizontalAlignment="Center"HorizontalContentAlignment="Center" FontSize="25" Background="#221a12"Foreground="#b8d00a"
MouseDown="Label_MouseDown">
<Label.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuTreeSource}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" />
</ContextMenu.ItemTemplate>
</ContextMenu>
</Label.ContextMenu>
</Label>
之前的樣式都是全局樣式,默認會自動使用。效果如下:
能夠發現一級的樣式稍微有些不一樣,我們需要改ContextMenu
的樣式
<Style TargetType="{x:Type ContextMenu}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Grid.IsSharedSizeScope" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContextMenu}">
<Border
BorderBrush="Black"
BorderThickness="1" >
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
簡單重寫Template即可。
總結
這里只簡單介紹如何去重寫樣式模板來改變菜單展示效果。具體使用中大家需要更好看的效果請自行設計。該wpf項目框架使用dotnet core3.1版本。在farmwork中應該一樣。