62993


ListBoxとListBoxItemにそれぞれContextMenuを設定し、項目と項目外で別のコンテキストメニューを開くようにしています。

このとき、ListBoxItemのコンテキストメニューを開いたときにListBoxのContextMenuOpeningイベントも同時に呼ばれてしまいます。  
イベント内でどのContextMenuが開かれたのかを区別する方法はありますでしょうか。

### 再現コード

[code]

    <Window x:Class="ContextMenuTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:ContextMenuTest"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ListBox ItemsSource="{Binding Items}" ContextMenuOpening="ListBox_ContextMenuOpening">
    
            <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <EventSetter Event="ContextMenuOpening" Handler="ListBoxItem_ContextMenuOpening" />
                    <Setter Property="ContextMenu">
                        <Setter.Value>
                            <ContextMenu>
                                <MenuItem Header="ItemMenu" />
                            </ContextMenu>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
    
            <ListBox.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="ListBoxMenu" />
                </ContextMenu>
            </ListBox.ContextMenu>
    
        </ListBox>
    </Grid>
    </Window>
    
[/code]

[code]

    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace ContextMenuTest
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = this;
            }
    
            public string[] Items { get; } = new string[]
            {
                "AAA", "BBB", "CCC"
            };
    
            private void ListBox_ContextMenuOpening(object sender, ContextMenuEventArgs e)
            {
                // ここで区別したい
                Debug.WriteLine($"ListBox: ContextMenuOpening: {e.Source}");
            }
    
            private void ListBoxItem_ContextMenuOpening(object sender, ContextMenuEventArgs e)
            {
                Debug.WriteLine($"ListBoxItem: ContextMenuOpening: {e.Source}");
            }
        }  
    }
    
[/code]

  * Windows 10
  * .NET Framework 4.7.2

### 詳細

項目AAAを右クリックしてコンテキストメニューを開いたときのデバッグ出力

[code]

    ListBoxItem: ContextMenuOpening: System.Windows.Controls.ContentPresenter
    ListBox: ContextMenuOpening: System.Windows.Controls.ListBox Items.Count:3
    
[/code]

ListBoxの項目外(CCCの下の空白部分)を右クリックしてコンテキストメニューを開いたときのデバッグ出力

[code]

    ListBox: ContextMenuOpening: System.Windows.Controls.ListBox Items.Count:3
    
[/code]

このように項目のコンテキストメニューを開いた場合でもListBox_ContextMenuOpeningが呼ばれてしまい、かつe.Sourceが同じなのでどちらのコンテキストメニューを開いたときのイベントなのか区別できません。


CC BY-SA 4.0
2020-02-09T10:11:26.973
62903
2020-02-12T08:00:44.597
2020-02-11T06:05:52.740
14817
14817
question
c# .net wpf
ContextMenuOpening内で原因となったContextMenuを区別したい


以下の様な感じで`sender`を`ListBox`でキャストすると、情報が取れます。  
`SelectedIndex`は 0～2, `SelectedItem`と`SelectedValue`は同じ値で "AAA"/"BBB"/"CCC"
でした。

[code]

    private void ListBox_ContextMenuOpening(object sender, ContextMenuEventArgs e)
    {
        // ここで区別したい
        Debug.WriteLine($"ListBox: ContextMenuOpening: {e.Source}");
        Debug.WriteLine($"  SelectedIndex: {((ListBox)sender).SelectedIndex }");
        Debug.WriteLine($"  SelectedItem : {((ListBox)sender).SelectedItem }");
        Debug.WriteLine($"  SelectedValue: {((ListBox)sender).SelectedValue }");
    }
    
[/code]

* * *

> ListBoxの項目外(CCCの下の空白部分)を右クリックしてコンテキストメニューを開いたときのデバッグ出力
>
>
> このように項目のコンテキストメニューを開いた場合でもListBox_ContextMenuOpeningが呼ばれてしまい、かつe.Sourceが同じなのでどちらのコンテキストメニューを開いたときのイベントなのか区別できません。

