メインウィンドウWPF でコンテキストメニューを表示すること自体は簡単ですが、制御しようと思うと幅広い考え方をする必要があり分かりづらいので、まとめて整理しました。

テストプログラムは GitHub に上げてあります。MVVM ライブラリは Livet を使用しています。Visual Studio 2022 + .NET 6 にて動作確認しています。


シンプルなコンテキストメニュー

素の状態のサンプルとして、ラベルにコンテキストメニューを付けています。ContextMenu タグを付けるだけの簡単なお仕事です。

<Label Content="シンプルなコンテキストメニュー:右クリックでオープンする(左クリックではオープンしない)" >
<Label.ContextMenu>
    <ContextMenu>
        <MenuItem Header="シンプルラベルメニュー 1" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="シンプルラベルメニュー 1" />     
(省略...)
        </MenuItem>
    </ContextMenu>
</Label.ContextMenu>
</Label>

ラベル右クリックラベルを右クリックするとコンテキストメニューが表示されます。

テストプログラムでは、コンテキストメニューを捕捉したことが分かるようにするため、コンテキストメニューをクリックするとステータスバーにメッセージを表示します。

左クリックでもオープンするコンテキストメニュー

通常、コンテキストメニューは右クリックで表示されますが、EventTrigger でイベントを捕捉することにより、ボタン左クリックでもコンテキストメニューを表示することができます。

<Button Name="ButtonLeftSample" Content="左クリックでもオープンするコンテキストメニュー(コントロールのイベントを捕捉してオープンする)" Margin="0,10,0,0" >
<behaviors:Interaction.Triggers>
    <behaviors:EventTrigger EventName="Click">
        <behaviors:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=ButtonLeftSample}" PropertyName="IsOpen" Value="True"/>
        <behaviors:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=ButtonLeftSample}" PropertyName="PlacementTarget" Value="{Binding ElementName=ButtonLeftSample, Mode=OneWay}"/>
    </behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
<Button.ContextMenu>
    <ContextMenu >
        <MenuItem Header="左クリックメニュー 1" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="左クリックメニュー 1" />
        <MenuItem Header="左クリックメニュー 2" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="左クリックメニュー 2" />
    </ContextMenu>
</Button.ContextMenu>
</Button>

ラベルで同様のことをやろうと思った場合、ラベルには Click イベントが無いので、代わりに MouseLeftButtonUp イベントを捕捉するとそれっぽくなるのではないでしょうか(ラベル以外でマウスボタンを押して、ドラッグでラベルまで移動してきて、そこでマウスボタンを離した場合もコンテキストメニューが表示されてしまいますが……)。

テキストボックスデフォルトのコンテキストメニューを非表示にする

テキストボックスには、デフォルトでコンテキストメニュー(コピー、貼り付け等)が付いてきます。

このコンテキストメニューを表示したくない場合は、コンテキストメニューを null にします。

<TextBox Width="100" Text="非表示" VerticalAlignment="Center" ContextMenu="{x:Null}" />

リストボックスのアイテムのみ右クリックでオープンするコンテキストメニュー

ListBoxリストボックスにコンテキストメニューを付けると、リストボックス内のどこをクリックしてもコンテキストメニューが表示されます。

リストボックスのアイテムがある部分を右クリックした場合のみコンテキストメニューを表示したい場合は、ItemContainerStyle で ListBoxItem に対してコンテキストメニューを付けます。

この時、DataContext を指定しないとコマンドを捕捉できません。これは、コンテキストメニューの VisualTree が変な感じになっているからのようです。WPF の嫌なところです……。

<ListBox ItemsSource="{Binding NarrowListBoxItems}" Width="290" >
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem" >
            <Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}"/>
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
                        <MenuItem Header="アイテムのみ ListBox メニュー 1" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="アイテムのみ ListBox メニュー 1" />
                        <MenuItem Header="アイテムのみ ListBox メニュー 2" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="アイテムのみ ListBox メニュー 2" />
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>
DataGrid についても同様のやり方で、アイテムがある部分のみでのコンテキストメニュー表示ができます。

DataGrid の場合、TargetType を DataGridRow にします。

動的に状態を制御するコンテキストメニュー

コンテキストメニューのヘッダー(ラベル)等を動的に制御したい場合は、ContextMenuOpening イベントを捕捉すると簡単です。ContextMenuOpening イベントハンドラー内で、ヘッダーや IsEnabled などをいじることで、コンテキストメニューの状態を動的に制御できます。

Visibility をいじることで、ある程度の項目の増減もできます。

例えば、ファイルを右クリックでファイル系メニュー、フォルダーを右クリックでフォルダー系メニューを表示したい場合、両方のメニューを記述しておいて、不要なメニューの Visibility を Collapsed にすることで対応できます。

<Label Content="動的に状態を制御するコンテキストメニュー" Margin="0,10,0,0" >
<behaviors:Interaction.Triggers>
    <behaviors:EventTrigger EventName="ContextMenuOpening">
        <l:LivetCallMethodAction MethodName="PrepareDynamicStateContextMenu" MethodTarget="{Binding}" />
    </behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
<Label.ContextMenu>
    <ContextMenu>
        <MenuItem Header="{Binding MenuItemHeaderDynamicState}" HeaderStringFormat="{}{0} 1" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="動的状態制御メニュー 1" />
        <MenuItem Header="{Binding MenuItemHeaderDynamicState}" HeaderStringFormat="{}{0} 2" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="動的状態制御メニュー 2" IsEnabled="{Binding IsDynamicStateMenuItemEnabled}" />
        <MenuItem Header="{Binding MenuItemHeaderDynamicState}" HeaderStringFormat="{}{0} 3" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="動的状態制御メニュー 3" Visibility="{Binding DynamicStateMenuItemVisibility}"/>
    </ContextMenu>
</Label.ContextMenu>
</Label>

動的にアイテム数を制御するコンテキストメニュー

Visibility の制御ではまかなえないほどフリーダムにコンテキストメニューのアイテムを変更したい場合は、ItemsSource をバインドし、ContextMenuOpening イベントで ItemsSource を変更します。

<Label Content="動的にアイテム数を制御するコンテキストメニュー" >
<behaviors:Interaction.Triggers>
    <behaviors:EventTrigger EventName="ContextMenuOpening">
        <l:LivetCallMethodAction MethodName="PrepareDynamicItemsContextMenu" MethodTarget="{Binding}" />
    </behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
<Label.ContextMenu>
    <ContextMenu ItemsSource="{Binding DynamicMenuItems}" />
</Label.ContextMenu>
</Label>

コンテキストメニューでサブフォルダーを列挙して表示するようなケースでは、こちらの方法が良いかと思います。