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

感情迁移 提交于 2019-11-28 19:47:59

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

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

By binding the Data Context like so in the 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.

I got it from a codeproject article at this link:

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

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.

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.

For RoutedEventArgs

  • RoutedEventArgs.source is the reference to the object that raised the event
  • RoutedEventArgs.originalSource is the reporting source as determined by pure hit testing, before any possible Source adjustment by a parent class.

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

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

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

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.

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

  if(menu==null) return;

Second problem

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

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.

This works for me:-

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

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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!