web-dev-qa-db-ja.com

WPFコンテキストメニュー項目のクリックイベントハンドラーで右クリックされたオブジェクトを参照するにはどうすればよいですか?

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 RequeryDataCommandsクラスを宣言してから、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コンテキストメニュー項目のクリックイベントハンドラーで右クリックされたオブジェクトを参照するにはどうすればよいですか?

11
rem

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 GridTextBlock t = cm.PlacementTarget as TextBlock(またはカスタムオブジェクトタイプ)に置き換えます。

25
kenwarner

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

6
Tbonechk27

RoutedEventArgs の場合

  • RoutedEventArgs.sourceは、イベントを発生させたオブジェクトへの参照です
  • RoutedEventArgs.originalSourceは、親クラスによるソース調整の前に、純粋なヒットテストによって決定されたレポートソースです。

したがって、.Senderが答えになるはずです。ただし、これはメニュー項目の追加方法とバインド方法によって異なります

これを参照してください 回答コレクション そしてあなたの状況に適した方法を選択してください!

2
TFD

送信者がMenuItemまたはその派生クラスでない場合、menu = sender as MenuItemはnullになります。その後、メニューを逆参照しようとすると爆発します。

送信者は、具体的にMenuItemオブジェクトではなく、Menu、ContextMenu、ToolStripMenuItem、またはその他の形式のメニュー項目である可能性があります。デバッガブレークポイントを使用してここでコードを停止し、送信者オブジェクトを調べて、それがどのクラスであるかを正確に確認します。

2
Jason Williams

senderの代わりにRoutedEventArgs.Sourceをチェックするべきではありませんか?

1
Vijay Patel

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を使用する必要はないので、代わりにそれを使用してください。

1
Ray Burns

これは私のために働きます:-

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

ありがとう...

0
Abhishek

私の場合、私は使用することができました:

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