我有一个菜单和一些子菜单
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel { Header = "Select a Building",
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel { Header = "Building 4",
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel { Header = "< 500",
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel {Header = "Executives" },
new MenuItemViewModel {Header = "Engineers" },
new MenuItemViewModel {Header = "Sales" },
new MenuItemViewModel {Header = "Marketing"},
new MenuItemViewModel {Header = "Support"}
}
},
new MenuItemViewModel { Header = "500 - 999",
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel {Header = "Executives" },
new MenuItemViewModel {Header = "Engineers" },
new MenuItemViewModel {Header = "Sales" },
new MenuItemViewModel {Header = "Marketing"},
new MenuItemViewModel {Header = "Support"}
}
}
}
}
}
我正在尝试捕获用户所做的每个选择的值,并将它们显示在列表框中。例如,用户选择"Building 4“,然后选择"500 - 999”,然后选择"support“,因为这些值已填充到列表框中。我在MenuItemViewModel中有一个名为Execute()的函数,它将获得最后选择的值的标题,即"support“,但我不知道如何将该值放到列表框中。
这是ViewModel
public class MenuItemViewModel
{
private readonly ICommand _command;
string Value;
public ObservableCollection<Cafe> Cafes
{
get;
set;
}
public MenuItemViewModel()
{
_command = new CommandViewModel(Execute);
}
public string Header { get; set; }
public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
public ICommand Command
{
get
{
return _command;
}
}
public void Execute()
{
MessageBox.Show("Clicked at " + Header);
}
}
最后是MenuItem和ListBox的xaml:
<Menu x:Name="buildingMenu" Margin="0,15,0,0" HorizontalAlignment="Left" Height="20" Width="200" ItemsSource="{Binding MenuItems}" >
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<ListBox Name="selectionListBox" HorizontalAlignment="Right" Height="179" Margin="0,177,55,0" VerticalAlignment="Top" Width="500" />
我尝试将标题添加到ViewModel中的列表中,但我无法获得任何工作。是否有类似于combobox的SelectedValue的东西可以在这里使用?谢谢你的帮助
发布于 2018-07-12 07:36:41
这实际上比您预期的要难得多,因为您使用菜单的方式并不是真正设计为使用它们的。
首先,您将需要视图模型中要绑定到的项的列表,例如:
public ObservableCollection<string> ListItems { get; set; }
以及ListBox中相应的绑定:
<ListBox ItemsSource="{Binding ListItems}" Name="selectionListBox" />
接下来,您需要在单击项目时使用这些项目填充此列表。对于您目前的处理方式来说,这很棘手,因为ListItems
需要在您的主视图模型中,而命令处理程序在MenuItemViewModel中。这意味着您需要为MenuItemViewModel添加一种调用其父对象的方法,或者更好地将命令处理程序移动到主视图模型(以便它现在是共享的),并让MenuItems在其数据上下文对象中传递。我使用MVVM Lite,在该框架中,您可以执行以下操作:
private ICommand _Command;
public ICommand Command
{
get { return (_Command = (_Command ?? new RelayCommand<MenuItemViewModel>(Execute))); }
}
public void Execute(MenuItemViewModel menuItem)
{
// ... do something here...
}
您似乎没有使用MVVM Lite,但是无论您的框架是什么,它都支持将参数传递给您的execute函数,所以我将把它留给您去查看。无论哪种方式,您的MenuItem命令绑定现在都需要修改为指向此公共处理程序:
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding ElementName=buildingMenu, Path=DataContext.Command}" />
</Style>
您的另一个需求是在选择每个子菜单项时填充列表,这就是事情变得糟糕的地方。子菜单在关闭时不会触发命令,它们会触发SubmenuOpened和SubmenuClosed事件。事件无法绑定到视图模型中的命令处理程序,因此您必须改用代码隐藏处理程序。如果你在XAML中显式声明你的MenuItems,你可以直接在那里设置它们,但是你的菜单绑定到一个集合,所以你的MenuItems是由框架填充的,你必须通过在你的样式中添加一个EventSetter来设置处理程序:
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="SubmenuOpened" Handler="OnSubMenuOpened" />
<Setter Property="Command" Value="{Binding ElementName=buildingMenu, Path=DataContext.Command}" />
</Style>
这是可行的,因为在MainWindow代码隐藏中调用指定的处理程序函数:
private void OnSubmenuOpened(object sender, RoutedEventArgs e)
{
// uh oh, what now?
}
当然,问题是视图中存在事件处理程序,但在MVVM中,我们需要在视图模型中使用它。如果您乐于像这样破坏视图,以便让应用程序正常工作,那么可以查看this SO question中的一些示例代码,了解如何让应用程序正常工作。但是,如果您像我一样是MVVM的铁杆分子,那么您可能需要一个不涉及代码隐藏的解决方案,在这种情况下,您将再次需要参考您的框架。MVVM Lite提供了一种允许您将事件转换为命令的行为,通常是这样使用的:
<MenuItem>
<i:Interaction.Triggers xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:EventTrigger EventName="OnSubmenuOpened">
<cmd:EventToCommand Command="{Binding SubmenuOpenedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
然而,行为的问题是,你不能将它们设置为一种风格。样式适用于所有对象,这就是为什么EventSetter工作得很好。必须为每个使用行为的对象创建行为。
因此,最后一个难题是,如果你在MVVM中有需要在样式中设置行为的情况,那么你将需要使用vspivak在他的文章Using System.Windows.Interactivity Behaviors and Actions in WPF/Silverlight styles中发布的解决方案。我自己在商业项目中使用过它,对于将事件处理程序重定向到样式中的命令处理程序这一更大的问题来说,它是一个很好的通用解决方案。
希望这回答了你的问题。我肯定这看起来非常复杂,但你想做的是一个相当病态的场景,远远超出了WPF的典型使用方式。
发布于 2021-11-08 06:36:49
将Click
事件放在创建的任何MenuItem
上,当该事件被激发时,从作为MenuItem
种子的源的DataContext
中获取数据项。
示例
我创建了一个最近的操作列表,这些操作需要作为Recent
菜单下的子菜单。我将子列表设定为MRU
类型的模型,该模型用于通过ItemsSource
加载菜单*并保存在ObservableCollection<MRU>() MRUS
中:
Xaml
<MenuItem Header="Recent"
Click="SelectMRU"
ItemsSource="{Binding Path=MRUS}">
...
</MenuItem>
代码背后
private void SelectMRU(object sender, RoutedEventArgs e)
{
try
{
var mru = (e.OriginalSource as MenuItem).DataContext as MRU;
if (mru is not null)
{
VM.ClearAll();
this.Title = mru.Name;
ShowJSON(File.ReadAllText(mru.Address));
}
}
catch (Exception ex)
{
VM.Error = ex.Demystify().ToString();
}
}
public class MRU
{
public string Name { get; set; }
public string Address { get; set; }
public string Data { get; set; }
public bool IsValid => !string.IsNullOrWhiteSpace(Name);
public bool IsFile => !string.IsNullOrWhiteSpace(Address);
public bool IsData => !IsFile;
public MRU(string address)
{
if (string.IsNullOrWhiteSpace(address)
|| !File.Exists(address)) return;
Address = address;
Name = System.IO.Path.GetFileName(address);
}
// This will be displayed as a selectable menu item.
public override string ToString() => Name;
}
请注意,完整的菜单代码可在公共gist Gradient Menu Example with MRU中查看
https://stackoverflow.com/questions/51294103
复制相似问题