如何从菜单项中提取事件

3
在这个应用程序中,我在一个用户控件中放置了一个菜单。
<UserControl>
    <Grid>
        <Menu x:Name="mainMenu">
            <MenuItem  Header="File">
                <MenuItem Header="Open Analysis">
                    <MenuItem Header="Load all" />
                    <MenuItem Header="Select from tracks" />
                </MenuItem>
            </MenuItem>
        </Menu>
     </Grid>
</UserControl>

我希望将菜单上的所有“click”事件路由和冒泡到Usercontrol中的一个Click事件。因此,我实现了以下代码:
public static readonly RoutedEvent ClickEvent = eventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MenuItem));
public event RoutedEventHandler Click
{
    add { AddHandler(ClickEvent, value); }
    remove { RemoveHandler(ClickEvent, value); }
}

但是这在异常情况下会失败:
{"RoutedEvent Name 'Click' for OwnerType 'System.Windows.Controls.MenuItem' 
already used."}

我猜这个事件被注册了多次,因为有多个菜单项,所以第二次注册失败。

但是,我怎样才能将所有这些事件“捆绑”(就像一个单一的处理程序附加点)成一个用户控件中的单个事件,并以良好的可维护方式?由于某些子菜单由数据库提供,因此是动态构建的。


您可以为所有的菜单项定义一个事件,然后在事件处理程序中检查触发事件的元素,例如: if (MenuItemSender.Header == "Load all") { LoadAll(); }。不过,在此情况下创建事件的做法并不值得推荐,因为已经有ClickMouseLeftButtonDown事件可以使用了。 - Anatoliy Nikolaev
我需要在用户控件上实现一个“单击事件(Click event)”。 - Caspar Kleijne
很抱歉要指出这个问题,但事件必须针对控件,例如按钮、菜单等。UserControl将所有这些元素组合在一起,不应该与它们绑定,因为今天你需要一个菜单项/按钮,那么在UserControl中需要实现其他使用其他事件的元素。特别是,有些人(包括我)更喜欢使用DataTemplate,如果使用DataTemplate代替UserControl会怎样呢?这不涉及任何事件。希望UserControl不依赖于其中的元素。 - Anatoliy Nikolaev
4个回答

3
除了已经提到的内容,我建议您考虑一个简单的消息解决方案。
每次您点击任何菜单项时,您都可以广播一条类型为MenuItemClickedMessage的消息。
public class MenuItemClickedMessage
{
   public MenuItem MenuItemClicked { get; set; }
}

您可以创建一个组件来监听该事件。一旦它接收到事件,您就可以从MenuItemClicked属性中提取菜单项并做出相应的反应。
这将需要对简单消息传递解决方案进行一些研究:http://rachel53461.wordpress.com/2011/05/28/switching-between-viewsusercontrols-using-mvvm/

2
与直觉相反,你收到的错误不是由于多个菜单项,而是由于已存在的Click事件引起的。该事件注册是静态的,并且已经被WPF Framework使用。
如何解决呢?
可以为你的菜单创建一个样式,在WPF中,样式可以包含EventSetters。这使您可以为容器中的任何菜单项注册单击处理程序。
    <Grid>
        <Grid.Resources>
            <Style TargetType="MenuItem">
                <EventSetter Event="Click" Handler="OnMenuItemClick" />
            </Style>
        </Grid.Resources>
        <Menu x:Name="mainMenu">
           ...
        </Menu>
    </Grid>

在您的处理程序中,请确保将Handled属性设置为true,以停止冒泡:

 private void OnMenuItemClick(object sender, RoutedEventArgs e)
 {
     e.Handled = true;
 }

更新 考虑不要在此处触发UserControl的“Click”事件,因为这可能是一个设计问题:当某人使用您的UserControl并在用户控件上触发“Click”事件时,您期望得到一个“Click”,而不是在控件内部菜单项上点击。

考虑改用自定义事件名称,例如MenuItemClick,其中可以在事件参数中提供菜单项的标识符。


这不会将事件暴露在用户控件之外。并且与菜单控件中的属性MenuItem.Click="MenuItem_Click"的工作方式相同。 - Caspar Kleijne
Bas:感谢您的回答,但我的问题是如何从菜单项中冒泡事件,而不是如何触发它们。 - Caspar Kleijne

2
尽管我强烈建议不这样做,而是像Bas所指出的那样声明一个独特的新事件,但您可以简单地重新使用类MenuItem中现有的RoutedEvent:
public partial class YourUserControl : UserControl
{
    ...

    public event RoutedEventHandler Click
    {
        add { AddHandler(MenuItem.ClickEvent, value); }
        remove { RemoveHandler(MenuItem.ClickEvent, value); }
    }
}

+1 这确实是被要求的正确做法,但我也建议不要这样做。尝试在用户控件中创建一个按钮并触发单击事件;它不会冒泡。 - Bas

2

MenuItemClick - 是一个路由事件,使用RoutedEventArgs.OriginalSource而不是sender。这指向最初触发事件的控件。

<Grid>
        <Menu x:Name="mainMenu">
            <MenuItem  Header="File" Click="OnFileMenuItemClicked">
                <MenuItem Header="Open Analysis">
                    <MenuItem Header="Load all" />
                    <MenuItem Header="Select from tracks" />
                </MenuItem>
            </MenuItem>
        </Menu>
</Grid>


private void OnFileMenuItemClicked(object sender, RoutedEventArgs e)
{
    MenuItem item = e.OriginalSource as MenuItem;
    if(null != item)
    {
        // Handle the menuItem click here
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接