泥庭

2014年4月18日

【WPF】Ribbonを使う(その③:RibbonApplicationMenu編)

Filed under: .NET, WPF — タグ: , , — yone64 @ 1:05 AM

前回から間が空きましたが、第3回。今回はRibbonApplicationMenuを見ていきたいと思います。

様々なApplicationMenu

RibbonApplicationMenuは、プラットフォームごとに違いが大きい項目です。ちょっと見てみましょう。

Office2007

image

http://support.microsoft.com/kb/926338/ja

最初に、Ribbonが導入されたアプリケーションですね。ApplicationMenuを開くためのボタンが特徴的です。

Office2010

image

Officeは次のバージョンから、大きく変わってます。
ApplicationMenuがボタンではなくて、タブになりました。表示内容もWindowいっぱいに表示されるようになってます。Office2013も基本は同じです。

ペイント(Windows7)

image

Office2007と2010の中間みたいなデザインになっています。メニューを開くためのボタンはタブになっていますが、メニューはDropDown形式です。タブに表示されるのがアイコンなのが特徴です。

ペイント(Windows8)

image

基本はWindows7のペイントと同じですが、タブに表示されるのがアイコンから文字に変更されています。

WPFのApplicationMenu

RibbonApplicationMenuは、ItemsControlのサブクラスです。子要素のItemsを表示する領域のほかに、アイコンを表示する領域・補助ペイン・フッターペインが拡張されています。

image

なお、上記ApplicationMenuを表示するためのXAMLは以下の通りです。(※ApplicationMenu部分のみ抜粋)

<Ribbon>
    <Ribbon.ApplicationMenu>
        <RibbonApplicationMenu SmallImageSource="/Images/005_Task_16x16_72.png">
            <RibbonApplicationMenu.AuxiliaryPaneContent>
                <RibbonGallery>
                    <RibbonGalleryCategory Header="最近使ったファイル" MaxColumnCount="1">
                        <RibbonGalleryItem Content="C:\aaa\bbb.txt"/>
                        <RibbonGalleryItem Content="C:\aaa\bbb2.txt"/>
                    </RibbonGalleryCategory>
                </RibbonGallery>
            </RibbonApplicationMenu.AuxiliaryPaneContent>
            <RibbonApplicationMenu.FooterPaneContent>
                <DockPanel LastChildFill="False">
                    <RibbonButton Label="閉じる" Margin="2"
                                    DockPanel.Dock="Right"
                                    SmallImageSource="/Images/1385_Disable_16x16_72.png"/>
                    <RibbonButton Label="オプション" Margin="2"
                                    DockPanel.Dock="Right"
                                    SmallImageSource="/Images/109_AllAnnotations_Info_16x16_72.png"/>
                </DockPanel>
            </RibbonApplicationMenu.FooterPaneContent>
            <RibbonApplicationMenuItem Header="開く"
                                        ImageSource="/Images/075b_UpFolder_32x32_72.png"/>
            <RibbonApplicationMenuItem Header="新規作成">
                <RibbonApplicationMenuItem Header="フォルダーを作成する"
                                            ImageSource="/Images/newfldr.ico"/>
                <RibbonApplicationMenuItem Header="ファイルを作成する"
                                            ImageSource="/Images/077_AddFile.ico"/>
            </RibbonApplicationMenuItem>
            <RibbonSeparator/>
            <RibbonApplicationSplitMenuItem Header="印刷"
                                            ImageSource="/Images/Printer.ico">
                <RibbonApplicationMenuItem Header="印刷"
                                            ImageSource="/Images/Printer.ico"/>
                <RibbonApplicationMenuItem Header="印刷プレビュー"
                                            ImageSource="/Images/007_PrintView_128x128_72.png"/>
            </RibbonApplicationSplitMenuItem>
            <RibbonApplicationMenuItem Header="閉じる"/>
        </RibbonApplicationMenu>
    </Ribbon.ApplicationMenu>
</Ribbon>

SmallImageSource (アイコン)
ApplicationMenuを表示するためのタブに表示する画像を指定します。RibbonApplicationMenuクラスにはLabelプロパティーがありますが、使用されません。画像のみ指定可能です。

