C# 与 ViewModel 绑定的 MVVM 动态菜单 UI
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1392160/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
MVVM Dynamic Menu UI from binding with ViewModel
提问by Raj
I am working with a team on LoB application. We would like to have a dynamic Menu
control, which creates the menu based on the logged in user profile. In previous development scenarios (namely ASP.NET) we use to iterate through data which describes collection and generate MenuItem
dynamically. In MVVM how would I do this? Can I separate XAML view from ViewModel which describes menu elements?
我正在与 LoB 应用程序的团队合作。我们想要一个动态Menu
控件,它根据登录的用户配置文件创建菜单。在以前的开发场景(即 ASP.NET)中,我们使用迭代来描述集合和MenuItem
动态生成的数据。在 MVVM 中,我将如何执行此操作?我可以将 XAML 视图与描述菜单元素的 ViewModel 分开吗?
Solution:
解决方案:
With inputs from commentators I were able to bind Menu
dynamically with the data from ViewModel. This articlewas of great help too.
通过评论员的输入,我能够Menu
动态绑定来自 ViewModel 的数据。这篇文章也很有帮助。
XAML:
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
data class:
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
}
采纳答案by Mark Seemann
Try something like this:
尝试这样的事情:
public class MenuItemViewModel
{
public MenuItemViewModel()
{
this.MenuItems = new List<MenuItemViewModel>();
}
public string Text { get; set; }
public IList<MenuItemViewModel> MenuItems { get; private set; }
}
Assume that your DataContext has a property called MenuItems which is a list of MenuItemViewModel. Something like this should work, then:
假设您的 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>
回答by mike
I know this is an old post but I need this plus how to bind Commands.
我知道这是一篇旧帖子,但我需要这个以及如何绑定命令。
As to Guge's question on how to bind Commands: VMMenuItems is a property in my view model class of type
至于古格关于如何绑定命令的问题:VMMenuItems is a property in my view model class of type
ObservableCollection<Menu>
and Menu is the class defined above. The MenuItem's Command Property is being bound to the Command Property of the Menu class. In my view model class
而 Menu 是上面定义的类。MenuItem 的 Command 属性绑定到 Menu 类的 Command 属性。在我的视图模型类中
Menu.Command = _fou
where
在哪里
private ICommand _fou;
The xaml
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>
回答by Adam Lenda
This should get you where you are going
这应该让你去你要去的地方
<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}"/>
Note that in my example, my menu Item has a property of type ICommand called Command.
请注意,在我的示例中,我的菜单项有一个名为 Command 的 ICommand 类型属性。
回答by Simon_Weaver
If you're wondering how to do separators it's really quite easy.
如果你想知道如何做分隔符,那真的很容易。
The code below is part of my ViewModel. Since XAML uses reflection all I need to do is to return 'object' which can be a MenuItemViewModel
, Separator
, or (if for some wierd reason I needed to) an actual MenuItem
.
下面的代码是我的 ViewModel 的一部分。由于XAML使用反射所有我需要做的是回到“对象”可以是一个MenuItemViewModel
,Separator
或者(如果出于某种奇怪的原因,我需要)的实际MenuItem
。
I'm using yield
to dynamically generate the items because it just seems to read better for me. Even though I'm using yield
- if the items change I still need to raise a PropertyChanged
event for "ContextMenu"
as usual but I don't unnecessarily generate the list until it's needed.
我正在使用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;
}
回答by boskicthebrain
This solutiondoesn't need any code in code behind and that makes it simpler solution.
此解决方案不需要任何代码背后的代码,这使得它更简单的解决方案。
<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>
And MenuItem is represented as:
而 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()
{
}
}
The concrete MenuItems can be either instantiated directly or you could make your own SubTypes through inheritance.
具体的 MenuItem 可以直接实例化,也可以通过继承创建自己的 SubType。