この場合、何かメニューの処理が終わったタイミング等で、以下の様な選択状態を解除する処理を入れてみてはどうでしょう？  
例えばこんな感じでXAMLに`ContextMenuClosing`イベント処理を登録して、

[code]

    <ListBox ItemsSource="{Binding Items}"
             ContextMenuOpening="ListBox_ContextMenuOpening"
             ContextMenuClosing="ListBox_ContextMenuClosing">
    
[/code]

以下のように選択解除処理を行ったら、その次の項目外クリックで `SelectedIndex`が -1,
`SelectedItem`と`SelectedValue`が空文字列で通知されました。

[code]

    private void ListBox_ContextMenuClosing(object sender, ContextMenuEventArgs e)
    {
        ((ListBox)sender).SelectedIndex = -1;
        ((ListBox)sender).SelectedItem = "";
        ((ListBox)sender).SelectedValue = "";
    }
    
[/code]

他には中身を確認していませんが、こんな対処があるようです。  
[yoshikazuendo/ListBoxSelectUnSelectSample.xaml](https://gist.github.com/yoshikazuendo/0115ebaabf5868c0dc78)

> 【WPF】単一選択のListBoxで選択解除の機能をBehaviorで実装してみた。  
>  ※参考サイト  
>  [ListBox with single select and also unselect on
> click…?](https://stackoverflow.com/q/5139956/9014308)  
>  [WPF “ビヘイビア”
> なるもの](https://tocsworld.wordpress.com/2014/07/02/wpf-%E3%83%93%E3%83%98%E3%82%A4%E3%83%93%E3%82%A2-%E3%81%AA%E3%82%8B%E3%82%82%E3%81%AE/)

* * *

ちなみに以下は複数選択モードの場合の処理なので使えませんでした。

[Visual Studio / WPF > ListBoxの選択を解除する > 自作クラスのbfSelected
プロパティ=falseではListBoxのSelectedItemsが残ったまま](https://qiita.com/7of9/items/7b2847e8f0bbe8084b8a)

>
[code]

>     private void B_clearClick(object sender, RoutedEventArgs e)
>     {
>         var myList = (this.DataContext as MyDataList);
>         // 1. 以下ではListBoxの選択状態が残ったままになった
>         //foreach (var elem in myList)
>         //{
>         //    elem.bfSelected = false;
>         //}
>         //myList.UpdateSelectedItems();
>  
>         // 2. 以下ではListBoxの選択状態がクリアされる
>         listBox1.SelectedItems.Clear();
>         myList.UpdateSelectedItems();
>     }
>  
[/code]

>
> 上記において1の処理ではDataContextのmyList要素のbfSelectedはfalseになり、右側のListBoxの表示はクリアされた。  
>  一方で、左側のlistBox1のSelectedItemsは残ったままだった。  
>  (次回選択時に残っていたSelectedItemsも選択状態になった。)
>
> 2の処理ではlistBox1のSelectedItemsもクリアされた。  
>  listBox1を直接指定している点はいいのかどうか。Bindingだけで処理できる方が良いようには思う。


CC BY-SA 4.0
2020-02-11T02:54:47.030
62944
2020-02-11T08:15:32.583
2020-02-11T08:15:32.583
26370
26370
62903
answer


ListBoxItemのContextMenuOpeningから直接ContextMenuを開きつつe.Handled=trueでルーティングイベントを停止することでListBox側のイベントが呼ばれることを回避しました。

[code]

    private void ListBoxItem_ContextMenuOpening(object sender, ContextMenuEventArgs e)
    {
        Debug.WriteLine($"ListBoxItem: ContextMenuOpening: {e.Source}");
    
        if (sender is FrameworkElement element && element.ContextMenu != null)
        {
            element.ContextMenu.IsOpen = true;
            e.Handled = true;
        }
    }
    
[/code]


CC BY-SA 4.0
2020-02-12T08:00:44.597
62993
2020-02-12T08:00:44.597
14817
62903
answer