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

时间:2022-03-26 05:55:24

In WPF application there is a Grid with a number of objects (they are derived from a custom control). I want to perform some actions on each of them using context menu:

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

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

But in the event handler I cannot get know which of the objects was right-clicked:

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

    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;
    }

On that commented line Debugger says: Object reference not set to an instance of an object.

在注释行调试器中说:对象引用没有设置为对象的实例。

Please help, what is wrong in my code?

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

Edited (added):

编辑(添加):

I tried to do the same, using Command approach:

我试着用命令的方法做同样的事情:

I declared a DataCommands Class with RoutedUICommand Requery and then used Window.CommandBindings

我使用RoutedUICommand请求声明了一个DataCommands类,然后使用windows . commandbindings

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

XAML of MenuItem now looks like:

MenuItem的XAML现在是:

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

And event handler looks like:

事件处理程序如下:

    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);
    }

But debugger shows the same run-time error: Object reference not set to an instance of an object.

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

What is missing in my both approaches?

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

How I should reference right-clicked object in WPF Context Menu item click event handler?

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

8 个解决方案

#1


22  

note the CommandParameter

注意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>

and use it in the handler to figure out which Grid it is

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

    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
                }
            }
        }
    }

Update:
If you want the menuitem handler to get to the Grid's children instead of the Grid itself, use this approach

更新:如果您希望menuitem处理程序能够访问网格的子节点,而不是网格本身,请使用此方法。

<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>

Just replace the TextBlocks with whatever your custom object type is. Then in the event handler, replace Grid g = cm.PlacementTarget as Grid with TextBlock t = cm.PlacementTarget as TextBlock (or whatever your custom object type is).

只需用任何自定义对象类型替换文本块。然后在事件处理程序中,替换网格g = cm。以文本块t = cm的网格划分目标。PlacementTarget作为TextBlock(或任何自定义对象类型)。

#2


5  

By binding the Data Context like so in the xaml:

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

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

You can then do this:

你可以这样做:

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

   MyDoStuffFunction(menuItem.DataContext);
}

The data context will be bound to the object that was clicked to open the ContextMenu.

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

I got it from a codeproject article at this link:

我是从这个链接上的一篇codeproject文章中得到的:

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

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

#3


2  

menu = sender as MenuItem will be null if the sender is not a MenuItem or a derived class thereof. Subsequently trying to dereference menu will blow up.

如果发送方不是MenuItem或其派生类,那么作为MenuItem的发送方将为null。随后尝试取消引用菜单将会崩溃。

It's likely that your sender is a Menu or ContextMenu or a ToolStripMenuItem or some other form of menu item, rather than specifically being a MenuItem object. Use a debugger breakpoint to stop the code here and examine the sender object to see exactly what class it is.

很可能您的发件人是一个菜单或ContextMenu或ToolStripMenuItem或其他形式的菜单项,而不是特定的MenuItem对象。使用调试器断点来停止这里的代码,并检查sender对象,以确定它是什么类。

#4


2  

For RoutedEventArgs

对于RoutedEventArgs

  • RoutedEventArgs.source is the reference to the object that raised the event
  • RoutedEventArgs。源是指向引发事件的对象的引用。
  • RoutedEventArgs.originalSource is the reporting source as determined by pure hit testing, before any possible Source adjustment by a parent class.
  • RoutedEventArgs。原始源是由纯命中测试确定的报告源,然后由父类进行任何可能的源调整。

So .Sender should be the answer. But this depends on how the menuitems are added and bound

所以。sender就是答案。但这取决于如何添加和绑定菜单项

See this answer collection and choose the method that will work for you situation!

查看此答案集合并选择适合您的方法!

#5


1  

Shouldn't you be checking RoutedEventArgs.Source instead of sender?

你不应该检查轮盘车吗?源而不是发送者?

#6


1  

You had two different problems. Both problems resulted in the same exception, but were otherwise unrelated:

你有两个不同的问题。这两个问题导致相同的例外,但在其他方面是不相关的:

First problem

第一个问题

In your first approach your code was correct and ran well except for the problem here:

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

SCurrent.Status = MyCustControl.Status.Sixth;

The name "Status" is used both as a static member and as an instance member. I think you cut-and-pasted the code incorrectly into your question.

名称“Status”用作静态成员和实例成员。我认为你在你的问题中插入了错误的代码。

It may also be necessary to add the following after MenuItem menu = sender as MenuItem;, depending on your exact situation:

可能还需要在MenuItem菜单= sender后添加以下菜单作为MenuItem;这取决于您的具体情况:

  if(menu==null) return;

Second problem

第二个问题

In your second approach you used "sender" instead of "e.Source". The following code works as desired:

在第二种方法中,你使用了“sender”而不是“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);    
}

Final Note

最后请注意

Note: There is no reason at all to bind CommandParameter for this if you use the commanding approach. It is significantly slower and takes more code. e.Source will always be the source object so there is no need to use CommandParameter, so use that instead.

