Ищите пример Приз Модулей, загружающих себя в меню
Кто-нибудь знает примеры кода WPF, используя Prism, в котором каждый модуль регистрируется как элемент меню в меню внутри другого модуля?
(В настоящее время у меня есть приложение, которое пытается сделать это с помощью 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>
Но я знаю, что я создал структуру приложения MVPM без призмы, и меню всегда были хорошо привязаны к ObservableCollections в ViewModel, и выше, похоже, нарушает этот хороший шаблон. Является ли это обычным способом сделать это в Призме?
Ответы
Ответ 1
Update:
Я создал образец для вас. Здесь: Пример
У вас появилось несколько вещей, о которых вы, вероятно, еще не подумали, например, контракт, который позволит вашим модулям управлять вашей оболочкой (чтобы вы могли делать такие вещи, как Open Window, что-то вроде этого). Он разработан с учетом MVVM... Я не знаю, если вы используете это, но я бы это рассмотрел.
Я попробовал несколько минут, чтобы получить заголовки вкладок правильно, но я закончил тем, что оставил "A Tab". Он остался для вас упражнением, если вы идете с пользовательским интерфейсом с вкладками. Я разработал его для того, чтобы быть беззаботным, поэтому вы можете заменить XAML в Shell.xaml, не нарушая ничего. Это одно из преимуществ для материалов RegionManager, если вы используете его правильно.
В любом случае, удачи!
Я никогда не видел примера этого, но вам придется реализовать это самостоятельно.
Вам нужно создать свой собственный интерфейс, что-то вроде этого:
public interface IMenuRegistry
{
void RegisterViewWithMenu(string MenuItemTitle, System.Type viewType);
}
Затем ваши модули будут объявлять зависимость от IMenuRegistry и регистрировать свои представления.
Ваша реализация IMenuRegistry (которую вы, скорее всего, реализуете и зарегистрируете в том же проекте, на котором размещается ваш Bootstrapper), вы бы добавили эти пункты меню в свое меню или древовидную структуру или все, что вы используете для своего меню.
Когда пользователь нажимает на элемент, вам нужно будет использовать ваш метод Bootstrapper.Container.Resolve(viewType)
для создания экземпляра представления и заполнения его каким-либо заполнителем, который вы хотите показать.
Ответ 2
Я использую MEF вместе с призмой 6.0 и MVVM
1.Создайте класс Menuviewmodel для класса Leafmenu и TopLevel MenuViewmodel для меню Toplevel. Класс Menuviewmodel будет иметь все свойства, с которыми вы хотите связать свое меню. Moduleui, реализующий этот interafce, должен иметь такой атрибут
[Экспорт (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);
}
}
}
- Создайте интерфейс IMenu, у которого есть свойство MenuViewModel
public interface IMenu
{
MenuViewModel ExtensionMenuViewModel
{
get;
}
}
3. Вам необходимо реализовать интерфейс IMenu в ModuleUi всех ваших модулей, которые будут загружены в меню.
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);
}
- в вашем основном проекте добавьте refence в IMenu interafce dll
8. В классе mainwindow.xaml.cs объявить свойство
public ObservableCollection ClientMenuViewModels { получить; частный набор; }
объявить частное поле
private IEnumerable<IMenu> menuExtensions;
-
В вашем конструкторе mainwindow или 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
}