2011年7月11日月曜日

WPF CommandBindingsしたコンテキストメニューをすぐにアクティブにする

趣味で使うためにVisual C# 2010 Expressをインストールしました。 ちょっとwpfを触ってみる予定です。 手始めに、このサイトを参考に簡単な画像表示ソフトを作りました。

コードはこんな感じです。

// AppCommands.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Windows;

namespace LeisurelyImageViewer
{
    class AppCommands
    {
        public readonly static RoutedUICommand CommandOpen= new RoutedUICommand(
            "開く",
            "CommandOpen",
            typeof(AppCommands),
            new InputGestureCollection
            {
                new KeyGesture(Key.O, ModifierKeys.Control)
            }
        );
    }
}
<!-- MainWindow.xaml -->
<Window x:Class="LeisurelyImageViewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:app="clr-namespace:LeisurelyImageViewer"
        Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="{x:Static app:AppCommands.CommandOpen}" Executed="OpenFile"/>
    </Window.CommandBindings>
    <DockPanel Name="mainDockPanel">
        <ScrollViewer Name="mainScrollViewer" VerticalScrollBarVisibility="Disabled" Background="Black">
            <ScrollViewer.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="開く" Command="{x:Static app:AppCommands.CommandOpen}"/>
                </ContextMenu>
            </ScrollViewer.ContextMenu>
            <Image Name="mainImage" Stretch="Uniform"/>
        </ScrollViewer>
    </DockPanel>
</Window>
// MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace LeisurelyImageViewer
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void OpenFile(object sender, RoutedEventArgs e)
        {
            Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
            dlg.CheckFileExists = true;
            dlg.Filter = "Image Files(*.PNG;*.BMP;*.JPG;*.GIF)|*.PNG;*.BMP;*.JPG;*.GIF|All files (*.*)|*.*";
            if (dlg.ShowDialog() == true)
            {
                BitmapImage newImg = new BitmapImage();
                newImg.BeginInit();
                newImg.UriSource = new Uri(dlg.FileName);
                newImg.EndInit();
                mainImage.Source = newImg;
            }
        }
    }
}

単純に、ファイル選択ダイアログで選んだファイルを表示するだけです。 こんな小さなプログラムなのに問題がありました。 コンテキストメニューの「開く」がなぜかすぐにアクティブにならなかったのです。 最初に右クリックでメニューを出したときはグレー表示で、2度目か、場合によってはそれ以降メニューを出したときやっとアクティブになってました。

他の開発環境ならあっという間に検索できそうなネタですが、wpfは日本であまり普及していないらしく、解決法を探すのに少しだけ手間取りました。 英語の掲示板を機械翻訳したサイトにありました。

英語の文章はほとんど読んでません。 とりあえず対処法だけ拾いました。 すぐアクティブにするにはMenuItemにCommandTargetの設定をしなければならないようです。 意味は分からないけど、

CommandTarget="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}

とするらしい。 上のコードのContextMenuのところはこうなります。

<ContextMenu>
    <MenuItem Header="開く" Command="{x:Static app:AppCommands.CommandOpen}" CommandTarget="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>

後で気が向いたら意味とか仕組みとかを調べておかなくては。

メニューの項目がたった1つしかないっていうアプリケーションはあまりありません。 当然メニューの項目は2つ以上あるのが普通です。 今のところ、毎回「CommandTarget=...」と書くしかないのかなぁ? 意味とか仕組みとかがわかったら共通の設定を1箇所で済ませるような方法も分かるかもしれませんね?

あ~。 上のコード、usingの整理していなかった...