ラベル リボンUI の投稿を表示しています。 すべての投稿を表示
ラベル リボンUI の投稿を表示しています。 すべての投稿を表示

2014年7月12日土曜日

WindowChromeでWPFのRibbonWindowの枠を完璧にする

前回の記事の続きになります。
もはや永遠のテーマとも言えた.NET4.5のRibbonWindowの枠の話です。
なんとか

解 決 出 来 ま し た \(^o^)/

(´◔౪◔)۶ヨッシャ!

さて、それでは完成図をご覧いただきましょう。


前回の記事で1つ目の問題になっていた、ウィンドウの外枠とクライアント領域の間の境界線がちゃんと引けています。実はこれ、他愛のないことだったんですけどね。
ですが、何度も苦労させてきた最大化するとタイトルバーが黒くなる問題、これもバッチリ解決出来ました。


バッチリです。

とりあえず、例によってXAMLを載せます。

<RibbonWindow x:Class="WindowChromeTest.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:WindowChromeTest.Views"
        xmlns:vm="clr-namespace:WindowChromeTest.ViewModels"
        Title="MainWindow" Height="350" Width="525" BorderThickness="5" >

    <WindowChrome.WindowChrome>
        <WindowChrome GlassFrameThickness="7,29,7,7" NonClientFrameEdges="Bottom" ResizeBorderThickness="7" />
    </WindowChrome.WindowChrome>
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    
    <i:Interaction.Triggers>
        <!--WindowのContentRenderedイベントのタイミングでViewModelのInitializeメソッドが呼ばれます-->
        <i:EventTrigger EventName="ContentRendered">
            <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize"/>
        </i:EventTrigger>

        <!--Windowが閉じたタイミングでViewModelのDisposeメソッドが呼ばれます-->
        <i:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction/>
        </i:EventTrigger>

    </i:Interaction.Triggers>

    <DockPanel>
        <Border BorderBrush="Black" />
        <Ribbon DockPanel.Dock="Top" BorderThickness="1" >    
            <Ribbon.QuickAccessToolBar>
                <RibbonQuickAccessToolBar>
                    <RibbonButton SmallImageSource="/WindowChromeTest;component/Images/Configuration.ico" />
                    <RibbonButton SmallImageSource="/WindowChromeTest;component/Images/Information.ico" />
                </RibbonQuickAccessToolBar>
            </Ribbon.QuickAccessToolBar>
            <RibbonTab Header="Home">
                <RibbonGroup Header="Group">
                    <RibbonButton LargeImageSource="/WindowChromeTest;component/Images/Configuration.ico" Label="Config" />
                </RibbonGroup>
            </RibbonTab>
        </Ribbon>
        <TextBox Text="これはテキストボックスです" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible" />
    </DockPanel>
</RibbonWindow>

まず、第1の問題点の境界線の話ですが、これは、グラスフレームの範囲を具体的な数字で指定することで解決しています。
要するに、 全体をグラスフレームにしちゃうと当然その中に描いたものとグラスフレームの境界線は引かれなくなってしまいますが、ちゃんとクライアント領域の外側だけをグラスフレームにすると、そことクライアント領域の中の間の境界線が引かれるということです。

第2の問題点の最大化ですが、これはなぜ解決できたのか実はよくわかっていません。
しかし、NonClientFrameEdgesプロパティをBottomにしたら解決できてしまいました。

WindowChrome.NonClientFrameEdges プロパティ

「ウィンドウ フレームのどの外枠がクライアントによって所有されないかを示す値」って一体どういう意味だ?
意味わかりません。機械翻訳だから意味わからないのかなと思って原文を見てみましたが、原文もほとんどそのままで、結局意味はわからずじまいです。そもそも、ウィンドウフレームがクライアントに所有されたらなぜ最大化したときに黒くなくなるのかっていうのがわかっていませんからね…。

ちなみに、挙動をいろいろ研究してみたところ、
  • Topにする→右上の閉じる、最大化、最小化のボタンが押せなくなった
  • Rightにする→右上の閉じる、最大化、最小化のボタンが若干右に寄って見栄えが悪くなった
という現象が起こりました。Leftにしたらソフトウェアのアイコンが左に寄って見栄えが悪くなるのかなーって思ったらどうもそうではない模様。ほんとよくわからないです。

どうもこのプロパティ、.NET4.5から実装されたプロパティみたいで(ていうか、WindowChrome自体が参照を追加せずとも使えるように組み込まれたのが.NET4.5から)、資料も全然出回っていないようなんですよね。
何か詳しいことを知っている人がいたらぜひ教えてください。

WindowChromeでWPFのRibbonWindowの枠を改善する

