Buscando Prisma ejemplo de carga de módulos a sí mismos en un menú
-
18-09-2019 - |
Pregunta
¿Alguien sabe de ejemplos de código WPF usando Prism en el que los módulos de registrar cada uno a sí mismos como un elemento de menú en un menú dentro de otro módulo?
(He Actualmente tengo una aplicación que intenta hacer esto con la EventAggregator, por lo que un módulo de escucha los eventos publicados de otros módulos que necesitan tener su título en el menú como un elemento de menú, pero estoy problemas para conseguir con el orden de carga y roscado, etc. Quiero encontrar un ejemplo que utiliza la estructura clásica de la prisma para hacer esto).
Estoy pensando en términos de lo siguiente:
Shell.xaml:
<DockPanel>
<TextBlock Text="Menu:" DockPanel.Dock="Top"/>
<Menu
Name="MenuRegion"
cal:RegionManager.RegionName="MenuRegion"
DockPanel.Dock="Top"/>
</DockPanel>
Los contratos Ver:
<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>
Clientes Ver:
<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>
Sin embargo, hasta saber que he hecho estructura de la aplicación no Prisma MVVM y menús eran siempre muy bien obligado a ObservableCollections en el modelo de vista y lo anterior parece romper este patrón agradable. Se lo anterior, la forma habitual de hacerlo en Prisma?
Solución
Actualización:
He creado una muestra para usted. Es aquí: ejemplo
Tiene algunas cosas que probablemente no ha pensado todavía, como un contrato que permitirá a sus módulos para el control de la shell (por lo que puede hacer cosas como ventana abierta, ese tipo de cosas). Está diseñado con MVVM en cuenta ... No sé si está utilizando, pero yo lo consideraría.
He intentado durante unos minutos para obtener los títulos de las fichas correcta, pero terminé dejando fuera con "A Tab". Se deja como ejercicio para usted si usted va con una interfaz de usuario con pestañas. He diseñado que sea lookless, para que pueda reemplazar el XAML en el Shell.xaml sin romper nada. Esa es una de las ventajas de la materia RegionManager si se utiliza bien.
De todos modos, buena suerte!
Nunca he visto un ejemplo de esto, pero que tendría que poner en práctica esto por sí mismo.
Usted tendría que crear su propia interfaz, algo como esto:
public interface IMenuRegistry
{
void RegisterViewWithMenu(string MenuItemTitle, System.Type viewType);
}
Su Módulos entonces sería declarar una dependencia de una IMenuRegistry y registrar sus puntos de vista.
Su puesta en práctica de IMenuRegistry (que probablemente implementar y registrar en el mismo proyecto que aloja su programa previo) deberá añadir los elementos de menú al menú o vista de árbol o lo que usted está utilizando para su menú.
Cuando un usuario hace clic en un elemento que tendrá que utilizar el método de Bootstrapper.Container.Resolve(viewType)
para crear una instancia de la vista y rellenarlo en cualquier marcador de posición que desea mostrar en.
Otros consejos
Estoy usando MEF junto con el prisma 6.0 y MVVM
1.Create una clase Menuviewmodel para Leafmenu y TopLevel MenuViewmodel clase para el menú de Nivel Superior. Menuviewmodel clase tendrá todas las propiedades que desea vincular a su menú con. Moduleui la aplicación de esta interafce debe tener un atributo como esto
[Exportar (typeof (imenu))]
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);
}
}
}
- Crear una wich imenu interfaz tiene la propiedad MenuViewModel
public interface IMenu
{
MenuViewModel ExtensionMenuViewModel
{
get;
}
}
3.You necesidad de aplicar imenu interfaz en ModuleUi de todos los módulos que se cargan en un menú.
4.Implement MefBootstrapper
5.Override Configurar método de catálogo global
6.To el catálogo añadir catálogo diretory que contiene todos los archivos DLL de módulo, dll.Code interfaz imenu está por debajo
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);
}
- en su proyecto principal añadir refence a imenu interafce DLL
8.In mainwindow.xaml.cs clase declarar una propiedad
ClientMenuViewModels ObservableCollection pública { obtener; conjunto privado; }
declarar un campo privado
private IEnumerable<IMenu> menuExtensions;
-
En su MainWindow o shell constructor
[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); } }}
}
- En su Shell.xaml o MainWindow.xaml definir una zona de menús y se unen a la propiedad 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;
}
}
aquí es la clase 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;
}
}
clase RelayCommand está por debajo
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
}