我正在与一个关于LoB应用程序的团队合作。我们希望有一个动态Menu
控件,它根据登录的用户配置文件创建菜单。在以前的开发方案(即ASP.NET)中,我们使用迭代描述集合的数据并动态生成MenuItem
。在MVVM中我该怎么做?我可以将XAML视图与描述菜单元素的ViewModel分开吗?
解决方案:
通过评论员的输入,我能够动态地将Menu
与来自ViewModel的数据绑定。这article也很有帮助。
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
}
答案 0 :(得分:15)
尝试这样的事情:
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>
答案 1 :(得分:13)
这可以让你到达目的地
<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}"/>
请注意,在我的示例中,我的菜单项具有ICommand类型的属性,称为Command。
答案 2 :(得分:5)
This 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>
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可以直接实例化,也可以通过继承制作自己的SubType。
答案 3 :(得分:4)
我知道这是一个旧帖子,但我需要这个以及如何绑定命令。
至于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 :(得分:1)
如果你想知道如何做分隔符,那真的很容易。
以下代码是我的ViewModel的一部分。由于XAML使用反射,我需要做的就是返回“对象”,它可以是MenuItemViewModel
,Separator
,或者(如果出于某些我需要的原因)实际的MenuItem
。
我正在使用yield
来动态生成项目,因为它似乎对我来说更好。即使我正在使用yield
- 如果项目发生更改,我仍然需要像往常一样为PropertyChanged
举办"ContextMenu"
事件,但在需要之前我不会不必要地生成列表。< / p>
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;
}