午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPF、右クリックメニューの表示、非表示切り替えはnullとIsOpen = trueでできた

ContextMenuOpeningイベントのときにnull指定で非表示にできるけど、また表示したくなったときにContextMenuを指定しても一回のクリックでは表示されない!そんなときは、IsOpen = trueで強制表示

これじゃない結果

マウスカーソルに赤丸表示が右クリックした印

これじゃない動作
期待する動作はRect1か2を右クリック時は右クリックメニューを開いて、それ以外の場所の右クリックでは開かない、なんだけどなんか違う
Rect以外を右クリックしたあとにRectを右クリックしても1回のクリックではメニューが開かなくて2回クリックすると開く動作になっている


結果1

結果1
これが求めていた動作、Rect以外のクリック後でも1回の右クリックでメニューが開いている


結果2

結果2
これは蛇足
Rect1と2を右クリックしたときは、それぞれ専用の右クリックメニューを開く
それ以外の場所を右クリックしたときは開かない


環境

コード

github.com



デザイナー画面
MainWindow.xaml

<Window x:Class="_20230408_ContextMemu.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:_20230408_ContextMemu"
        mc:Ignorable="d"
        Title="MainWindow" Height="150" Width="400">
  <Grid>
    <UniformGrid Rows="1" Margin="70,30">
      <DockPanel>
        <TextBlock Text="Rect1" DockPanel.Dock="Top"/>
        <Rectangle x:Name="MyRect1" Fill="BlueViolet"/>
      </DockPanel>
      <DockPanel>
        <TextBlock Text="Rect2" DockPanel.Dock="Top"/>
        <Rectangle x:Name="MyRect2" Fill="Gold"/>
      </DockPanel>
      <DockPanel>
        <TextBlock Text="その他要素" DockPanel.Dock="Top"/>
        <Rectangle Fill="Gray"/>
      </DockPanel>
    </UniformGrid>
  </Grid>
</Window>




MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;

//方法: ContextMenuOpening イベントを処理する - WPF .NET Framework | Microsoft Learn
//https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/advanced/how-to-handle-the-contextmenuopening-event


//右クリックメニュー(ContextMenu)の表示、非表示テスト
namespace _20230408_ContextMemu
{
    public partial class MainWindow : Window
    {
        private ContextMenu MyContextMenu1;
        private ContextMenu MyContextMenu2;
        public MainWindow()
        {
            InitializeComponent();

            Top = 300;
            Left = 300;
            MyContextMenu1 = new();            
            MyContextMenu1.Items.Add(new MenuItem() { Header = "ランボー怒りの" });
            MyContextMenu1.Items.Add(new MenuItem() { Header = "右クリック" });

            MyContextMenu2 = new();
            MyContextMenu2.Items.Add(new MenuItem() { Header = "Rect2専用" });
            MyContextMenu2.Items.Add(new MenuItem() { Header = "暮らし安心" });
            MyContextMenu2.Items.Add(new MenuItem() { Header = "ガウシアン" });

            //右クリックメニュー開くときの動作
            //これじゃない
            //ContextMenuOpening += MainWindow_ContextMenuOpening;

            //結果1
            ContextMenuOpening += MainWindow_ContextMenuOpening1;

            //結果2
            //ContextMenuOpening += MainWindow_ContextMenuOpening2;
        }

        //MyRectの1か2以外を右クリックした直後だと
        //MyRectの1か2を右クリックしてもメニューが表示されない
        //もう一度右クリックで表示される
        private void MainWindow_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            if (e.Source == MyRect1 || e.Source == MyRect2)
            {
                ContextMenu = MyContextMenu1;
            }
            else
            {
                ContextMenu = null;
            }
        }

        //これなら一回で表示される
        private void MainWindow_ContextMenuOpening1(object sender, ContextMenuEventArgs e)
        {
            if (e.Source == MyRect1 || e.Source == MyRect2)
            {
                //nullだった場合は表示したいメニューを指定後に強制表示
                if (ContextMenu == null)
                {
                    ContextMenu = MyContextMenu1;
                    ContextMenu.IsOpen = true;
                }
            }
            else ContextMenu = null;
        }

        //MainWindow_ContextMenuOpening1を改変
        //MyRect1か2なら、それぞれの右クリックメニューを表示する、それ以外は表示しない
        private void MainWindow_ContextMenuOpening2(object sender, ContextMenuEventArgs e)
        {
            bool flag = false;
            if (ContextMenu == null) { flag = true; }
            if (e.Source == MyRect1)
            {
                ContextMenu = MyContextMenu1;
                if (flag) { ContextMenu.IsOpen = true; }
            }
            else if (e.Source == MyRect2)
            {
                ContextMenu = MyContextMenu2;
                if (flag) { ContextMenu.IsOpen = true; }
            }
            else
            {
                ContextMenu = null;
            }
        }


    }
}




あらかじめ右クリックメニューを用意しておいて、右クリックメニューを開くタイミングの、ContextMenuOpeningイベント時にメニューを入れ替える

これじゃない動作と結果1と2の切り替えは手動

コメントアウトで切り替え

これじゃない動作と結果1の違い
結果1はnullチェックして、ContextMenu.IsOpen = true;


参照したところ

learn.microsoft.com
読んでもわからんけど、nullチェックとContextMenu.IsOpen = true;が関係あるっぽいのがわかった
なので、今回のもあっているかわからん、こう書いたら期待する動作になっただけ


感想

今回のテストのように特定のRectangleだけに表示したいだけなら、そのRectangle自体のContextMenuに付ければいいけどねえ
そうじゃないときもあって

範囲指定Thumbの右クリックメニュー

Pixtack3紫陽花
これは範囲指定ThumbのContextMenuに付けている

それ以外のThumbの右クリックメニュー

Pixtack3紫陽花

これも最初はThumb自体に付けようとしてたけど、ファイル保存のメソッドはMainWindowに書いたから、MainWindowのContextMenuに付けたほうが自然かなあとか
そもそもMainWindowにファイル保存のメソッドを書かないほうがいいのかとか、こうアプリの設計みたいなのは難しいねえ、どこに何を置くとかなんの機能をもたせるのかどんなクラスを作るのかとか


関連記事

次回は1週間後
gogowaten.hatenablog.com

前回のWPF記事は1ヶ月前
gogowaten.hatenablog.com