注意:如果您使用命令方法,则完全没有理由绑定CommandParameter。它非常慢,需要更多的代码。e。源对象始终是源对象,因此不需要使用CommandParameter,因此可以使用它。

#7


0  

This works for me:-

这适合我:-

XAML:-

XAML:-

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

For adding menu items:-

添加菜单项:-

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

And here comes the listener:-

听众来了:-

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

Thanks...

谢谢……

#8


0  

In my case I was able to use:

在我的案例中,我可以使用:

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;
}

#1


22  

note the CommandParameter

注意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>

and use it in the handler to figure out which Grid it is

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

    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
                }
            }
        }
    }

Update:
If you want the menuitem handler to get to the Grid's children instead of the Grid itself, use this approach

更新:如果您希望menuitem处理程序能够访问网格的子节点,而不是网格本身,请使用此方法。

<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>

Just replace the TextBlocks with whatever your custom object type is. Then in the event handler, replace Grid g = cm.PlacementTarget as Grid with TextBlock t = cm.PlacementTarget as TextBlock (or whatever your custom object type is).

只需用任何自定义对象类型替换文本块。然后在事件处理程序中,替换网格g = cm。以文本块t = cm的网格划分目标。PlacementTarget作为TextBlock(或任何自定义对象类型)。

#2


5  

By binding the Data Context like so in the xaml:

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

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

You can then do this:

你可以这样做:

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

   MyDoStuffFunction(menuItem.DataContext);
}

The data context will be bound to the object that was clicked to open the ContextMenu.

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

I got it from a codeproject article at this link:

我是从这个链接上的一篇codeproject文章中得到的:

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

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

#3


2  

menu = sender as MenuItem will be null if the sender is not a MenuItem or a derived class thereof. Subsequently trying to dereference menu will blow up.

如果发送方不是MenuItem或其派生类,那么作为MenuItem的发送方将为null。随后尝试取消引用菜单将会崩溃。

It's likely that your sender is a Menu or ContextMenu or a ToolStripMenuItem or some other form of menu item, rather than specifically being a MenuItem object. Use a debugger breakpoint to stop the code here and examine the sender object to see exactly what class it is.

很可能您的发件人是一个菜单或ContextMenu或ToolStripMenuItem或其他形式的菜单项,而不是特定的MenuItem对象。使用调试器断点来停止这里的代码,并检查sender对象,以确定它是什么类。

#4


2  

For RoutedEventArgs

对于RoutedEventArgs

  • RoutedEventArgs.source is the reference to the object that raised the event
  • RoutedEventArgs。源是指向引发事件的对象的引用。
  • RoutedEventArgs.originalSource is the reporting source as determined by pure hit testing, before any possible Source adjustment by a parent class.
  • RoutedEventArgs。原始源是由纯命中测试确定的报告源,然后由父类进行任何可能的源调整。

So .Sender should be the answer. But this depends on how the menuitems are added and bound

所以。sender就是答案。但这取决于如何添加和绑定菜单项

See this answer collection and choose the method that will work for you situation!

查看此答案集合并选择适合您的方法!

#5


1  

Shouldn't you be checking RoutedEventArgs.Source instead of sender?

你不应该检查轮盘车吗?源而不是发送者?

#6


1  

You had two different problems. Both problems resulted in the same exception, but were otherwise unrelated:

你有两个不同的问题。这两个问题导致相同的例外,但在其他方面是不相关的:

First problem

第一个问题

In your first approach your code was correct and ran well except for the problem here:

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

SCurrent.Status = MyCustControl.Status.Sixth;

The name "Status" is used both as a static member and as an instance member. I think you cut-and-pasted the code incorrectly into your question.

名称“Status”用作静态成员和实例成员。我认为你在你的问题中插入了错误的代码。

It may also be necessary to add the following after MenuItem menu = sender as MenuItem;, depending on your exact situation:

可能还需要在MenuItem菜单= sender后添加以下菜单作为MenuItem;这取决于您的具体情况:

  if(menu==null) return;

Second problem

第二个问题

In your second approach you used "sender" instead of "e.Source". The following code works as desired:

在第二种方法中,你使用了“sender”而不是“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);    
}

Final Note

最后请注意

Note: There is no reason at all to bind CommandParameter for this if you use the commanding approach. It is significantly slower and takes more code. e.Source will always be the source object so there is no need to use CommandParameter, so use that instead.

注意:如果您使用命令方法,则完全没有理由绑定CommandParameter。它非常慢,需要更多的代码。e。源对象始终是源对象,因此不需要使用CommandParameter,因此可以使用它。

#7


0  

This works for me:-

这适合我:-

XAML:-

XAML:-

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

For adding menu items:-

添加菜单项:-

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

And here comes the listener:-

听众来了:-

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

Thanks...

谢谢……

#8


0  

In my case I was able to use:

在我的案例中,我可以使用:

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;
}