Каков наилучший способ в MVVM создать меню, которое отображает различные страницы?

StackOverflow https://stackoverflow.com/questions/1018483

  •  06-07-2019
  •  | 
  •  

Вопрос

Я хочу создать простое приложение с шаблоном MVVM.

Это приложение будет состоять из двух основных частей:

  • меню сверху
  • Содержание ниже

Навигация будет простой:

  • каждый пункт меню (например,"Управление клиентами" или "Просмотр отчетов") позволит заполните область содержимого с новой страницей, которая обладает определенной функциональностью

У меня есть делал это раньше с кодом позади где в кодовом обработчике событий для пунктов меню были загружены все страницы, а та, которая должна быть отображена, была загружена как дочерняя для StackPanel.Это, однако, не будет работать в MVVM, поскольку вы не хотите вручную заполнять StackPanel, но отображать, напримеробъект "PageItem" с табличкой данных и т.д.

Итак, те из вас, кто создал простое приложение с клик-меню, подобное этому, с помощью MVVM, какова была ваша базовая структура приложения? Я размышляю в этом направлении:

MainView.xaml:

<DockPanel LastChildFill="False">

    <Menu 
        ItemsSource="{Binding PageItemsMainMenu}" 
        ItemTemplate="{StaticResource MainMenuStyle}"/>

    <ContentControl 
        Content="{Binding SelectedPageItem}"/>        

</DockPanel>

где меню заполнено коллекцией "элементов страницы", а DataTemplate отображает заголовок каждого "объекта элемента страницы" в качестве заголовка каждого элемента меню.

И ContentControl будет заполнен парой View / ViewModel, которая обладает полной функциональностью, но я не уверен в этом.

Это было полезно?

Решение

Во-первых, я думаю, вам следует сохранить обработчик событий в коде, нет смысла менять простой двухстрочный обработчик событий на сложный командный монстр без каких-либо практических причин (и не говорите о тестируемости, это главное меню, оно будет тестироваться при каждом запуске приложения).

Теперь, если вы действительно хотите пойти по чистому маршруту MVVM, все, что вам нужно сделать, это заставить ваше меню запустить команду, сначала в каком-нибудь разделе ресурсов добавьте этот стиль:

<Style x:Key="MenuItemStyle" TargetType="MenuItem">
    <Setter Property="Command" 
            Value="{Binding DataContext.SwitchViewCommand,
            RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/>
    <Setter Property="CommandParameter" 
            Value="{Binding}"/>
</Style>

Этот стиль заставит пункт меню запускать команду SwitchViewCommand в прикрепленной модели представления с DataContext элемента MenuItem в качестве параметра команды.

Фактическое представление совпадает с вашим кодом с дополнительной ссылкой на этот стиль в качестве ItemContainerStyle (поэтому оно применяется к пункту меню, а не к содержимому DataTemplate):

<DockPanel LastChildFill="False">

    <Menu DockPanel.Dock="Top"
        ItemsSource="{Binding PageItemsMainMenu}" 
        ItemTemplate="{StaticResource MainMenuStyle}"
        ItemContainerStyle="{StaticResource MenuItemStyle}"/>
    <ContentControl 
    Content="{Binding SelectedPageItem}"/>
</DockPanel>

Теперь в нужной вам модели представления (я использовал строки, потому что у меня нет вашего кода PageItem):

private string _selectedViewItem;
public List<string> PageItemsMainMenu { get; set; }
public string SelectedPageItem
{
    get { return _selectedViewItem; }
    set { _selectedViewItem = value; OnNotifyPropertyChanged("SelectedPageItem"); }
}
public ICommand SwitchViewCommand { get; set; }

И используйте любой командный класс, который вы используете, чтобы заставить команду вызывать этот код:

private void DoSwitchViewCommand(object parameter)
{
    SelectedPageItem = (string)parameter;
}

Теперь, когда пользователь нажимает на пункт меню, этот пункт меню вызывает SwitchViewCommand с элементом страницы в качестве параметра.

Команда вызовет команду DoSwitchViewCommand, которая установит свойство SelectedPageItem

Свойство вызовет NotifyPropertyChanged, которое произведет обновление пользовательского интерфейса через привязку данных.

Или вы можете написать 2-строчный обработчик событий на ваш выбор

Другие советы

я мог бы представить себе ObservableCollection в виртуальной машине, которая содержит все страницы, которые можно вызвать из меню.Затем привяжите ItemsControl и ContentControl к нему, чтобы ContentControl всегда показывал CurrentItem из этого списка.Конечно, меню будет привязано только к некоторому свойству Title тогда как ContentControl примет весь элемент целиком и подключит некоторое подходящее представление в соответствии с типом.

Другой вариант - использовать ListBox вместо меню, оформить ListBox так, чтобы он выглядел как меню, а затем вы можете привязать к выбранному значению, вот так:

<DockPanel LastChildFill="False">

    <ListBox 
        ItemsSource="{Binding PageItemsMainMenu}" 
        ItemTemplate="{StaticResource MainMenuStyle}"
        IsSynchronizedWithCurrentItem="True"/>

    <ContentControl 
        Content="{Binding PageItemsMainMenu/}"/>        

</DockPanel>

Обратите внимание на IsSynchronizedWithCurrentItem="True" для установки выбранного элемента и {Binding PageItemsMainMenu/} с косой чертой в конце, чтобы использовать его.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top