以前、こちらの記事で.NET4.5ではリボンウィンドウの枠が残念な感じになってしまうという話をしました。しかし、WindowChromeをいじるといいみたいな話がどこかで出ていたので、ちょっと色々と試してみました。

WindowChromeクラスはウィンドウの非クライアント領域のカスタマイズをするためのクラスのようです。

WindowChrome クラス

私も詳しくはわかっていないのですが、このクラスをうまく使うと、Windowsの一般的なウィンドウ枠を一切使わない独自のウィンドウ枠や、もしくはWindows7のWindowsフォトビュアーやエクスプローラーなどで見られる、画面全体にAeroのすりガラスのエフェクトを掛けたりするといったことができるようです。

それでは一度ここで、WindowChromeを使ってない、以前の記事にも出てきたみすぼらしいウィンドウ枠を見ておきましょう。


このようにウィンドウ枠が細り、角もきれいな円弧となっていません。

ここで、WindowChromeを使って全体をすりガラスにし、さらにボーダーの太さをいじってみました。


おっ!これはいい感じだ!だいぶマシになってる!
ではここでソースコード行ってみましょう。

<RibbonWindow x:Class="WindowChromeTest.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:WindowChromeTest.Views"
        xmlns:vm="clr-namespace:WindowChromeTest.ViewModels"
        Title="MainWindow" Height="350" Width="525" BorderThickness="5" >

    <WindowChrome.WindowChrome>
        <WindowChrome GlassFrameThickness="-1" ResizeBorderThickness="7" />
    </WindowChrome.WindowChrome>

    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    
    <i:Interaction.Triggers>
        <!--WindowのContentRenderedイベントのタイミングでViewModelのInitializeメソッドが呼ばれます-->
        <i:EventTrigger EventName="ContentRendered">
            <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize"/>
        </i:EventTrigger>

        <!--Windowが閉じたタイミングでViewModelのDisposeメソッドが呼ばれます-->
        <i:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction/>
        </i:EventTrigger>

    </i:Interaction.Triggers>

    <DockPanel>
        <Border BorderBrush="Black" />
        <Ribbon DockPanel.Dock="Top" BorderThickness="1" >    
            <Ribbon.QuickAccessToolBar>
                <RibbonQuickAccessToolBar>
                    <RibbonButton SmallImageSource="/WindowChromeTest;component/Images/Configuration.ico" />
                    <RibbonButton SmallImageSource="/WindowChromeTest;component/Images/Information.ico" />
                </RibbonQuickAccessToolBar>
            </Ribbon.QuickAccessToolBar>
            <RibbonTab Header="Home">
                <RibbonGroup Header="Group">
                    <RibbonButton LargeImageSource="/WindowChromeTest;component/Images/Configuration.ico" Label="Config" />
                </RibbonGroup>
            </RibbonTab>
        </Ribbon>
        <TextBox Text="これはテキストボックスです" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible" />
    </DockPanel>
</RibbonWindow>

WindowChromeでリサイズボーダーの太さを設定すると同時に、RibbonWindowのほうでボーダー太さも設定していることに注意してください。

ただし、これが良いことずくめかというとそういうわけでは無いようです。

まず1つ目は気づいている方もいるかとは思いますが、クライアント領域の外側の枠がありません。なんかのっぺらぼうみたいな感じで違和感ありますね…。なんかいい方法は無いんでしょうかね。

2つ目は割と致命的です。ウィンドウを最大化してみると…


タイトルバーが真っ黒になってしまいましたorz
クイックアクセスツールバーや、最小化、元に戻す、閉じるボタンは有効なようです(この3つのボタンは見えませんがその位置をクリックするとちゃんとその効果があるようです)。

うーん、困ったなあ。Fluent Ribbon Control Suiteも含めて文句なしの手段が無いのがまた困りものです。

ついでにですが、Windows8だと結構まともに動いてくれるようです。



っていうか、そもそもWindows8だとあの極細のみっともないウィンドウ枠もあんまり目立ってダサくないんですよね…。

2014年6月27日金曜日

Fluent Ribbon Control Suiteの文字を大きくする。

さて、前回Fluent Ribbon Control Suiteを導入した話をしましたが、デフォルトのフォントが小さすぎるんですね…。こんな感じです。



ところで、どうもこのコントロールは文字サイズの変更を想定して設計されていないらしいです。なんてこった…。
一応FontSizeプロパティに値を書くことで部分的にフォントは変えられますが、全てのコントロールにそれを書き込んでいくのはナンセンスです。おまけに、フォントサイズを変えたところでタブの高さ等は変わらないので、文字が部分的に隠れて見えなくなったりしてしまいます。

