是否有人知道使用Prism的WPF代码示例,其中每个模块在另一个模块的菜单中将自己注册为menuitem?
(我目前有一个应用程序尝试使用EventAggregator执行此操作,因此一个模块侦听来自其他模块的已发布事件,这些模块需要在菜单中将其标题作为菜单项,但我遇到了问题加载和线程等的顺序我希望找到一个使用经典Prism结构来做这个的例子。)
我在考虑这个问题:
Shell.xaml:
<DockPanel>
<TextBlock Text="Menu:" DockPanel.Dock="Top"/>
<Menu
Name="MenuRegion"
cal:RegionManager.RegionName="MenuRegion"
DockPanel.Dock="Top"/>
</DockPanel>
合同视图:
<UserControl x:Class="ContractModule.Views.AllContracts"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MenuItem Header="Contracts">
</MenuItem>
</UserControl>
客户查看:
<UserControl x:Class="CustomerModule.Views.CustomerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MenuItem Header="Customers">
</MenuItem>
</UserControl>
但是我知道我已经完成了非Prism MVVM应用程序结构,并且Menus总是很好地绑定到ViewModel中的ObservableCollections,上面似乎打破了这个漂亮的模式。以上是在Prism中这样做的习惯方法吗?
答案 0 :(得分:14)
<强>更新强>
我为你创建了一个样本。就在这里:Sample
有一些你可能还没有想过的东西,比如一个允许你的模块控制你的shell的合同(所以你可以做像Open Window这样的东西)。它的设计考虑了MVVM ......如果您使用它,我不知道,但我会考虑它。
我尝试了几分钟才能让标签标题正确,但我最后还是留下了“A Tab”。如果您使用选项卡式UI,它将作为练习留给您。我将它设计为无外观,因此您可以在不破坏任何内容的情况下替换Shell.xaml中的XAML。如果你正确使用它,这是RegionManager的优势之一。
无论如何,祝你好运!
我从未见过这样的例子,但你必须自己实现这个。
您必须创建自己的界面,如下所示:
public interface IMenuRegistry
{
void RegisterViewWithMenu(string MenuItemTitle, System.Type viewType);
}
然后,您的模块将声明对IMenuRegistry的依赖并注册其视图。
您的IMenuRegistry(您可能会在托管Bootstrapper的同一个项目中实现并注册)的实现,您可以将这些菜单项添加到菜单或树视图或您用于菜单的任何内容。
当用户点击某个项目时,您必须使用Bootstrapper.Container.Resolve(viewType)
方法创建一个视图实例,并将其填入您想要显示的任何占位符中。
答案 1 :(得分:0)
我正在使用MEF以及prism 6.0和MVVM
1.为Toplevel菜单创建Leafmenu和TopLevel MenuViewmodel类的Menuviewmodel类。 Menuviewmodel类将包含您要绑定菜单的所有属性。实现此interfce的Moduleui必须具有类似
的属性[导出(typeof运算(i菜单))]
public class MenuViewModel:ViewModelBase
{
public String Name { get; private set; }
public UIMenuOptions ParentMenu { get; private set; }
private bool _IsToolTipEnabled;
public bool IsToolTipEnabled
{
get
{
return _IsToolTipEnabled;
}
set
{
SetField(ref _IsToolTipEnabled, value);
}
}
private String _ToolTipMessage;
public String ToolTipMessage
{
get
{
return _ToolTipMessage;
}
set
{
SetField(ref _ToolTipMessage, value);
}
}
private IExtensionView extensionView;
public MenuViewModel(String name, UIMenuOptions parentmenu,
bool isMenuCheckable = false,
IExtensionView extensionView =null)
{
if(name.Contains('_'))
{
name= name.Replace('_', ' ');
}
name = "_" + name;
this.Name = name;
this.ParentMenu = parentmenu;
this.IsMenuCheckable = isMenuCheckable;
this.extensionView = extensionView ;
}
private RelayCommand<object> _OpenMenuCommand;
public ObservableCollection<MenuViewModel> MenuItems { get; set; }
public ICommand OpenMenuCommand
{
get
{
if(_OpenMenuCommand==null)
{
_OpenMenuCommand = new RelayCommand<object>((args =>
OpenMenu(null)));
}
return _OpenMenuCommand;
}
}
private void OpenMenu(object p)
{
if (extensionView != null)
{
extensionView .Show();
}
}
private bool _IsMenuEnabled=true;
public bool IsMenuEnabled
{
get
{
return _IsMenuEnabled;
}
set
{
SetField(ref _IsMenuEnabled, value);
}
}
public bool IsMenuCheckable
{
get;
private set;
}
private bool _IsMenuChecked;
public bool IsMenuChecked
{
get
{
return _IsMenuChecked;
}
set
{
SetField(ref _IsMenuChecked, value);
}
}
}
public class ToplevelMenuViewModel:ViewModelBase
{
public ObservableCollection<MenuViewModel> ChildMenuViewModels {
get; private set; }
public String Header { get; private set; }
public ToplevelMenuViewModel(String header,
IEnumerable<MenuViewModel> childs)
{
this.Header ="_"+ header;
this.ChildMenuViewModels =new
ObservableCollection<MenuViewModel>(childs);
}
}
}
- 创建具有MenuViewModel属性的IMenu界面
醇>
public interface IMenu
{
MenuViewModel ExtensionMenuViewModel
{
get;
}
}
3.您需要在所有模块的ModuleUi中实现IMenu接口,这些模块将被加载到菜单中。
4.实施MefBootstrapper
5.Override配置聚合目录方法
6.要在目录中添加包含所有模块dll的指令目录,IMenu接口dll.Code位于
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new
AssemblyCatalog(typeof(Bootstrapper).Assembly));
AggregateCatalog.Catalogs.Add(new
AssemblyCatalog(typeof(IMenu).Assembly));
//create a directorycatalog with path of a directory conatining
//your module dlls
DirectoryCatalog dc = new DirectoryCatalog(@".\Extensions");
AggregateCatalog.Catalogs.Add(dc);
}
主项目中的
- 为IMenu interafce dll添加了refence
醇>8.在mainwindow.xaml.cs类中声明一个属性
public ObservableCollection ClientMenuViewModels {get;私人集; }
声明私有字段
private IEnumerable<IMenu> menuExtensions;
在主窗口或shell构造函数
中[ImportingConstructor]
public MainWindow([ImportMany] IEnumerable<IMenu> menuExtensions)
{
this.menuExtensions = menuExtensions;
this.DataContext=this;
}
private void InitalizeMenuAndOwners()
{
if (ClientMenuViewModels == null)
{
ClientMenuViewModels = new
ObservableCollection<ToplevelMenuViewModel>();
}
else
{
ClientMenuViewModels.Clear();
}
if (menuExtensions != null)
{
var groupings = menuExtensions.Select
(mnuext => mnuext.ClientMenuViewModel).GroupBy(mvvm =>
mvvm.ParentMenu);
foreach (IGrouping<UIMenuOptions, MenuViewModel> grouping in
groupings)
{
UIMenuOptions parentMenuName = grouping.Key;
ToplevelMenuViewModel parentMenuVM = new
ToplevelMenuViewModel(
parentMenuName.ToString(),
grouping.Select(grp => { return (MenuViewModel)grp; }));
ClientMenuViewModels.Add(parentMenuVM);
}
}}
}
- 在您的Shell.xaml或Mainwindow.xaml中定义一个菜单区域并将itemssource属性绑定到ClientMenuViewModels
醇>
<Menu HorizontalAlignment="Left"
Background="#FF0096D6"
Foreground="{StaticResource menuItemForegroundBrush}"
ItemsSource="{Binding ClientMenuViewModels}"
TabIndex="3">
<Menu.Resources>
<Style x:Key="subMneuStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="#FF0096D6" />
<Setter Property="FontFamily" Value="HP Simplified" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Background" Value="White" />
<Setter Property="Command" Value="{Binding
OpenMenuCommand}" />
<Setter Property="IsCheckable" Value="{Binding
IsMenuCheckable}" />
<Setter Property="IsChecked" Value="{Binding
IsMenuChecked, Mode=TwoWay}" />
<Setter Property="IsEnabled" Value="{Binding
IsMenuEnabled, Mode=TwoWay}" />
<Setter Property="ToolTip" Value="{Binding
ToolTipMessage, Mode=OneWay}" />
<Setter Property="ToolTipService.ShowOnDisabled" Value="
{Binding IsToolTipEnabled, Mode=OneWay}" />
<Setter Property="ToolTipService.IsEnabled" Value="
{Binding IsToolTipEnabled, Mode=OneWay}" />
<Setter Property="ToolTipService.ShowDuration"
Value="3000" />
<Setter Property="ToolTipService.InitialShowDelay"
Value="10" />
</Style>
<my:MyStyleSelector x:Key="styleSelector" ChildMenuStyle="
{StaticResource subMneuStyle}" />
<HierarchicalDataTemplate DataType="{x:Type
plugins:ToplevelMenuViewModel}"
ItemContainerStyleSelector="{StaticResource styleSelector}"
ItemsSource="{Binding ChildMenuViewModels}">
<Label Margin="0,-5,0,0"
Content="{Binding Header}"
FontFamily="HP Simplified"
FontSize="12"
Foreground="{StaticResource menuItemForegroundBrush}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type plugins:MenuViewModel}">
<Label VerticalContentAlignment="Center"
Content="{Binding Name}"
Foreground="#FF0096D6" />
</DataTemplate>
</Menu.Resources>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
</Menu>
public class MyStyleSelector : StyleSelector
{
public Style ChildMenuStyle { get; set; }
public Style TopLevelMenuItemStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject
container)
{
if (item is MenuViewModel)
{
return ChildMenuStyle;
}
//if(item is ToplevelMenuViewModel)
//{
// return TopLevelMenuItemStyle;
//}
return null;
}
}
这里是ViewModelBase类
public class ViewModelBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler =Volatile.Read(ref PropertyChanged);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
};
}
protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName="")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
RelayCommand类位于
之下
public class RelayCommand<T> : ICommand
{
#region Fields
private readonly Action<T> _execute = null;
private readonly Predicate<T> _canExecute = null;
#endregion
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command with conditional execution.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}