泥庭

2014年12月2日

メニューが左側に出る件

Filed under: WPF — タグ: , — yone64 @ 10:31 PM

以前、RibbonMenuを取り上げた際に、メニューが左側にでるということを書きました。

↓その時の記事
【WPF】Ribbonを使う(その③:RibbonApplicationMenu編)

これ不具合でもなんでもなくて、Windowsの設定なんですね。タッチした際にメニューが隠れないようにするための設定のようです。

キャプチャ

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

2014年4月8日

【WPF】Ribbonを使う(その②:RibbonWindow編)

Filed under: .NET, WPF — タグ: , , — yone64 @ 10:03 PM

準備が整ったところで、さっそくRibbonメニューを使ったWindowを作ってみましょう。

通常のWindowの場合

とりあえず、WindowにRibbonを配置して、適当にボタンやらなんやらを追加してみましょう。

image

XAML的は、こんな感じです。

<Window x:Class="RibbonApplicatin.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="362" Width="911">
    <Grid>
        <Ribbon>
            <Ribbon.QuickAccessToolBar>
                <RibbonQuickAccessToolBar>
                    <RibbonButton SmallImageSource="/Images/112_ArrowCurve_Blue_Left_16x16_72.png" />
                    <RibbonButton SmallImageSource="/Images/112_ArrowCurve_Blue_Right_16x16_72.png" />
                </RibbonQuickAccessToolBar>
            </Ribbon.QuickAccessToolBar>
            <Ribbon.ApplicationMenu>
                <RibbonApplicationMenu SmallImageSource="/Images/005_Task_16x16_72.png">
                </RibbonApplicationMenu>
            </Ribbon.ApplicationMenu>
            <RibbonTab Header="タブ1" ContextualTabGroupHeader="ヘッダー">
                <RibbonGroup Header="グループ" >
                    <RibbonButton LargeImageSource="/Images/FavoriteStar_FrontFacing_32x32_72.png" Label="ボタン"/>
                    <RibbonButton LargeImageSource="/Images/FavoriteStar_FrontFacing_32x32_72.png" Label="ボタン"/>
                    <RibbonButton LargeImageSource="/Images/FavoriteStar_FrontFacing_32x32_72.png" Label="ボタン"/>
                </RibbonGroup>
            </RibbonTab>
            <RibbonTab Header="タブ2" ContextualTabGroupHeader="ヘッダー">
                <RibbonGroup Header="グループ" >
                    <RibbonButton LargeImageSource="/Images/FavoriteStar_FrontFacing_32x32_72.png" Label="ボタン"/>
                    <RibbonButton LargeImageSource="/Images/FavoriteStar_FrontFacing_32x32_72.png" Label="ボタン"/>
                    <RibbonButton LargeImageSource="/Images/FavoriteStar_FrontFacing_32x32_72.png" Label="ボタン"/>
                </RibbonGroup>
            </RibbonTab>
            <Ribbon.ContextualTabGroups>
                <RibbonContextualTabGroup Header="ヘッダー" Visibility="Visible"/>
            </Ribbon.ContextualTabGroups>
        </Ribbon>
    </Grid>
</Window>

メニュー構成は適当です。ここの機能の詳細はおいおい説明することになると思うので省略しますが、注目すべきはWindowのタイトルバーです。並べてみるとよくわかりますが、ペイントではQuickToolBarがWindowのタイトルバーに乗っていますが、WPFのほうでは別になっています。

image

RibbonWindow

WPFで、Quick Tool BarをWindowに乗せるためには、RibbonWindowを使います。Windowを指定している3ヶ所(XAMLで2ヶ所とC#のコードビハインドで1ヶ所)をRibbonWindowに書き換えます。

<RibbonWindow x:Class="RibbonApplicatin.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="362" Width="911">
	<!-- 中略 -->
</RibbonWindow>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Ribbon;
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 RibbonApplicatin
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : RibbonWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

これを変更することで、↓のようなWindowになります。それっぽくなりましたね。

image

注意

RibbonWindowを使い場合、Ribbonが乗っているPanel(この場合Grid)に背景色を設定してはいけません。Windowのタイトルバーはおかしなことになります。

目立つように、GridのBackgroundに赤を指定したものを見てみましょう。

image

タイトルバーのアプリケーションアイコンや、最小化・最大化・閉じるボタンあたりが塗りつぶされてしまいます。これをみると、RibbonWindowのクライアント描画領域がタイトルバーの上まであることがよくわかりますね。

2014年4月6日

【WPF】Ribbonを使う(その①)

Filed under: .NET, WPF, 未分類 — タグ: , , — yone64 @ 7:24 PM

.NET Framework 4.5から、Robbonコントロールが標準で追加されています(※1)。とっても今更感満載ですが、このRibbonコントロールを使う機会があったので備忘録をかねてまとめておきます。
(※1)
http://msdn.microsoft.com/ja-jp/library/ms171868(v=vs.110).aspx#client

なお、Ribbonは下記によくまとまっているので、併せてご参照ください。(むしろそっちだけで十分かも)

[WPFリボン プログラミング] 連載一覧 (第1回~第14回)
http://blogs.msdn.com/b/ttanaka/archive/2011/03/09/wpf-1-14.aspx

Ribbon | HIRO’s.NET Blog
http://blog.hiros-dot.net/?page_id=3436

外観

RibbonといえばOffice2007で導入されたあれですが、Officeのバージョンやアプリケーション間で微妙に違っています。ほぼ、Application Buttonと、そのボタンをクリックして表示されるPaneの違いですが、WPFのRibbonはWindows7のPaintのそれに一番近いです。

image
【Windows7版のPaint】

準備

それでは、さっそく使ってみましょう。標準で追加されているとはいえ、そのままでは使えません。最初は参照設定がついていないので、参照追加が必要です。

image

参照を追加すると、Ribbonコントロール関係のコントロールが追加できるようになります。めっちゃ数が多い。

image

続きは次回

 

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