Страницы

Поиск по вопросам

четверг, 2 января 2020 г.

Как в меню добавить сепаратор, если оно заполняется элементами от ICommand?

#c_sharp #wpf #net #xaml


Придумал такую схему. 

Создал класс 

public class MyCommand : ICommand, ICollection


То есть каждая команда включает еще вложенные команды (вложенные меню)

В классе окна добавляю свойство

public MyCommand Menu { get; set; }


В дизайнере делаю так




И так же в ресурсы окна добавляю вот что


    



Все. В коде формирую дерево из MyCommand, и меню в окне создается автоматически по
дереву. Класс MyCommand реализует слияние, так что я спокойно могу добавлять к основному
меню дополнительные пункты (например меню, которое создано в контенте TabItem). 

Все гениально и просто :), вот только я не знаю, как отделить пункты меню сепараторами.
Я ведь не оперирую контейнерами (MenuItem), поэтому не могу просто взять и добавить
new Separator(). Посоветуйте что нибудь для этого.
    


Ответы

Ответ 1



Разделитель — это часть представления, а не вью-модели. Идея делать маппинг один-к-одному между абстрактными командами и элементами управления во всём приложении — порочна. Подобный подход может использоваться в отдельных случаях, например, при генерации списка недавно открытых файлов в меню или предоставлении плагину абстрактного интерфейса для управления представлением (и то я бы задумался об альтернативах). Запихнуть разделитель в список можно. Всего-то нужно сделать иерархию классов ("пункт меню", "пункт меню-разделитель", "пункт меню-команда"), присыпать шаблончиками и биндингами... Но хорошо задумайтесь: а правильно ли это? Если вы пойдёте по пути маппинга один-к-одному между элементами управления и вью-моделями, то вы превратите MVVM в фарс: у вас в слое вью-моделей появится упрощённая проекция представления, и представление станет полностью управляться из слоя вью-моделей. Сама вью-модель превратится в помесь контроллера и презентера. Это уже не MVVM. Короче, не надо так делать.

Ответ 2



Отвечу не совсем на данный вопрос, но тоже про меню. Просто, в комментарии на один вопрос сказал, что покажу вариант, как можно собрать меню... Сепараторы тут тоже используются. Чтобы собрать меню, можно реализовать сервис AppCommandService, который позволяет регистрировать в себе реализации ICommand и содержит свойство Menu, к которому можно привязаться из xaml. Сразу скажу, что это не идеальная реализация такого сервиса, в том числе и потому что нельзя динамически добавлять и убирать пункты меню. Тут встроен IoC контейнер - Windsor, который в принципе можно заменить на другой контейнер или заменить коллекцией. Смысл такой: 1) команде даешь атрибут, который содержит строку с названиями пунктов разделенными \: [Menu(@"Файл\Сохранить Как\", "Сохранить Как PDF", Order = 2, Break = MenuBreak.Before)] public class SaveAsPdfCommand: ICommand { ... } //order - порядковый номер при отображении меню //Break - указывать, если надо поставить СЕПАРАТОР 2) Регистрируешь эту команду в сервисе CommandService.RegisterCommand(); //CommandService = new AppCommandService() - публичное свойство вью модели 3) Привязываешься к Xaml: Реализация: Атрибут: [AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] public class MenuAttribute : Attribute { public string Path { get; protected set; } public string Name { get; protected set; } public MenuAttribute(string path, string name) :this(path) { Name = name; } protected MenuAttribute (string path) { Path = path; Break = MenuBreak.None; } public int Order { get; set; } public MenuBreak Break { get; set; } public static MenuAttribute Extract(Type t) { var attributes = t.GetCustomAttributes(typeof(MenuAttribute), false).OfType(); if (attributes == null || attributes.Count() == 0) return null; MenuAttribute result = attributes.FirstOrDefault(); //здесь можно вставить условие по выбору атрибута, если их много return result; } } public enum MenuBreak : byte { None, Before, After, Both } Сервис: public class AppCommandService { private IWindsorContainer _InternalContainer = new WindsorContainer(new XmlInterpreter(new StaticContentResource(@""))); public void RegisterCommand() where T: ICommand { _InternalContainer.Register(Component.For().LifeStyle.Singleton); } public T ResolveCommand() where T : ICommand { return _InternalContainer.Resolve(); } public void Execute(object parameter = null) where T : ICommand { var command = _InternalContainer.Resolve(); if (command != null && command.CanExecute(parameter)) { command.Execute(parameter); } } public void ExecuteOnAppDispatcher(object parameter = null) where T : ICommand { Application.Current.Dispatcher.BeginInvoke(new Action(Execute), parameter); } #region MENU //MENU... private List _DefaultMenuPaths = new List(); public void RegisterMenuPath(string path) { _DefaultMenuPaths.Add(path); } //сборка дерева меню из аттрибутов private IEnumerable GetMenuItems() { var handlers = _InternalContainer.Kernel.GetHandlers(typeof(ICommand)); var items = ( from x in handlers let attr = MenuAttribute.Extract(x.ComponentModel.Implementation) where attr != null orderby attr.Order let command = _InternalContainer.Resolve(x.ComponentModel.Implementation) as ICommand select new { attr, command} ).ToArray(); var generatedItems = new Dictionary(); var menuRoots = new List(); foreach (var item in _DefaultMenuPaths) { var path = item.Split('\\'); String currentPath = String.Empty; MenuItem parent = null; MenuItem lastItem = null; foreach (var menuPath in path) { currentPath += menuPath + "\\"; parent = lastItem; if (!generatedItems.TryGetValue(currentPath, out lastItem)) { lastItem = new MenuItem() { Header = menuPath }; generatedItems.Add(currentPath, lastItem); if (parent != null) { parent.Items.Add(lastItem); } else { menuRoots.Add(lastItem); } } } } foreach (var item in items) { var path = PreparePath(item.attr.Path).Split('\\'); MenuItem lastItem = null; MenuItem parent = null; String currentPath = String.Empty; foreach (var menuPath in path) { parent = lastItem; currentPath += menuPath + "\\"; if (!generatedItems.TryGetValue(currentPath, out lastItem)) { lastItem = new MenuItem() { Header = menuPath }; generatedItems.Add(currentPath, lastItem); if (parent != null) { parent.Items.Add(lastItem); } else { menuRoots.Add(lastItem); } } } var menu = new MenuItem { Header = item.attr.Name, Command = item.command }; if (lastItem != null) { if (item.attr.Break == MenuBreak.Before || item.attr.Break == MenuBreak.Both) { lastItem.Items.Add(new Separator()); } lastItem.Items.Add(menu); if (item.attr.Break == MenuBreak.After || item.attr.Break == MenuBreak.Both) { lastItem.Items.Add(new Separator()); } } else { menuRoots.Add(menu); } } return menuRoots; } protected ObservableCollection _MenuItems = new ObservableCollection(); public ObservableCollection MenuItems { get { return _MenuItems; } } public void GenerateMenuItems() { MenuItems.Clear(); foreach (var item in GetMenuItems()) { MenuItems.Add(item); } } #endregion //добвление child - контейнера, чтобы резолвить команды с инъекциями public AppCommandService(IWindsorContainer container) { container.AddChildContainer(_InternalContainer); } //пустой конструктор, когда не надо вставлять зависимости public AppCommandService() { } private static string PreparePath(string path) { if (String.IsNullOrWhiteSpace(path)) return String.Empty; if (path[path.Length - 1] == '\\') { return path.Substring(0, path.Length - 1); } else { return path; } } }

Комментариев нет:

Отправить комментарий