在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上下文菜单项单击事件处理程序中引用右键单击的对象?
答案 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)
menu = sender as MenuItem
将为null。随后尝试取消引用菜单会爆炸。
您的发件人可能是Menu或ContextMenu或ToolStripMenuItem或其他形式的菜单项,而不是具体的MenuItem对象。使用调试器断点在此处停止代码并检查发送方对象以确切地查看它是什么类。
答案 3 :(得分:2)
所以.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;
}