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;
}
そのコメント行で、デバッガーは次のように述べています。オブジェクト参照がオブジェクトのインスタンスに設定されていません。
助けてください、私のコードの何が問題になっていますか?
編集(追加):
Commandアプローチを使用して、同じことを試みました:
RoutedUICommand Requery
でDataCommands
クラスを宣言してから、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コンテキストメニュー項目のクリックイベントハンドラーで右クリックされたオブジェクトを参照するにはどうすればよいですか?
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>
ハンドラーでそれを使用して、それがどのグリッドであるかを把握します
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 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
(またはカスタムオブジェクトタイプ)に置き換えます。
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を開くためにクリックされたオブジェクトにバインドされます。
私はこのリンクのcodeprojectの記事からそれを手に入れました:
http://www.codeproject.com/Articles/162784/WPF-ContextMenu-Strikes-Again-DataContext-Not-Upda
RoutedEventArgs の場合
したがって、.Senderが答えになるはずです。ただし、これはメニュー項目の追加方法とバインド方法によって異なります
これを参照してください 回答コレクション そしてあなたの状況に適した方法を選択してください!
送信者がMenuItemまたはその派生クラスでない場合、menu = sender as MenuItem
はnullになります。その後、メニューを逆参照しようとすると爆発します。
送信者は、具体的にMenuItemオブジェクトではなく、Menu、ContextMenu、ToolStripMenuItem、またはその他の形式のメニュー項目である可能性があります。デバッガブレークポイントを使用してここでコードを停止し、送信者オブジェクトを調べて、それがどのクラスであるかを正確に確認します。
sender
の代わりにRoutedEventArgs.Source
をチェックするべきではありませんか?
2つの異なる問題がありました。どちらの問題でも同じ例外が発生しましたが、それ以外は無関係でした。
最初の問題
最初のアプローチでは、コードは正しく、ここでの問題を除いて正常に実行されました。
SCurrent.Status = MyCustControl.Status.Sixth;
「ステータス」という名前は、静的メンバーとインスタンスメンバーの両方として使用されます。コードを切り取って質問に誤って貼り付けたと思います。
正確な状況によっては、MenuItem menu = sender as MenuItem;
の後に以下を追加する必要がある場合もあります。
if(menu==null) return;
2番目の問題
2番目のアプローチでは、「e.Source」の代わりに「sender」を使用しました。次のコードは必要に応じて機能します。
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
を使用する必要はないので、代わりにそれを使用してください。
これは私のために働きます:-
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);
}
ありがとう...
私の場合、私は使用することができました:
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;
}