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