(※)どうしても文字列を表示したい場合はGlyph関連クラスを利用することで表示可能となります。
http://stackoverflow.com/questions/6698191/how-to-set-text-at-the-head-of-a-ribbonapplicationmenu

AuxiliaryPaneContent (補助ペイン)
補助ペインは、多くのアプリケーションで最近使ったファイルなどを表示する領域になっています。WPFではContentControlのContent相当の領域となっているので、どのようなコントロールでもホスト可能です。ただ、RibbonGalleryをホストするの一般的のようです。

FooterPaneContent (フッター)
この領域もAuxiliaryPaneContentと同様、どのようなコントロールでもホスト可能です。ただ、Office2007ではボタンが設置されていましたが、それ以外のアプリケーションではこの領域は使われていません。

Items (メニュー)
ItemsControlの子要素をホストするための領域です。子要素としては、RibbonApplicationMenuItem / RibbonApplicationSplitMenuItem / RibbonSeparatorをとることができます。

子要素

前述の通り、RibbonApplicationMenuItem / RibbonApplicationSplitMenuItem / RibbonSeparatorをホストすることができます。各コントロールの違いは下記通りです。

RibbonApplicationMenuItem
ApplicationMenu内に表示される通常のメニューです。アイコンとラベルを表示可能で、階層表示もできます。

image

RibbonApplicationSplitMenuItem
ApplicationMenu内に表示される分割されたメニューです。アイコンとラベルを表示可能で、階層表示もできます。通常のメニューでは子要素を持つ場合に親要素で処理を実行することはできませんが、SplitMenuでは親要素で処理実行を行うこともできる点が異なります。

image

RibbonSeparator
メニューの区切りです。それ以上の役割はありません。

image

RibbonGalleryはまたの機会に。

Bindingに挑戦

RibbonApplicationMenuはItemsControlのサブクラスなので、ItemsSourceにBindingすることで子要素を作成することができます。ただし、ItemContainerに相当するものが、3種類あるため一工夫が必要になります。

では、BindingするClassを定義しておきます。恣意的ではありますが、後々のために以下のようにしました。

public class RibbonMenuViewModel
{
    public RibbonMenuType MenuType { get; set; }
    public string Text { get; set; }
    public string ImageSource { get; set; }
    public List<RibbonMenuViewModel> Children { get; set; }
}

public enum RibbonMenuType
{
    Normal,
    SplitMenu,
    Separator,
}

Binding用のデータ

this.DataContext = new List<RibbonMenuViewModel>
{
    new RibbonMenuViewModel
    {
        MenuType = RibbonMenuType.Normal,
        Text = "通常メニュー",
        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
    },
    new RibbonMenuViewModel
    {
        MenuType = RibbonMenuType.Normal,
        Text = "通常メニュー(階層)",
        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
        Children = new List<RibbonMenuViewModel>
        {
            new RibbonMenuViewModel
            {
                MenuType = RibbonMenuType.Normal,
                Text = "サブメニュー",
                ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
            },
            new RibbonMenuViewModel
            {
                MenuType = RibbonMenuType.Separator,
            },
            new RibbonMenuViewModel
            {
                MenuType = RibbonMenuType.SplitMenu,
                Text = "Splitサブメニュー",
                ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
                Children = new List<RibbonMenuViewModel>
                {
                    new RibbonMenuViewModel
                    {
                        MenuType = RibbonMenuType.Normal,
                        Text = "サブサブメニュー1",
                        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
                    },
                    new RibbonMenuViewModel
                    {
                        MenuType = RibbonMenuType.Separator,
                    },
                    new RibbonMenuViewModel
                    {
                        MenuType = RibbonMenuType.Normal,
                        Text = "サブサブメニュー2",
                        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
                    },
                },
            },
        },
    },
    new RibbonMenuViewModel
    {
        MenuType = RibbonMenuType.SplitMenu,
        Text = "分割メニュー",
        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
        Children = new List<RibbonMenuViewModel>
        {
            new RibbonMenuViewModel
            {
                MenuType = RibbonMenuType.SplitMenu,
                Text = "分割サブメニュー",
                ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
                Children = new List<RibbonMenuViewModel>
                {
                    new RibbonMenuViewModel
                    {
                        MenuType = RibbonMenuType.Normal,
                        Text = "サブサブメニュー1",
                        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
                    },
                    new RibbonMenuViewModel
                    {
                        MenuType = RibbonMenuType.Separator,
                    },
                    new RibbonMenuViewModel
                    {
                        MenuType = RibbonMenuType.Normal,
                        Text = "サブサブメニュー2",
                        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
                    },
                },
            },
            new RibbonMenuViewModel
            {
                MenuType = RibbonMenuType.Separator,
            },
            new RibbonMenuViewModel
            {
                MenuType = RibbonMenuType.SplitMenu,
                Text = "分割サブメニュー",
                ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
                Children = new List<RibbonMenuViewModel>
                {
                    new RibbonMenuViewModel
                    {
                        MenuType = RibbonMenuType.Normal,
                        Text = "サブサブメニュー1",
                        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
                    },
                    new RibbonMenuViewModel
                    {
                        MenuType = RibbonMenuType.Separator,
                    },
                    new RibbonMenuViewModel
                    {
                        MenuType = RibbonMenuType.Normal,
                        Text = "サブサブメニュー2",
                        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
                    },
                },
            },
        },
    },
    new RibbonMenuViewModel
    {
        MenuType = RibbonMenuType.Separator,
    },
    new RibbonMenuViewModel
    {
        Text = "Menu3",
        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
    },
    new RibbonMenuViewModel
    {
        Text = "Menu4",
        ImageSource = "/Images/FavoriteStar_FrontFacing_32x32_72.png",
    },
};

