2012年4月18日水曜日

wpf : ListBoxItemが右クリックで選択されないようにする

ちょっとだけ特殊な用途ですが、ListBoxItemにContextMenuを登録して、左クリックで選択の変更、右クリックでは選択を変更せずにContextMenuを表示という振る舞いにしたくなりました。 ListBox.ItemTemplateでMouseRightButtonDownイベントを登録し、握りつぶすだけで実現できました。

<Window x:Class="ListBoxItemEventTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxItemEventTest" Height="128" Width="256">
    <Window.Resources>
        <ContextMenu x:Key="contextMenu">
            <MenuItem Header="テストメニュー" Click="OnMenuItemClick"/>
        </ContextMenu>
    </Window.Resources>
    <ListBox Name="listBox">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Label
                        Content="{Binding}"
                        ContextMenu="{DynamicResource contextMenu}"
                        MouseRightButtonDown="OnListBoxMouseRightButtonDown"
                />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Window>
// MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace ListBoxItemEventTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            listBox.ItemsSource = new string[]
            {
                "ひとよひとよにひとみごろ",
                "ひとなみにおごれやおなご",
                "ふじさんろくおおむなく"
            };
        }

        public void OnListBoxMouseRightButtonDown(Object sender, MouseButtonEventArgs evt)
        {
            evt.Handled = true; //← 握りつぶし
        }

        private void OnMenuItemClick(object sender, RoutedEventArgs evt)
        {
            ListBoxItem listBoxItem = (ListBoxItem)GetOwnerControlFromMenuItem((MenuItem)sender, typeof(ListBoxItem));
            MessageBox.Show(this, listBoxItem.Content.ToString());
        }

        private DependencyObject GetOwnerControlFromMenuItem(MenuItem menuItem, Type controlType)
        {
            DependencyObject obj = menuItem;
            while (!(obj is ContextMenu))
                obj = LogicalTreeHelper.GetParent(obj);

            FrameworkElement elem = (FrameworkElement)((ContextMenu)obj).PlacementTarget;
            obj = elem.TemplatedParent;

            while (!(obj.GetType() == controlType))
                obj = VisualTreeHelper.GetParent(obj);

            return obj;
        }
    }
}

ContextMenuからListBoxItemを探す方法は前の投稿参照で。

実は、最初はListBoxのスタイルでMouseRightButtonDownを登録して試してみたんですよね。

<ListBox.Resources>
    <Style TargetType="ListBoxItem">
        <Setter
                Property="ContextMenu"
                Value="{DynamicResource contextMenu}"
        />
        <EventSetter
                Event="MouseRightButtonDown"
                Handler="OnListBoxMouseRightButtonDown"
        />
    </Style>
</ListBox.Resources>

でもこのやり方だとContextMenuのクリックイベントからListBoxItemを特定することができず、目的を達成できませんでした。 目的を達成できなかったのはただリファレンスの読み込みが足りなかったのか? それともやはりスタイルからでは実現できない振る舞いだったのか?

wpfって他のGUIライブラリに比べてこういう瑣末な部分の知識が要求されることが多いような気がします。 そして10年後に残って無さそうな...

無駄な事やっているような気がしてきました。

追記)

ListBox自体が他のコントロールのTemplateに書かれている場合、最初に書いたItemTemplateにMouseRightButtonDownイベントを書くやり方では例外が発生しました。 この場合はMouseRightButtonDownイベントはスタイルで、ContextMenuはListBox.ItemTemplateで書かないとダメらしいです。 瑣末だ...