質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.44%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

1回答

1132閲覧

[WPF][C#] ContextMenuのMenuItemを階層化したときに親階層のItemのクリックイベントを実行したい

goron1122

総合スコア1

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

1グッド

0クリップ

投稿2023/02/24 05:02

実現したいこと

TextBox内で選択したテキストに対してContextMenuを用いてテキストを変更させる機能を実装していますが、ContextMenuのMenuItemを階層化して親Itemでも子Itemでも別々の変更を適用させたいです。

前提

VisualStudio2017でWPFを用いてツールを作成していますが、TextBoxのテキストを編集する際にContextMenuを使って選択テキストに修飾させるサポート機能を追加しようとしています。その修飾機能はデフォルトの修飾とそれにオプションを付加した修飾があり、ContextMenuのMenuItemを階層化して親Itemはdefault動作を、子Itemはoption動作を割り当てたく思っています。

発生している問題

期待動作としては、ContextMenuの親Itemをクリックしたら親Item用のイベントのみ実行し、子Itemを実行したら子Item用のイベントのみを実行したいのですが、下記のソースコードで実行しても親Itemでは右クリックすることができず(クリックしても無反応)、子Itemを右クリックすると子Item用イベントを実行した後に親Item用イベントを連続して実行してしまう状態になっています。

問題を簡略化したサンプルコード

VisualStudio 2017のWPFアプリ(.NET Frameowk)のウィザードで生成されたコードをベースに課題部分を付加して実行できる形で添付します

xaml

1<Window x:Class="WpfApp1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:WpfApp1" 7 mc:Ignorable="d" 8 Title="MainWindow" Height="450" Width="800"> 9 <Grid> 10 <TextBox x:Name="textbox1" HorizontalAlignment="Left" Height="23" Margin="28,23,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"> 11 <TextBox.ContextMenu> 12 <ContextMenu> 13 <MenuItem Header="Add brackets" Click="Add_Quot"> 14 <MenuItem x:Name="menu_c1" Header="Parentheses" Click="Add_Brackets"/> 15 <MenuItem x:Name="menu_c2" Header="Braces" Click="Add_Brackets"/> 16 <MenuItem x:Name="menu_c3" Header="Brackets" Click="Add_Brackets"/> 17 </MenuItem> 18 </ContextMenu> 19 </TextBox.ContextMenu> 20 </TextBox> 21 </Grid> 22</Window>

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Text; 5using System.Threading.Tasks; 6using System.Windows; 7using System.Windows.Controls; 8using System.Windows.Data; 9using System.Windows.Documents; 10using System.Windows.Input; 11using System.Windows.Media; 12using System.Windows.Media.Imaging; 13using System.Windows.Navigation; 14using System.Windows.Shapes; 15 16namespace WpfApp1 17{ 18 /// <summary> 19 /// MainWindow.xaml の相互作用ロジック 20 /// </summary> 21 public partial class MainWindow : Window 22 { 23 public MainWindow() 24 { 25 InitializeComponent(); 26 } 27 private void Add_Quot(object sender, RoutedEventArgs e) 28 { 29 int selectedPosition = textbox1.SelectionStart; 30 int selectedEndPosition = selectedPosition + textbox1.SelectedText.Length; 31 textbox1.Text = textbox1.Text.Insert(selectedEndPosition, "\"").Insert(selectedPosition, "\""); 32 } 33 private void Add_Brackets(object sender, RoutedEventArgs e) 34 { 35 int selectedPosition = textbox1.SelectionStart; 36 int selectedEndPosition = selectedPosition + textbox1.SelectedText.Length; 37 string f = (((MenuItem)sender).Name == "menu_c1") ? "(" : (((MenuItem)sender).Name == "menu_c2") ? "{" : (((MenuItem)sender).Name == "menu_c3") ? "[" : ""; 38 string b = (((MenuItem)sender).Name == "menu_c1") ? ")" : (((MenuItem)sender).Name == "menu_c2") ? "}" : (((MenuItem)sender).Name == "menu_c3") ? "]" : ""; 39 textbox1.Text = textbox1.Text.Insert(selectedEndPosition, b).Insert(selectedPosition, f); 40 } 41 } 42}

補足情報(FW/ツールのバージョンなど)

Visual Studio 2017(Version 15.9.49)
.NET Framework Version 4.8.04084

TN8001👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

ContextMenuのMenuItemを階層化して親Itemはdefault動作を、子Itemはoption動作を割り当てたく思っています。

Windowsのコンテキストメニュー(メニューでも)で、そのような動作になるものは見たことがありません。
標準動作と違いすぎて、一般ユーザーはAdd_Quotできることに気が付かないでしょう。

普通はこうですが、これではダメなのでしょうか?

xml

1<ContextMenu> 2 <MenuItem Click="Add_Quot" Header="Add quotations" /> 3 <MenuItem Header="Add brackets"> 4 <MenuItem Click="Add_Brackets" Header="Parentheses" /> 5 <MenuItem Click="Add_Brackets" Header="Braces" /> 6 <MenuItem Click="Add_Brackets" Header="Brackets" /> 7 </MenuItem> 8</ContextMenu>

「自分だけしか使わないので一般的でなくても実現したい」というのであれば、ClickではなくPreviewMouseLeftButtonUpかなんかにすればできましたが...


要はVisual Studioの[▶️開始]ボタンみたいなことですよね?(ボタンとしても動くし子メニューもある)

UWP(WinUI)にはSplitButton(ボタンとコンボボックスが合わさったようなの)があってイメージは近いです(同じではなく既定の動作を変更できるボタンって感じ)
ボタン - Windows apps | Microsoft Learn

MahApps.Metroにも同様のものがあります。
MahApps.Metro - SplitButton

SplitButtonPopupで出せば、やりたいことと一般的な動作のバランスがいいかもしれません。

xml

1<Window 2 x:Class="Qhqiyay1jejj09u.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" 6 xmlns:sys="clr-namespace:System;assembly=mscorlib" 7 Width="800" 8 Height="450"> 9 <StackPanel> 10 11 <TextBox Margin="8" Text="オリジナル"> 12 <TextBox.ContextMenu> 13 <ContextMenu> 14 <MenuItem Click="Add_Quot" Header="Add brackets"> 15 <MenuItem Click="Add_Brackets" Header="Parentheses" /> 16 <MenuItem Click="Add_Brackets" Header="Braces" /> 17 <MenuItem Click="Add_Brackets" Header="Brackets" /> 18 </MenuItem> 19 </ContextMenu> 20 </TextBox.ContextMenu> 21 </TextBox> 22 23 <TextBox Margin="8" Text="一般的"> 24 <TextBox.ContextMenu> 25 <ContextMenu> 26 <MenuItem Click="Add_Quot" Header="Add quotations" /> 27 <MenuItem Header="Add brackets"> 28 <MenuItem Click="Add_Brackets" Header="Parentheses" /> 29 <MenuItem Click="Add_Brackets" Header="Braces" /> 30 <MenuItem Click="Add_Brackets" Header="Brackets" /> 31 </MenuItem> 32 </ContextMenu> 33 </TextBox.ContextMenu> 34 </TextBox> 35 36 <TextBox Margin="8" Text="むりくり"> 37 <TextBox.ContextMenu> 38 <ContextMenu> 39 <MenuItem Header="Add brackets" PreviewMouseLeftButtonUp="Add_Quot_PreviewMouseLeftButtonUp"> 40 <MenuItem Click="Add_Brackets" Header="Parentheses" /> 41 <MenuItem Click="Add_Brackets" Header="Braces" /> 42 <MenuItem Click="Add_Brackets" Header="Brackets" /> 43 </MenuItem> 44 </ContextMenu> 45 </TextBox.ContextMenu> 46 </TextBox> 47 48 <TextBox 49 Margin="8" 50 MouseRightButtonUp="TextBox_MouseRightButtonUp" 51 Text="SplitButton" /> 52 <Popup 53 Name="popup" 54 Placement="MousePoint" 55 StaysOpen="False"> 56 <mah:SplitButton 57 Click="SplitButton_Click" 58 SelectedIndex="0" 59 SelectionChanged="SplitButton_SelectionChanged"> 60 <mah:SplitButton.Items> 61 <sys:String>Add Quotations</sys:String> 62 <sys:String>Add Parentheses</sys:String> 63 <sys:String>Add Braces</sys:String> 64 <sys:String>Add Brackets</sys:String> 65 </mah:SplitButton.Items> 66 </mah:SplitButton> 67 </Popup> 68 </StackPanel> 69</Window>

cs

1using System.Windows; 2using System.Windows.Controls; 3using System.Windows.Input; 4using MahApps.Metro.Controls; 5 6namespace Qhqiyay1jejj09u 7{ 8 public partial class MainWindow : Window 9 { 10 public MainWindow() => InitializeComponent(); 11 12 private void Add_Quot(object sender, RoutedEventArgs e) 13 { 14 // x:Nameに依存しないようにしただけで深い意味はない 15 if (sender is MenuItem item && GetPlacementTarget(item) is TextBox textBox) 16 { 17 var start = textBox.SelectionStart; 18 var end = start + textBox.SelectedText.Length; 19 20 textBox.Text = textBox.Text.Insert(end, "\"").Insert(start, "\""); 21 } 22 } 23 24 private void Add_Brackets(object sender, RoutedEventArgs e) 25 { 26 if (sender is MenuItem item && GetPlacementTarget(item) is TextBox textBox) 27 { 28 var start = textBox.SelectionStart; 29 var end = start + textBox.SelectedText.Length; 30 31 // x:Nameに依存したくないのでHeaderで比較したが、 32 // 実際はsender == menu_c1で十分でしょう 33 var s = (string)item.Header; 34 var (f, b) = s == "Parentheses" ? ("(", ")") : 35 s == "Braces" ? ("{", "}") : 36 s == "Brackets" ? ("[", "]") : ("", ""); 37 38 textBox.Text = textBox.Text.Insert(end, b).Insert(start, f); 39 } 40 } 41 42 43 private void Add_Quot_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) 44 { 45 if (sender != e.Source) return; // 子メニューでも来るので親以外はスルー 46 47 if (sender is MenuItem item && GetPlacementTarget(item) is TextBox textBox) 48 { 49 var start = textBox.SelectionStart; 50 var end = start + textBox.SelectedText.Length; 51 52 textBox.Text = textBox.Text.Insert(end, "\"").Insert(start, "\""); 53 54 textBox.ContextMenu.IsOpen = false; // コンテキストメニューを閉じる 55 } 56 } 57 58 59 private void TextBox_MouseRightButtonUp(object sender, MouseButtonEventArgs e) 60 { 61 popup.PlacementTarget = (UIElement)sender; 62 popup.IsOpen = true; // ポップアップを開く 63 } 64 65 private void SplitButton_Click(object sender, RoutedEventArgs e) 66 { 67 if (sender is SplitButton splitButton && popup.PlacementTarget is TextBox textBox) 68 { 69 var start = textBox.SelectionStart; 70 var end = start + textBox.SelectedText.Length; 71 72 var s = (string)splitButton.SelectedValue; 73 var (f, b) = s == "Add Quotations" ? ("\"", "\"") : 74 s == "Add Parentheses" ? ("{", "}") : 75 s == "Add Braces" ? ("{", "}") : 76 s == "Add Brackets" ? ("[", "]") : ("", ""); 77 78 textBox.Text = textBox.Text.Insert(end, b).Insert(start, f); 79 80 popup.IsOpen = false; // ポップアップを閉じる 81 } 82 } 83 private void SplitButton_SelectionChanged(object sender, SelectionChangedEventArgs e) 84 => SplitButton_Click(sender, null); // 選択変更時実行まではしてくれないので雑に実行w 85 86 87 88 private static UIElement GetPlacementTarget(MenuItem menuItem) 89 { 90 while (menuItem != null) 91 { 92 if (menuItem.Parent is ContextMenu contextMenu) 93 return contextMenu.PlacementTarget; 94 menuItem = menuItem.Parent as MenuItem; 95 } 96 return null; 97 } 98 } 99}

NuGet Gallery | MahApps.Metro 2.4.9

アプリ画像

投稿2023/02/24 09:26

編集2023/02/24 09:36
TN8001

総合スコア9455

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

goron1122

2023/02/24 12:12

複数の解決策から作法としての常識的なことまで色々とお教えいただきありがとうございます! このツールは幾人かの身近な友人の間でのみ使用するもので、広く配布するものではありませんが、それでもWindowsの作法からあまり外れた用い方をしない方がよさそうですね。 ただ、上述したように一方はdefaultで他方は複数のoptionの選択というちょっとウェイトが異なるものの選択のため同列にはしたくないという希望があります(ここでは示していませんが親階層と同列の別機能(オプションなし)もあるので位置的にも)。 そのため、SplitButtonでのやり方を学ばせていただこうかと思います。 本当に助かりました。ありがとうございました。
TN8001

2023/02/24 12:55 編集

> 複数の解決策から作法としての常識的なことまで色々とお教えいただきありがとうございます! 👍 > このツールは幾人かの身近な友人の間でのみ使用するもので、広く配布するものではありません 全員に使い方を説明できる状況なら、「むりくり」でもアリかなって気はします。 > ただ、上述したように一方はdefaultで他方は複数のoptionの選択というちょっとウェイトが異なるものの選択のため同列にはしたくないという希望があります SplitButtonは「デフォルトを選択可能にする」タイプなので、(Add Quotationsも含め)それぞれ同列扱いです(念のため) > SplitButtonでのやり方を学ばせていただこうかと思います。 MahApps.Metroを試す場合は、NuGet以外に(回答では省略しましたが)App.xamlの編集も必要です。 [MahApps.Metro - Quick Start](https://mahapps.com/docs/guides/quick-start) > 親階層と同列の別機能(オプションなし)もある SplitButton案だと、PopupとMenuItemの両立は厄介かもしれません^^; PopupでなくContextMenu内にSplitButtonを入れることになりそうだが、なじむようにスタイルを合わすのが大変そう...
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.44%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問