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

≯℡__Kan透↙ 提交于 2020-01-09 13:56:06

问题


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:

   <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

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

XAML of MenuItem now looks like:

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


回答1:


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




回答2:


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




回答3:


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.




回答4:


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!




回答5:


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




回答6:


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.




回答7:


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




回答8:


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


来源:https://stackoverflow.com/questions/2032832/how-to-reference-right-clicked-object-in-wpf-context-menu-item-click-event-handl

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