如何在WPF上下文菜单项单击事件处理程序中引用右键单击的对象?

时间:2010-01-09 08:37:53

标签: c# wpf event-handling contextmenu

在WPF应用程序中,有一个Grid,其中包含许多对象(它们来自自定义控件)。我想使用上下文菜单对每个操作执行一些操作:

   <Grid.ContextMenu>
     <ContextMenu>
       <MenuItem  Name="EditStatusCm" Header="Change status" Click="EditStatusCm_Click"/>
     </ContextMenu>                   
   </Grid.ContextMenu> 

但是在事件处理程序中,我无法知道右键单击了哪些对象:

    private void EditStatusCm_Click(object sender, RoutedEventArgs e)
    {
        MyCustControl SCurrent = new MyCustControl();
        MenuItem menu = sender as MenuItem;
        SCurrent = menu.DataContext as MyCustControl; // here I get a run-time error
        SCurrent.Status = MyCustControl.Status.Sixth;
    }

在该注释行上调试器说: 对象引用未设置为对象的实例。

请帮忙,我的代码有什么问题?

已编辑(已添加):

我尝试使用命令方法执行相同操作:

我使用DataCommands宣布了RoutedUICommand Requery班级,然后使用了Window.CommandBindings

<Window.CommandBindings>
  <CommandBinding Command="MyNamespace:DataCommands.Requery" Executed="RequeryCommand_Executed"></CommandBinding>
</Window.CommandBindings>

MenuItem的XAML现在看起来像:

<Grid.ContextMenu>
 <ContextMenu>
  <MenuItem  Name="EditStatusCm" Header="Change status"  Command="MyNamespace:DataCommands.Requery"/>
 </ContextMenu>                   
</Grid.ContextMenu>

事件处理程序如下所示:

    private void RequeryCommand_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        IInputElement parent = (IInputElement)LogicalTreeHelper.GetParent((DependencyObject)sender);
        MyCustControl SCurrent = new MyCustControl();
        SCurrent = (MuCustControl)parent;
        string str = SCurrent.Name.ToString();// here I get the same error
        MessageBox.Show(str);
    }

但调试器显示相同的运行时错误: 对象引用未设置为对象的实例。

我的两种方法都缺少什么?

如何在WPF上下文菜单项单击事件处理程序中引用右键单击的对象?

8 个答案:

答案 0 :(得分:23)

请注意CommandParameter

<Grid Background="Red" Height="100" Width="100">
    <Grid.ContextMenu>
        <ContextMenu>
            <MenuItem 
                Header="Change status" 
                Click="EditStatusCm_Click"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
        </ContextMenu>
    </Grid.ContextMenu>
</Grid>

并在处理程序中使用它来确定它是哪个Grid

    private void EditStatusCm_Click(object sender, RoutedEventArgs e)
    {
        MenuItem mi = sender as MenuItem;
        if (mi != null)
        {
            ContextMenu cm = mi.CommandParameter as ContextMenu;
            if (cm != null)
            {
                Grid g = cm.PlacementTarget as Grid;
                if (g != null)
                {
                    Console.WriteLine(g.Background); // Will print red
                }
            }
        }
    }

更新
如果您希望menuitem处理程序转到Grid的子节点而不是Grid本身,请使用此方法

<Grid Background="Red" Height="100" Width="100">
    <Grid.Resources>
        <ContextMenu x:Key="TextBlockContextMenu">
            <MenuItem 
                Header="Change status" 
                Click="EditStatusCm_Click"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
        </ContextMenu>

        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="ContextMenu" Value="{StaticResource TextBlockContextMenu}" />
        </Style>
    </Grid.Resources>

    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>

    <TextBlock Text="Row0" Grid.Row="0" />
    <TextBlock Text="Row1" Grid.Row="1" />
</Grid>

只需将TextBlocks替换为您的自定义对象类型即可。然后在事件处理程序中,将Grid g = cm.PlacementTarget as Grid替换为TextBlock t = cm.PlacementTarget as TextBlock(或任何自定义对象类型)。

