【问题标题】:get selected menuitem wpf获取选定的菜单项 wpf
【发布时间】:2018-12-20 00:05:33
【问题描述】:

我有一个菜单和一些子菜单

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() 的函数,这将获取最后一个选定值的标题,即“支持”,但我无法弄清楚如何将该值添加到列表框。

这是视图模型

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 中的列表中,但无法进行任何操作。有没有可以在这里使用的类似于组合框的 SelectedValue 的东西?感谢您的帮助

【问题讨论】:

标签: c# wpf menu menuitem


【解决方案1】:

这实际上比您想象的要困难得多,因为您使用菜单的方式并不是真正为使用而设计的。

首先,您需要在视图模型中绑定一个项目列表,例如:

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 Command 绑定以指向这个通用处理程序:

<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 的通常使用方式。

【讨论】:

  • 哇,好的,谢谢马克,我会努力实现这个。您知道实现此类功能的更好方法吗?我想过一个组合框,但我不知道如何用一个组合框来做子菜单。
【解决方案2】:

在任何已创建的MenuItem 上放置一个Click 事件,并在触发该事件时,从源的DataContext 中获取数据项,该数据项为MenuItem 播种。

示例

我在Recent 菜单下创建了一个最近需要作为子菜单的操作列表。我将子列表作为MRU 类型的模型播种,用于通过ItemsSource 加载菜单并保存在ObservableCollection&lt;MRU&gt;() 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 gist Gradient Menu Example with MRU 中查看

【讨论】:

    猜你喜欢
    • 2013-04-18
    • 1970-01-01
    • 2022-11-02
    • 2017-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-30
    相关资源
    最近更新 更多