で、XAML。とりあえずはBindingだけバージョン。

<Ribbon>
    <Ribbon.ApplicationMenu>
        <RibbonApplicationMenu ItemsSource="{Binding}" />
    </Ribbon.ApplicationMenu>
</Ribbon>

これを実行すると、以下のようになります。

image

メニュー部分がItemsControlになっているのがよくわかります。しかし、このままではアレなのでItemTemplateにDataTemplateを適用するのが常套手段です。Menuは、階層構造をとるものなのでHierarchicalDataTemplateになります。XAMLは以下のように修正しました。

<Ribbon>
    <Ribbon.ApplicationMenu>
        <RibbonApplicationMenu ItemsSource="{Binding}">
            <RibbonApplicationMenu.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}" >
                    <TextBlock Text="{Binding Text}"/>
                </HierarchicalDataTemplate>
            </RibbonApplicationMenu.ItemTemplate>
        </RibbonApplicationMenu>
    </Ribbon.ApplicationMenu>
</Ribbon>

TextプロパティーをTextBlockとBindingすることで、メニューラベルが表示されるようになります。だいぶそれっぽっくなりました。

image

しかし、ItemTemplateで変更できるのはContentPresenterの内部だけです。たとえば、RibbonApplicationMenuItemのImageSouceプロパティーは、ItemTemplateからは設定することができません。Container自身のプロパティーは、ItemContainerStyleを通じて設定します。

<Ribbon>
    <Ribbon.ApplicationMenu>
        <RibbonApplicationMenu ItemsSource="{Binding}">
            <RibbonApplicationMenu.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}" >
                    <TextBlock Text="{Binding Text}"/>
                </HierarchicalDataTemplate>
            </RibbonApplicationMenu.ItemTemplate>
            <RibbonApplicationMenu.ItemContainerStyle>
                <Style TargetType="{x:Type RibbonApplicationMenuItem}">
                    <Setter Property="ImageSource" Value="{Binding ImageSource}"/>
                </Style>
            </RibbonApplicationMenu.ItemContainerStyle>
        </RibbonApplicationMenu>
    </Ribbon.ApplicationMenu>
</Ribbon>

Style設定後の画面は次の通りです。無事アイコンが設定されました。

image

しかし、ItemContainerStyleで出来るのもここまでです。Container自体の型は変更できません、RibbonApplicationMenuItemのままです。Containerの型を変更することができません。これを変更するには、ItemContainerTemplateSelectorを使用します。TemplateSelector系は、XAMLだけで閉じないのがつらい感じですね。というわけで以下のようなClassを用意します。