答案 1 :(得分:5)

通过在xaml中绑定数据上下文:

ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource=    {RelativeSource Self}}">

然后你可以这样做:

private void Context_MenuClick(object sender, RoutedEventArgs e)
{
   var menuItem = e.Source as MenuItem;

   MyDoStuffFunction(menuItem.DataContext);
}

数据上下文将绑定到单击的对象以打开ContextMenu。

我从这个链接的代码项目文章中得到了它:

http://www.codeproject.com/Articles/162784/WPF-ContextMenu-Strikes-Again-DataContext-Not-Upda

答案 2 :(得分:2)

如果发件人不是MenuItem或其派生类,则

menu = sender as MenuItem将为null。随后尝试取消引用菜单会爆炸。

您的发件人可能是Menu或ContextMenu或ToolStripMenuItem或其他形式的菜单项,而不是具体的MenuItem对象。使用调试器断点在此处停止代码并检查发送方对象以确切地查看它是什么类。

答案 3 :(得分:2)

RoutedEventArgs

  • RoutedEventArgs.source 是引发事件的对象的引用
  • RoutedEventArgs.originalSource 是在由父类进行任何可能的源调整之前由纯匹配测试确​​定的报告源。

所以.Sender应该是答案。但这取决于菜单项的添加和绑定方式

请参阅此answer collection并选择适合您情况的方法!

答案 4 :(得分:1)

您不应该检查RoutedEventArgs.Source而不是sender吗?

答案 5 :(得分:1)

你有两个不同的问题。这两个问题都导致了同样的例外情况,但其他情况则无关:

第一个问题

在您的第一种方法中,您的代码是正确的并且运行良好,除了此处的问题:

SCurrent.Status = MyCustControl.Status.Sixth;

名称“Status”既可用作静态成员,也可用作实例成员。我认为您将代码错误地剪切并粘贴到您的问题中。

根据您的具体情况,可能还需要在MenuItem menu = sender as MenuItem;之后添加以下内容:

  if(menu==null) return;

第二个问题

在您的第二种方法中,您使用“发件人”而不是“e.Source”。以下代码按预期工作:

private void RequeryCommand_Executed(object sender, ExecutedRoutedEventArgs e)    
{    
    IInputElement parent = (IInputElement)LogicalTreeHelper.GetParent((DependencyObject)e.Source);
      // Changed "sender" to "e.Source" in the line above
    MyCustControl SCurrent = new MyCustControl();    
    SCurrent = (MuCustControl)parent;    
    string str = SCurrent.Name.ToString();// Error gone
    MessageBox.Show(str);    
}

最后的注释

注意:如果你使用指挥方法,根本没有理由绑定CommandParameter。它明显更慢并且需要更多代码。 e.Source始终是源对象,因此无需使用CommandParameter,因此请改用它。

答案 6 :(得分:0)

这对我有用: -

XAML: -

<DataGrid.ContextMenu>
<ContextMenu x:Name="AddColumnsContextMenu" MenuItem.Click="AddColumnsContextMenu_Click">
</ContextMenu>

添加菜单项: -

foreach (String s in columnNames)
{
var item = new MenuItem { IsCheckable = true, IsChecked = true ,Header=s};
AddColumnsContextMenu.Items.Add(item);
}

听众来了: -

private void AddColumnsContextMenu_Click(object sender, RoutedEventArgs e)
{
    MenuItem mi = e.Source as MenuItem;
    string title = mi.Header.ToString();
    MessageBox.Show("Selected"+title);
}

...谢谢

答案 7 :(得分:0)

在我的情况下,我能够使用:

private void MenuItem_Click(object sender, RoutedEventArgs e)
{    
    MenuItem menuItem        = e.Source as MenuItem;
    ContextMenu parent       = menuItem.Parent as ContextMenu;
    ListBoxItem selectedItem = parent.PlacementTarget as ListBoxItem;
}