Подтвердить что ты не робот

MVVM Dynamic Menu UI от привязки с ViewModel

Я новичок в WPF и MVVM. Я работаю с командой в LoB-приложении. Мы хотели бы иметь динамический элемент управления Menu, который создает меню на основе зарегистрированного профиля пользователя. В предыдущих сценариях разработки (а именно ASP.NET) мы используем для итерации через данные, которые описывают сбор и генерируют MenuItem динамически. В MVVM, как бы я это сделал? Можно ли отделить вид XAML от ViewModel, который описывает элементы меню?

Решение:

Со входом от комментаторов я смог динамически связывать Menu с данными из ViewModel. Эта статья также очень помогла.

XAML:

<HierarchicalDataTemplate DataType="{x:Type self:Menu}" ItemsSource="{Binding Path=Children, UpdateSourceTrigger=PropertyChanged}">
    <ContentPresenter Content="{Binding Path=MenuText}" RecognizesAccessKey="True"/>
</HierarchicalDataTemplate>

[...]

<Menu Height="21" Margin="0" Name="mainMenu" VerticalAlignment="Top" HorizontalAlignment="Stretch" 
      ItemsSource="{Binding Path=MenuItems, UpdateSourceTrigger=PropertyChanged}" ItemContainerStyle="{StaticResource TopMenuItems}">
    <Menu.Background>
        <ImageBrush ImageSource="/Wpf.Modules;component/Images/MenuBg.jpg" />
    </Menu.Background>
</Menu>

Menu класс данных:

public class Menu : ViewModelBase
{
    public Menu()
    {
        IsEnabled = true;
        Children = new List<Menu>();
    }

    #region [ Menu Properties ]

    private bool _isEnabled;
    private string _menuText;
    private ICommand _command;
    private IList<Menu> _children;

    public string MenuText
    {
        get { return _menuText; }
        set
        {
            _menuText = value;
            base.OnPropertyChanged("MenuText");
        }
    }

    public bool IsEnabled
    {
        get { return _isEnabled; }
        set
        {
            _isEnabled = value;
            base.OnPropertyChanged("IsEnabled");
        }
    }

    public ICommand Command
    {
        get { return _command; }
        set
        {
            _command = value;
            base.OnPropertyChanged("Command");
        }
    }

    public IList<Menu> Children
    {
        get { return _children; }
        set
        {
            _children = value;
        }
    }

    #endregion
}
4b9b3361

Ответ 1

Попробуйте что-то вроде этого:

public class MenuItemViewModel
{
    public MenuItemViewModel()
    {
        this.MenuItems = new List<MenuItemViewModel>();
    }

    public string Text { get; set; }

    public IList<MenuItemViewModel> MenuItems { get; private set; }
}

Предположим, что у вашего DataContext есть свойство, называемое MenuItems, которое является списком MenuItemViewModel. Что-то вроде этого должно работать, затем:

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:self="clr-namespace:WpfApplication1"
        Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <HierarchicalDataTemplate DataType="{x:Type self:MenuItemViewModel}"
                                  ItemsSource="{Binding Path=MenuItems}">
            <ContentPresenter Content="{Binding Path=Text}" />
        </HierarchicalDataTemplate>
    </Window.Resources>
    <DockPanel>
        <Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" />
        <Grid />
    </DockPanel>
</Window>

Ответ 2

Это должно привести вас туда, куда вы направляетесь

<UserControl x:Class="WindowsUI.Views.Default.MenuView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:ViewModels="clr-namespace:WindowsUI.ViewModels"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
    <Style TargetType="{x:Type MenuItem}">
        <Setter Property="Header" Value="{Binding Path=DisplayName}"/>
        <Setter Property="Command" Value="{Binding Path=Command}"/>
    </Style>
    <HierarchicalDataTemplate 
        DataType="{x:Type ViewModels:MenuItemViewModel}"
        ItemsSource="{Binding Path=Items}">
    </HierarchicalDataTemplate>
</UserControl.Resources>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=Items}"/>

Обратите внимание, что в моем примере мое меню Item имеет свойство типа ICommand с именем Command.

Ответ 3

