获取选定的菜单项 wpf
get selected menuitem wpf
我有一个菜单和一些子菜单
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() 的函数,这将获取最后选择的值的 Header,即 "support",但我不知道如何将该值添加到列表框。
这是视图模型
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" />
我已尝试将 Header 添加到 ViewModel 中的列表,但无法正常工作。是否有类似于组合框的 SelectedValue 的东西可以在这里使用?感谢帮助
这实际上比您预期的要难得多,因为您使用菜单的方式并非真正设计用于使用。
首先,您需要在视图模型中有一个要绑定的项目列表,例如:
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,但无论您的框架是什么,它都支持将参数传递到您的执行函数中,所以我会留给您查找。无论哪种方式,您的 MenuItem 命令绑定现在都需要修改以指向此通用处理程序:
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding ElementName=buildingMenu, Path=DataContext.Command}" />
</Style>
您的另一个要求是在选择每个子菜单项时填充列表,这就是事情变得令人讨厌的地方。子菜单在关闭时不会触发命令,它们会触发 SubmenuOpened 和 SubmenuClosed 事件。事件无法绑定到视图模型中的命令处理程序,因此您必须改用代码隐藏处理程序。如果您在 XAML 中明确声明您的 MenuItems,您可以直接在那里设置它们,但是您的 Menu 绑定到一个集合,因此您的 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 的通常使用方式。
在创建的任何 MenuItem
上放置一个 Click
事件,当事件被触发时,从为 [=14= 播种的源 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;
}
请注意,完整的菜单代码可以在 public 要点中查看 Gradient Menu Example with MRU
我有一个菜单和一些子菜单
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() 的函数,这将获取最后选择的值的 Header,即 "support",但我不知道如何将该值添加到列表框。
这是视图模型
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" />
我已尝试将 Header 添加到 ViewModel 中的列表,但无法正常工作。是否有类似于组合框的 SelectedValue 的东西可以在这里使用?感谢帮助
这实际上比您预期的要难得多,因为您使用菜单的方式并非真正设计用于使用。
首先,您需要在视图模型中有一个要绑定的项目列表,例如:
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,但无论您的框架是什么,它都支持将参数传递到您的执行函数中,所以我会留给您查找。无论哪种方式,您的 MenuItem 命令绑定现在都需要修改以指向此通用处理程序:
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding ElementName=buildingMenu, Path=DataContext.Command}" />
</Style>
您的另一个要求是在选择每个子菜单项时填充列表,这就是事情变得令人讨厌的地方。子菜单在关闭时不会触发命令,它们会触发 SubmenuOpened 和 SubmenuClosed 事件。事件无法绑定到视图模型中的命令处理程序,因此您必须改用代码隐藏处理程序。如果您在 XAML 中明确声明您的 MenuItems,您可以直接在那里设置它们,但是您的 Menu 绑定到一个集合,因此您的 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 的通常使用方式。
在创建的任何 MenuItem
上放置一个 Click
事件,当事件被触发时,从为 [=14= 播种的源 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;
}
请注意,完整的菜单代码可以在 public 要点中查看 Gradient Menu Example with MRU