web-dev-qa-db-ja.com

ViewModelとのバインドによるMVVMダイナミックメニューUI

私はLoBアプリケーションのチームと協力しています。ログインしたユーザープロファイルに基づいてメニューを作成する動的なMenuコントロールが必要です。以前の開発シナリオ(つまりASP.NET)では、収集を説明するデータを反復処理し、MenuItemを動的に生成するために使用していました。 MVVMでこれを行うにはどうすればよいですか?メニュー要素を説明するViewModelからXAMLビューを分離できますか?

解決策:

コメンテーターからの入力により、ViewModelからのデータとMenuを動的にバインドすることができました。この 記事 も大いに役立ちました。

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
}
16
Raj

次のようなものを試してください。

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

    public string Text { get; set; }

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

DataContextにMenuItemViewModelのリストであるMenuItemsというプロパティがあると想定します。このようなものが機能するはずです:

<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>
16
Mark Seemann

これはあなたが行くところにあなたを連れて行くはずです

<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にCommandというタイプのICommandのプロパティがあることに注意してください。

14
Adam Lenda

このソリューション コードビハインドにコードを含める必要がないため、ソリューションが簡単になります。

        <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()
        {

        }
    }

具体的なMenuItemは、直接インスタンス化することも、継承を通じて独自のサブタイプを作成することもできます。

5
boskicthebrain

これが古い投稿であることは知っていますが、これに加えてコマンドをバインドする方法が必要です。

コマンドをバインドする方法に関するGugeの質問について:VMMenuItemsは、タイプのビューモデルクラスのプロパティです。

ObservableCollection<Menu>

menuは、上記で定義されたクラスです。 MenuItemのCommandプロパティは、MenuクラスのCommandプロパティにバインドされています。私のビューモデルクラスでは

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>
4
mike

セパレーターの作り方がわからない場合は、とても簡単です。

以下のコードは私のViewModelの一部です。 XAMLはリフレクションを使用するため、必要なのは 'object'を返すことだけです。これは、MenuItemViewModelSeparator、または(何らかの奇妙な理由で必要な場合は)実際のMenuItem

yieldを使用してアイテムを動的に生成しています。これは、読みやすくなっているように見えるためです。 yieldを使用していますが、アイテムが変更された場合でも、通常どおり"ContextMenu"に対してPropertyChangedイベントを発生させる必要がありますが、必要になるまでリストを不必要に生成することはありません。 。

    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;
    }
1
Simon_Weaver