Это решение не нуждается в коде в коде, и это упрощает решение.

        <Menu>
            <MenuItem ItemsSource="{Binding Path=ChildMenuItems}" Header="{Binding Path=Header}">
                <MenuItem.Resources>
                    <HierarchicalDataTemplate DataType="{x:Type vm:MenuItemViewModel}" ItemsSource="{Binding ChildMenuItems}">
                        <MenuItem Header="{Binding Path=Header}" Command="{Binding Path=Command}"/>
                    </HierarchicalDataTemplate>
                    <DataTemplate DataType="{x:Type vm:SeparatorViewModel}">
                        <Separator>
                            <Separator.Template>
                                <ControlTemplate>
                                    <Line X1="0" X2="1" Stroke="Black" StrokeThickness="1" Stretch="Fill"/>
                                </ControlTemplate>
                            </Separator.Template>
                        </Separator>
                    </DataTemplate>
                </MenuItem.Resources>
            </MenuItem>
        </Menu>

И MenuItem представлен как:

public class MenuItemViewModel : BaseViewModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MenuItemViewModel"/> class.
        /// </summary>
        /// <param name="parentViewModel">The parent view model.</param>
        public MenuItemViewModel(MenuItemViewModel parentViewModel)
        {
            ParentViewModel = parentViewModel;
            _childMenuItems = new ObservableCollection<MenuItemViewModel>();
        }

        private ObservableCollection<MenuItemViewModel> _childMenuItems;
        /// <summary>
        /// Gets the child menu items.
        /// </summary>
        /// <value>The child menu items.</value>
        public ObservableCollection<MenuItemViewModel> ChildMenuItems
        {
            get
            {
                return _childMenuItems;
            }
        }

        private string _header;
        /// <summary>
        /// Gets or sets the header.
        /// </summary>
        /// <value>The header.</value>
        public string Header
        {
            get
            {
                return _header;
            }
            set
            {
                _header = value; NotifyOnPropertyChanged("Header");
            }
        }

        /// <summary>
        /// Gets or sets the parent view model.
        /// </summary>
        /// <value>The parent view model.</value>
        public MenuItemViewModel ParentViewModel { get; set; }

        public virtual void LoadChildMenuItems()
        {

        }
    }

Конкретные MenuItems могут быть либо созданы напрямую, либо вы можете создавать собственные субтипы через наследование.

Ответ 4

Я знаю, что это старый пост, но мне нужно это и как связывать команды.

Что касается вопроса Guge о том, как связать команды: VMMenuItems - это свойство в моем классе модели модели типа

ObservableCollection<Menu>

и Menu - это класс, определенный выше. Свойство CommandItem Command привязано к Свойству Command класса Menu. На мой взгляд, класс модели

Menu.Command = _fou

где

private ICommand _fou;

xaml

<ListView.ContextMenu>
    <ContextMenu ItemsSource="{Binding Path=VMMenuItems}">
           <ContextMenu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">                                    
                        <Setter Property="Command" Value="{Binding Command}"/>
                  </Style>
            </ContextMenu.ItemContainerStyle>
      </ContextMenu>                    
</ListView.ContextMenu>

Ответ 5

Если вам интересно, как делать разделители, это очень легко.

Код ниже является частью моего ViewModel. Поскольку XAML использует отражение, все, что мне нужно сделать, это вернуть "объект", который может быть MenuItemViewModel, Separator или (если для какой-то более простой причины, в которой я нуждался) фактический MenuItem.

Я использую yield для динамического создания элементов, потому что это просто кажется мне лучше для меня. Даже если я использую yield - если элементы меняются, мне все равно нужно поднять событие PropertyChanged для "ContextMenu", как обычно, но я не излишне сгенерировал список, пока он не понадобится.

    public IEnumerable<object> ContextMenu
    {
        get
        {
            // ToArray() needed or else they get garbage collected
            return GetContextMenu().ToArray();
        }
    }

    public IEnumerable<object> GetContextMenu()
    {
        yield return new MenuItemViewModel()
        {
            Text = "Clear all flags",
        };

        // adds a normal 'Separator' menuitem
        yield return new Separator();

        yield return new MenuItemViewModel()
        {
            Text = "High Priority"
        };

        yield return new MenuItemViewModel()
        {
            Text = "Medium Priority"
        };

        yield return new MenuItemViewModel()
        {
            Text = "Low Priority"
        };

        yield break;
    }