Поскольку это довольно сложно, я обновил ответ с помощью загружаемого примера.
PrismMenuServiceExample
Моя цель состояла в том, чтобы позволить различным модулям регистрировать менюКоманды и сгруппировать их вместе с заголовком и отсортировать пункты меню в правильном порядке.Прежде всего, давайте покажем пример того, как выглядит меню.
Это полезно, например, меню «Инструменты» может иметь группу «Модуль1», которая имеетпункты меню перечислены для каждого инструмента, принадлежащего модулю 1, который модуль 1 может зарегистрировать независимо от других модулей.
У меня есть «служба меню», которая позволяет модулям регистрировать новые меню и пункты меню.Каждый узел имеет свойство Path, которое сообщает службе, где разместить меню.Этот интерфейс вероятен в инфраструктурном проекте, так что все модули могут разрешить его.
public interface IMenuService
{
void AddTopLevelMenu(MenuItemNode node);
void RegisterMenu(MenuItemNode node);
}
Затем я могу реализовать этот MenuService там, где это необходимо.(Инфраструктурный проект, отдельный модуль, возможно, Shell).Я продолжаю и добавляю некоторые «стандартные» меню, которые определены для всего приложения, хотя любой модуль может добавить новые меню верхнего уровня.
Я мог бы создать эти меню в коде, но вместо этого я вытащил их из ресурсовпотому что было проще записать их в XAML в файле ресурсов.Я добавляю этот файл ресурсов в ресурсы моего приложения, но вы можете загрузить его напрямую.
public class MainMenuService : IMenuService
{
MainMenuNode menu;
MenuItemNode fileMenu;
MenuItemNode toolMenu;
MenuItemNode windowMenu;
MenuItemNode helpMenu;
public MainMenuService(MainMenuNode menu)
{
this.menu = menu;
fileMenu = (MenuItemNode)Application.Current.Resources["FileMenu"];
toolMenu = (MenuItemNode)Application.Current.Resources["ToolMenu"];
windowMenu = (MenuItemNode)Application.Current.Resources["WindowMenu"];
helpMenu = (MenuItemNode)Application.Current.Resources["HelpMenu"];
menu.Menus.Add(fileMenu);
menu.Menus.Add(toolMenu);
menu.Menus.Add(windowMenu);
menu.Menus.Add(helpMenu);
}
#region IMenuService Members
public void AddTopLevelMenu(MenuItemNode node)
{
menu.Menus.Add(node);
}
public void RegisterMenu(MenuItemNode node)
{
String[] tokens = node.Path.Split('/');
RegisterMenu(tokens.GetEnumerator(), menu.Menus, node);
}
#endregion
private void RegisterMenu(IEnumerator tokenEnumerator, MenuItemNodeCollection current, MenuItemNode item)
{
if (!tokenEnumerator.MoveNext())
{
current.Add(item);
}
else
{
MenuItemNode menuPath = current.FirstOrDefault(x=> x.Text == tokenEnumerator.Current.ToString());
if (menuPath == null)
{
menuPath = new MenuItemNode(String.Empty);
menuPath.Text = tokenEnumerator.Current.ToString();
current.Add(menuPath);
}
RegisterMenu(tokenEnumerator, menuPath.Children, item);
}
}
}
Вот пример одного из этих предопределенных меню в моем файле ресурсов:
<!-- File Menu Groups -->
<menu:MenuGroupDescription x:Key="fileCommands"
Name="Files"
SortIndex="10" />
<menu:MenuGroupDescription x:Key="printerCommands"
Name="Printing"
SortIndex="90" />
<menu:MenuGroupDescription x:Key="applicationCommands"
Name="Application"
SortIndex="100" />
<menu:MenuItemNode x:Key="FileMenu"
x:Name="FileMenu"
Text="{x:Static inf:DefaultTopLevelMenuNames.File}"
SortIndex="10">
<menu:MenuItemNode Group="{StaticResource fileCommands}"
Text="_Open File..."
SortIndex="10"
Command="{x:Static local:FileCommands.OpenFileCommand}" />
<menu:MenuItemNode Group="{StaticResource fileCommands}" Text="Recent _Files" SortIndex="20"/>
<menu:MenuItemNode Group="{StaticResource fileCommands}" Text="Con_vert..." SortIndex="30"/>
<menu:MenuItemNode Group="{StaticResource fileCommands}"
Text="_Export"
SortIndex="40"
Command="{x:Static local:FileCommands.ExportCommand}" />
<menu:MenuItemNode Group="{StaticResource fileCommands}" Text="_Save" SortIndex="50"/>
<menu:MenuItemNode Group="{StaticResource fileCommands}" Text="Save _All" SortIndex="60"/>
<menu:MenuItemNode Group="{StaticResource fileCommands}"
Text="_Close"
SortIndex="70"
Command="{x:Static local:FileCommands.CloseCommand}" />
<menu:MenuItemNode Group="{StaticResource printerCommands}" Text="Page _Setup..." SortIndex="10"/>
<menu:MenuItemNode Group="{StaticResource printerCommands}" Text="_Print..." SortIndex="10"/>
<menu:MenuItemNode Group="{StaticResource applicationCommands}"
Text="E_xit"
SortIndex="10"
Command="{x:Static local:FileCommands.ExitApplicationCommand}" />
</menu:MenuItemNode>
ОК, здесь перечислены типы, которые определяют структуру моей системы меню ... (Не то, как это выглядит)
MainMenuNode в основном существует, так что вы можете легко создать для него другой шаблон.Вы, вероятно, какая строка меню или что-то, что представляет меню в целом.
public class MainMenuNode
{
public MainMenuNode()
{
Menus = new MenuItemNodeCollection();
}
public MenuItemNodeCollection Menus { get; private set; }
}
Вот определение для каждого MenuItem.Они включают в себя Path, который сообщает службе, куда их помещать, SortIndex, который подобен TabIndex, который позволяет им организовывать их в правильном порядке, и GroupDescription, который позволяет вам размещать их в «группы», которые могут иметь разные стили.и отсортировано.
[ContentProperty("Children")]
public class MenuItemNode : NotificationObject
{
private string text;
private ICommand command;
private Uri imageSource;
private int sortIndex;
public MenuItemNode()
{
Children = new MenuItemNodeCollection();
SortIndex = 50;
}
public MenuItemNode(String path)
{
Children = new MenuItemNodeCollection();
SortIndex = 50;
Path = path;
}
public MenuItemNodeCollection Children { get; private set; }
public ICommand Command
{
get
{
return command;
}
set
{
if (command != value)
{
command = value;
RaisePropertyChanged(() => this.Command);
}
}
}
public Uri ImageSource
{
get
{
return imageSource;
}
set
{
if (imageSource != value)
{
imageSource = value;
RaisePropertyChanged(() => this.ImageSource);
}
}
}
public string Text
{
get
{
return text;
}
set
{
if (text != value)
{
text = value;
RaisePropertyChanged(() => this.Text);
}
}
}
private MenuGroupDescription group;
public MenuGroupDescription Group
{
get { return group; }
set
{
if (group != value)
{
group = value;
RaisePropertyChanged(() => this.Group);
}
}
}
public int SortIndex
{
get
{
return sortIndex;
}
set
{
if (sortIndex != value)
{
sortIndex = value;
RaisePropertyChanged(() => this.SortIndex);
}
}
}
public string Path
{
get;
private set;
}
И набор пунктов меню:
public class MenuItemNodeCollection : ObservableCollection<MenuItemNode>
{
public MenuItemNodeCollection() { }
public MenuItemNodeCollection(IEnumerable<MenuItemNode> items) : base(items) { }
}
Вот как я закончил группировку MenuItems .. У каждого из них есть GroupDescription
public class MenuGroupDescription : NotificationObject, IComparable<MenuGroupDescription>, IComparable
{
private int sortIndex;
public int SortIndex
{
get { return sortIndex; }
set
{
if (sortIndex != value)
{
sortIndex = value;
RaisePropertyChanged(() => this.SortIndex);
}
}
}
private String name;
public String Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
RaisePropertyChanged(() => this.Name);
}
}
}
public MenuGroupDescription()
{
Name = String.Empty;
SortIndex = 50;
}
public override string ToString()
{
return Name;
}
#region IComparable<MenuGroupDescription> Members
public int CompareTo(MenuGroupDescription other)
{
return SortIndex.CompareTo(other.SortIndex);
}
#endregion
#region IComparable Members
public int CompareTo(object obj)
{
if(obj is MenuGroupDescription)
return sortIndex.CompareTo((obj as MenuGroupDescription).SortIndex);
return this.GetHashCode().CompareTo(obj.GetHashCode());
}
#endregion
}
Затем я могу спроектировать, как будет выглядеть мое меню, с помощью следующих шаблонов:
<local:MenuCollectionViewConverter x:Key="GroupViewConverter" />
<!-- The style for the header of a group of menu items -->
<DataTemplate x:Key="GroupHeaderTemplate"
x:Name="GroupHeader">
<Grid x:Name="gridRoot"
Background="#d9e4ec">
<TextBlock Text="{Binding Name}"
Margin="4" />
<Rectangle Stroke="{x:Static SystemColors.MenuBrush}"
VerticalAlignment="Top"
Height="1" />
<Rectangle Stroke="#bbb"
VerticalAlignment="Bottom"
Height="1" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Name}"
Value="{x:Null}">
<Setter TargetName="gridRoot"
Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<!-- Binds the MenuItemNode's properties to the generated MenuItem container -->
<Style x:Key="MenuItemStyle"
TargetType="MenuItem">
<Setter Property="Header"
Value="{Binding Text}" />
<Setter Property="Command"
Value="{Binding Command}" />
<Setter Property="GroupStyleSelector"
Value="{x:Static local:MenuGroupStyleSelectorProxy.MenuGroupStyleSelector}" />
</Style>
<Style x:Key="TopMenuItemStyle"
TargetType="MenuItem">
<Setter Property="Header"
Value="{Binding Text}" />
<Setter Property="Command"
Value="{Binding Command}" />
<Setter Property="GroupStyleSelector"
Value="{x:Static local:MenuGroupStyleSelectorProxy.MenuGroupStyleSelector}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Children.Count}"
Value="0">
<Setter Property="Visibility"
Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding}"
Value="{x:Null}">
<Setter Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- MainMenuView -->
<DataTemplate DataType="{x:Type menu:MainMenuNode}">
<Menu ItemsSource="{Binding Menus, Converter={StaticResource GroupViewConverter}}"
ItemContainerStyle="{StaticResource TopMenuItemStyle}" />
</DataTemplate>
<!-- MenuItemView -->
<HierarchicalDataTemplate DataType="{x:Type menu:MenuItemNode}"
ItemsSource="{Binding Children, Converter={StaticResource GroupViewConverter}}"
ItemContainerStyle="{StaticResource MenuItemStyle}" />
Ключом к этой работе было выяснить, как внедрить мой CollectionView с правильными определениями сортировки и определениями группирования в мой DataTemplate.Вот как я это сделал:
[ValueConversion(typeof(MenuItemNodeCollection), typeof(IEnumerable))]
public class MenuCollectionViewConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType != typeof(IEnumerable))
throw new NotImplementedException();
CollectionViewSource src = new CollectionViewSource();
src.GroupDescriptions.Add(new PropertyGroupDescription("Group"));
src.SortDescriptions.Add(new SortDescription("Group", ListSortDirection.Ascending));
src.SortDescriptions.Add(new SortDescription("SortIndex", ListSortDirection.Ascending));
src.Source = value as IEnumerable;
return src.View;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.GetType() != typeof(CollectionViewSource))
throw new NotImplementedException();
return (value as CollectionViewSource).Source;
}
#endregion
}
public static class MenuGroupStyleSelectorProxy
{
public static GroupStyleSelector MenuGroupStyleSelector { get; private set; }
private static GroupStyle Style { get; set; }
static MenuGroupStyleSelectorProxy()
{
MenuGroupStyleSelector = new GroupStyleSelector(SelectGroupStyle);
Style = new GroupStyle()
{
HeaderTemplate = (DataTemplate)Application.Current.Resources["GroupHeaderTemplate"]
};
}
public static GroupStyle SelectGroupStyle(CollectionViewGroup grp, int target)
{
return Style;
}
}