私は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
}
次のようなものを試してください。
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>
これはあなたが行くところにあなたを連れて行くはずです
<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のプロパティがあることに注意してください。
このソリューション コードビハインドにコードを含める必要がないため、ソリューションが簡単になります。
<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は、直接インスタンス化することも、継承を通じて独自のサブタイプを作成することもできます。
これが古い投稿であることは知っていますが、これに加えてコマンドをバインドする方法が必要です。
コマンドをバインドする方法に関する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>
セパレーターの作り方がわからない場合は、とても簡単です。
以下のコードは私のViewModelの一部です。 XAMLはリフレクションを使用するため、必要なのは 'object'を返すことだけです。これは、MenuItemViewModel
、Separator
、または(何らかの奇妙な理由で必要な場合は)実際の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;
}