そこで、LayoutTransformを使ってみることにしました。WPFの画面表示はGPUが司っているので、割と表示内容を行列変換的なことで変形しやすくなっています。
ScaleTransformで縦横それぞれ好きな比率で引き伸ばせますね。DockPanelを引き伸ばしてみましょう。

        <DockPanel.LayoutTransform>
            <ScaleTransform CenterX="0" CenterY="0" ScaleX="1.2" ScaleY="1.2" />
        </DockPanel.LayoutTransform>

はい、縦横それぞれ1.2倍ずつしています。
しかし、これではちょっとダサくなってしまいました。


コントロール全体は大きくなったものの、ウィンドウ枠の半透明な部分のサイズが変わっていないので、リボンタブを突っ切る形で半透明な部分が終わっています。これではとてもダサいです。
そこで、RibbonWindowのGlassBorderThicknessを指定してみました。
"8,60,8,8"をセットすることで、タブの位置に合わせて透明部分を下にずらすことができます。


はい。透明な部分は下がりましたが、その透過率を下げるグラデーション?みたいなやつの位置が変わらないのでこれもこれでとてもダサくなってしまいました。
いろいろいじってみましたけど、これは変わりそうにも無いですね…。
ならばいっそのこと、その半透明な部分を無くしてしまえばいいのだ!
というわけで、GlassBorderThicknessに"8,31,8,8"を入れてみました。


タブの部分の半透明な領域は無くなってしまいましたが、落としどころとしては悪くない気はします。

ただ、LayoutTransformで全体を1.2倍しているので、あらゆるパーツのフォントポイント等が大きくなってしまうんですよね…。その辺は若干気持ち悪いですが…。

なんかいい方法は無いんですかね。

2014年6月25日水曜日

リボンUI

個人的には、WinFormsではなくWPFを使うモチベーションとして、「リボンUIが使える」というものが挙げられます。

.NET4.0時代からMicrosoftはRibbonのライブラリを自社のページで公開していましたし、.NET4.5からはSystem.Windows.Controls.Ribbon名前空間内にRibbonクラスが組み込まれました。これにより、特に別途外部ライブラリを追加しなくてもリボンインターフェースを使ったアプリケーションが作れるようになりました。

そもそもWin32API時代から身からすると、さまざまなUIのコントロールはOS、もしくはそれに準ずるフレームワークが提供するのが常識でした(IEに新しいUIのコントロールが付いてきた時代を思い返せばとても懐かしいと思える人もいるかもしれません)。
そうやって多くのUIを共通パーツとしてDLLに入れてプログラマーに提供することで、アプリケーションからOSまで含めたシステム全体のサイズを小さくすることができ、ユーザーにとっても異なるアプリケーションで似たような一貫性のあるUIで操作できるというメリットが生まれます。
しかし、現代ではHDDやSSDの大容量化が進み、システム全体のサイズを気にするような時代でもありませんし、.NETなんかはかなりソフトウェアをパッケージングして提供する仕組みが(言語としてや、Nugetなどのサービスとしての両面から見て)とても優れています。
そのせいもあってか、Microsoftは最近あまりユーザーインターフェースの開発に積極的じゃないイメージがあります。基本的なボタンやコンボボックスなどのコントロールはさすがに提供してくれますが、WinFormsやWin32APIにあってWPFに無いコントロールもちらほら見かけます。さらに、IEやOfficeなどのMicrosoftのアプリケーションの中核商品に使われているコントロールも、昔はIEの新バージョンをインストールすれば使えるようになりましたが、今はあまり配布しているようには見えません。

しかし、リボンUIのライブラリはなぜかちゃんと提供してくれていたんですね。
やっぱり、当初VistaやOffice2007と同時に導入されてものすごく叩かれたからなんですかね。できるだけ多くのアプリケーションにリボンUIを使ってもらえれば、それだけリボンUIが世間に受け入れらていくものになるでしょう。


さて、それではリボンUIを導入してみましょう。

WPFのプロジェクトを作ります。私の場合はLivetのテンプレートから作ります。.NET4.5にすることを忘れないで下さい。


こんなかんじでウィンドウが出てきました。
次に、System.Windows.Controls.Ribbonへの参照を追加します。


つづいてソースコードを改造します。
と言っても非常に単純で、ウィンドウの親クラスがWindowからRibbonWindowになるだけです。
MainWindow.xamlとMainWindow.xaml.csの当該箇所を修正します。



あっれ~~~~~?????
なんかウィンドウ枠が小さくなってすごくダサくなっています。この現象が起こったのは.NET4.5からで、.NET4.0向けのライブラリでは問題ありませんでした。
まあそもそもリボンウィンドウの日本語記事があまり多くないのもあるのですが、これに関する日本語の情報は私が調べた限りでは皆無、英語の掲示板とかを見ても、結局有用な解決方法までは出てきませんでした。
例えばこのスレでは、最後にMicrosoftのWPF開発チームが書き込みをしていますが、もっと優先度が高い仕事はたくさんあるし、もしこれが本当にバグだと思うなら再現に必要な情報をもっと出せみたいなことを言っているようです。えー…再現できない環境とかあるのかよ…。

