Ищете пример Prism, когда модули загружаются сами в меню
-
18-09-2019 - |
Вопрос
Кто-нибудь знает о примерах кода WPF с использованием Prism, в которых каждый модуль регистрирует себя как menuitem в меню внутри другого модуля?
(В настоящее время у меня есть приложение, которое пытается сделать это с помощью EventAggregator, поэтому один модуль прослушивает опубликованные события из других модулей, у которых должен быть заголовок в меню в качестве пункта меню, но у меня возникают проблемы с порядком загрузки и потоковой передачи и т.д.Я хочу найти пример, в котором для этого используется классическая структура призмы.)
Я думаю в терминах этого:
Оболочка.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>
Но, насколько я знаю, я создал структуру приложения без призмы MVVM, и меню всегда были хорошо привязаны к ObservableCollections в ViewModel, и приведенное выше, похоже, нарушает этот приятный шаблон.Является ли описанное выше обычным способом сделать это в Prism?
Решение
Обновить:
Я создал образец для вас.Это здесь: Образец
В нем есть несколько вещей, о которых вы, вероятно, еще не подумали, например, контракт, который позволит вашим модулям управлять вашей оболочкой (так что вы можете делать такие вещи, как Open Window и тому подобное).Он разработан с учетом MVVM...Я не знаю, используете ли вы это, но я бы подумал об этом.
В течение нескольких минут я пытался исправить названия вкладок, но в итоге остановился на "Вкладке".Это оставлено в качестве упражнения для вас, если вы используете пользовательский интерфейс с вкладками.Я разработал его так, чтобы он не выглядел, так что вы можете заменить XAML в Shell.xaml, ничего не нарушая.Это одно из преимуществ RegionManager, если вы используете его правильно.
В любом случае, удачи!
Я никогда не видел подобного примера, но вам пришлось бы реализовать это самостоятельно.
Вам пришлось бы создать свой собственный интерфейс, что-то вроде этого:
public interface IMenuRegistry
{
void RegisterViewWithMenu(string MenuItemTitle, System.Type viewType);
}
Затем ваши модули объявили бы зависимость от IMenuRegistry и зарегистрировали бы свои представления.
Ваша реализация IMenuRegistry (которую вы, вероятно, внедрили бы и зарегистрировали в том же проекте, в котором размещен ваш загрузчик), вы бы добавили эти пункты меню в свое меню, treeview или что бы вы ни использовали для своего меню.
Когда пользователь нажимает на элемент, вам придется использовать свой Bootstrapper.Container.Resolve(viewType)
метод создания экземпляра представления и размещения его в любом заполнителе, в котором вы хотите его отобразить.
Другие советы
Я использую MEF вместе с prism 6.0 и MVVM
1.Создайте класс Menuviewmodel для Leafmenu и класс MenuViewmodel верхнего уровня для меню верхнего уровня.Класс Menuviewmodel будет иметь все свойства, с которыми вы хотите связать свое меню.Moduleui, реализующий это взаимодействие, должен иметь такой атрибут, как этот
[Экспорт (тип (имя))]
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.Переопределите метод настройки aggregate catalog
6.To в каталог добавьте директорию, содержащую все библиотеки DLL вашего модуля, именуемую interface dll.Код приведен ниже
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
8.In класс mainwindow.xaml.cs объявляет свойство
общедоступная наблюдаемая коллекция ClientMenuViewModels { получить;частный набор;}
объявить закрытое поле
private IEnumerable<IMenu> menuExtensions;
В вашем mainwindow или конструкторе оболочки
[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
}