public class RibbonMenuTemplateSelector : ItemContainerTemplateSelector
{
    public DataTemplate NormalTemplate { get; set; }
    public DataTemplate SplitMenuTemplate { get; set; }
    public DataTemplate SeparateorTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl)
    {
        switch (((RibbonMenuViewModel)item).MenuType)
        {
            case RibbonMenuType.Normal:
                return NormalTemplate;
            case RibbonMenuType.SplitMenu:
                return SplitMenuTemplate;
            case RibbonMenuType.Separator:
                return SeparateorTemplate;
            default:
                break;
        }
        return null;
    }
}

で、これを使うと、こんな感じで書けます。ItemsContainerTemplateSelectorに渡すための各種DataTemplateを定義し、ItemsContainerTemplateSelectorとそれを有効にするためのUsesItemContainerTemplateを設定します。

<Window.Resources>
    <DataTemplate x:Key="NormalTemplate">
        <RibbonApplicationMenuItem Header="{Binding Text}"
                                   ImageSource="{Binding ImageSource}"
                                   ItemsSource="{Binding Children}"
                                   UsesItemContainerTemplate="True"
                                   ItemContainerTemplateSelector="{DynamicResource TemplateSelector}"/>
    </DataTemplate>
    <DataTemplate x:Key="SplitMenuTemplate">
        <RibbonApplicationSplitMenuItem Header="{Binding Text}"
                                        ImageSource="{Binding ImageSource}"
                                        ItemsSource="{Binding Children}"
                                        UsesItemContainerTemplate="True"
                                        ItemContainerTemplateSelector="{DynamicResource TemplateSelector}"/>
    </DataTemplate>
    <DataTemplate x:Key="SeparatorTemplate">
        <RibbonSeparator/>
    </DataTemplate>
    <local:RibbonMenuTemplateSelector x:Key="TemplateSelector"
                                      NormalTemplate="{StaticResource NormalTemplate}"
                                      SplitMenuTemplate="{StaticResource SplitMenuTemplate}"
                                      SeparateorTemplate="{StaticResource SeparatorTemplate}"/>
</Window.Resources>
<Grid>
    <Ribbon>
        <Ribbon.ApplicationMenu>
            <RibbonApplicationMenu ItemContainerTemplateSelector="{DynamicResource TemplateSelector}"
                                   UsesItemContainerTemplate="True"
                                   ItemsSource="{Binding}"/>
        </Ribbon.ApplicationMenu>
    </Ribbon>
</Grid>

すると、下記通りメニューを設定するとことができます。結局ItemsTemplateとItemContainerStyleは使わなくてよかったんですね。

image

ただ、実行はできるのですが、XAMLを開くと「プロパティーにループ参照があります。」というエラーが出ます。確かにループ参照しているので、指摘の通りなのですが、エラーになるのはなんだか嫌ですね。

その他Tips

ApplicationMenuを表示しない

RibbonApplicationMenuのVisibilityを設定することで可能です。

<Ribbon.ApplicationMenu>
    <RibbonApplicationMenu Visibility="Collapsed"/>
</Ribbon.ApplicationMenu>

image

不具合っぽいの

WPFのApplicationMenuには、表示が左側にずれるという環境依存の不具合があります(※1)。ただ、表示位置がずれる環境では、その他アプリケーションのDropDownの位置ももれなくずれているため(※2)、完全にWPFのせいというわけではないようです。ただし、ApplicationMenuがずれるのはWPFだけなのですよね(※3)。解決方法をご存知の方は教えてください。CodePlexにはこれに関するIssueが登録されているようです(※4)。ちなみに、この不具合は、Windows7でもWindows8でも発生を確認しています。

(※1)
image

(※2)ペイントの例
image

(※3)ペイントの場合、ApplicationMenuがずれることはない
image

(※4)
http://wpf.codeplex.com/workitem/14546

広告

1件のコメント »

  1. […] ↓その時の記事 【WPF】Ribbonを使う(その③:RibbonApplicationMenu編) […]

    ピンバック by メニューが左側に出る件 | 泥庭 — 2014年12月2日 @ 10:31 PM


RSS feed for comments on this post. TrackBack URI

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

WordPress.com で無料サイトやブログを作成.

%d人のブロガーが「いいね」をつけました。