私が実際に条件を変えてコンパイルして実行してみた限りだと…
.NET4.0向けのライブラリ+.NET4.0=通常表示
.NET4.5内蔵ライブラリ=枠が細くなる
.NET4.0向けのライブラリ+.NET4.5=枠が細くなる
という再現結果になり、.NET4.5は使うなってことになってしまいました。
なんかこういうのは気に食わないですねえ。新しい機能が使えなくなってしまいます。
どちらかというと私はAppleよりMicrosoft信者ですが、こういうデザインに関する根本的な問題がでてきたら「Appleなら…」という気持ちも出てきてしまいます。

そのような中途方に暮れていると、こんなライブラリを発見しました。

Fluent Ribbon Control Suite

デザインがとても忠実にOffice2010のスタイルを再現しています。
これはもう公式ライブラリを捨ててこれを使うしか無いんじゃないか!?
ドキュメンテーションも結構しっかりと書いてあったりします。サンプルコードも充実しています。

というわけで導入してみました。
導入でつまずいたところは、最新のLivetのプロジェクトテンプレートだとなんかのファイルで競合を起こしてうまくビルドができない点ですかね。
仕方がないので、ただのWPFプロジェクトを立ち上げてから、Fluent RibbonとLivetの両方をNuget経由で入手しました。

Livetの流儀でウィンドウをViewsディレクトリ下に入れるの好きなので、デフォルトでついてくるMainWindow.xamlは削除し、Viewsディレクトリの中にMainWindow.xamlを入れます。そうすると、スタートアップウィンドウのパスが変わるので、App.xamlを修正してやります。
ついでに、 テーマのリソースを指定してやる必要があるので、それを追加します。

<Application x:Class="RibbonTest.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="Views\MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary Source="pack://application:,,,/Fluent;Component/Themes/Office2010/Silver.xaml" />
    </Application.Resources>
</Application>

あとは、標準のリボンライブラリとほとんど同じです。
 親クラスをWindowからFluent.RibbonWindowにしてやって、一通りのコントロールをXAMLで記述します。こんなかんじです。

<Fluent:RibbonWindow x:Class="RibbonTest.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:RibbonTest.Views"
        xmlns:vm="clr-namespace:RibbonTest.ViewModels"
        xmlns:Fluent="clr-namespace:Fluent;assembly=Fluent"
        Title="MainWindow" Width="640" Height="480">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    
     <i:Interaction.Triggers>     
        <i:EventTrigger EventName="ContentRendered">
            <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize"/>
        </i:EventTrigger>

        <i:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    
    <DockPanel>
        <Fluent:Ribbon DockPanel.Dock="Top">
            <Fluent:Ribbon.Menu>
                <Fluent:Backstage Header="Menu" >
                    <Fluent:BackstageTabControl>
                        <Fluent:BackstageTabItem Header="New" />
                        <Fluent:BackstageTabItem Header="Open" />
                    </Fluent:BackstageTabControl>
                </Fluent:Backstage>
            </Fluent:Ribbon.Menu>
            <Fluent:RibbonTabItem Header="Home">
                <Fluent:RibbonGroupBox Header="Group1">
                    <Fluent:Button Header="Button1" />
                    <Fluent:Button Header="Button2" />
                </Fluent:RibbonGroupBox>
            </Fluent:RibbonTabItem>
            <Fluent:RibbonTabItem Header="Insert">
                <Fluent:RibbonGroupBox Header="Group2">
                    <Fluent:Button Header="Button3" />
                    <Fluent:Button Header="Button4" />
                </Fluent:RibbonGroupBox>
            </Fluent:RibbonTabItem>
        </Fluent:Ribbon>
        <TextBox />
    </DockPanel>
</Fluent:RibbonWindow>

これを起動するとこんな感じになります。


見事にそれっぽいリボンUIができました。
ついでに、Menuを押すとこのような画面に遷移します。


ウッヒョオオオオオォォォォォォ!!めっちゃOffice2010っぽい!!
Microsoftの標準ライブラリはOffice2007スタイルで、メニューボタンを押すとドロップダウンメニューが出てくるだけなんですよね。

ただ1つ、なんかフォントが小さいですね…。
FontSizeとかLayoutTransformとかでいじってみましたが、どれも満足行く結果は得られずじまいでした。
ただ、テーマという概念があるっぽいので、どうにかできないかちょっと検討してみます…。