読者です 読者をやめる 読者になる 読者になる

SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

WPF用にLightBox的なポップアップ表示をするライブラリを作ってみた

WPF C#

こんな風に、LightBox的なダイアログ表示を行うライブラリを作ってみました。
f:id:minami_SC:20160403174651g:plain

概要

WPFLightBox的なダイアログ表示を行うライブラリです。
ダイアログは、別ウィンドウではなく、呼び出し元ウィンドウ上にAdornerとして重ねて表示します。

コードビハインドからMessageBoxを呼ぶのと同じ感覚で、イイ感じのLightBox表示ができます。

LightBox.Show(this, new SampleDialog());

ざっくり特徴をまとめると以下のような感じ。

  • WindowのXAMLコードには手をいれずに、元のウィンドウ上にLightBoxを重ねて表示できる。
  • モーダル/モードレスどちらの表示にも対応
    • Show/ShowDialog/ShowAsyncの3種類の呼び出し方法
  • LightBoxの中身には、任意のFrameworkElement派生クラスの要素を表示できる。
    • ImageコントロールのようなPrimitiveな型や、独自に作成したUserControlなどを表示可能。
  • アニメーション対応
    • LightBoxを表示するとき/閉じるときにアニメーションを付けることができます。
  • XAMLで各種デザインをカスタマイズ可能
  • 複数ダイアログの同時表示

コードは短めですが、割とカスタマイズ性の高いライブラリになっているのでは、と思います。

使い方

インストール

Nugetからの以下のコマンドでインストールできます。

Install-Package Lighty

また、GUIからでもLightyと検索すれば見つかります。
f:id:minami_SC:20160403174800p:plain

コイツをインストールすればOK!!

準備

lightyの機能を、c#コードから利用する場合には、以下の名前空間のusingを追加しておきます。

using SourceChord.Lighty;

また、xaml側でlightbox表示のカスタマイズをしたい場合には、このxml名前空間を使います。

xmlns:lighty="clr-namespace:SourceChord.Lighty;assembly=Lighty"

lightbox表示

lightyを使ってポップアップ表示を行うには、コードビハインドから以下のコードを呼び出します。
(SampleDialogは任意のユーザーコントロール)

LightBox.Show(this, new SampleDialog());

すると、このようにウィンドウ全体が暗くなって、ダイアログが表示されます。
f:id:minami_SC:20160403174813p:plain

第一引数には、LightBox表示を行う領域。
第二引数には、表示するコンテンツを指定します。
ウィンドウ全体に表示したい場合には、上記の例のようにコードビハインドから呼び出す際にthisを指定すればOKです。

モーダル/モードレス/Asyncな表示

ダイアログ表示用に、以下の3種類のメソッドを用意しています。

Method Name Description
Show LightBoxをモードレス表示します。
ShowDialog LightBoxをモーダル表示します。すべてのLightBoxを閉じるまで、後続のコードは実行しません。
ShowAsync Show()メソッドのAsyncバージョン。asyncメソッド内でawaitと併用して使うことができます。

それぞれ、こんな風に呼び出すことができます。

        private async void OnClickButton(object sender, RoutedEventArgs e)
        {
            // modeless dialog
            LightBox.Show(this, new SampleDialog());

            // async method
            await LightBox.ShowAsync(this, new SampleDialog());

            // modal dialog
            LightBox.ShowDialog(this, new SampleDialog());
        }
表示内容の指定

Showメソッドなどの第二引数には、FrameworkElement派生の任意のオブジェクトを指定できます。

usercontrolをコンテンツとして表示

LightBox.Show(this, new SampleDialog());

f:id:minami_SC:20160403174813p:plain:w250

任意のエレメントを表示

            var image = new Image();
            image.Source = new BitmapImage(new Uri("Images/1.jpg", UriKind.Relative));
            LightBox.Show(this, image);

f:id:minami_SC:20160403174837p:plain:w250

任意の領域への表示

Showメソッドなどの第一引数には、XAMLのロジカルツリー上の任意の要素を指定できます。
こんな風に、任意のgridなどを指定して、その要素上にLightBox表示ができます。

<Window x:Class="LightySample.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:lighty="clr-namespace:SourceChord.Lighty;assembly=Lighty"
        xmlns:local="clr-namespace:LightySample"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="Window1"
        Width="500"
        Height="300"
        mc:Ignorable="d">
    <Grid>
        <Button x:Name="button"
                Width="75"
                Margin="10,10,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Click="button_Click"
                Content="Button" />
        <Grid x:Name="subGrid"
              Width="300"
              Height="200"
              Margin="10"
              HorizontalAlignment="Right"
              VerticalAlignment="Bottom" />
    </Grid>
</Window>
        private void button_Click(object sender, RoutedEventArgs e)
        {
            LightBox.Show(this.subGrid, new SampleDialog());
        }

f:id:minami_SC:20160403174900p:plain

ダイアログの閉じ方

ダイアログを閉じるには以下のような方法があります。

ApplicationCommandを利用
WPFの定義済みルーティングコマンドApplicationCommands.Closeを実行すると、そのコマンドを発行した要素を含むダイアログが閉じられます。

        <Button Width="75"
                Margin="10"
                HorizontalAlignment="Center"
                VerticalAlignment="Bottom"
                Command="ApplicationCommands.Close"
                Content="Close" />

コードビハインドから閉じる
ユーザーコントロールを表示している場合、ボタンクリック時にダイアログを閉じたい、というような場合には、以下のようにCloseメソッドを呼び出します。 LightyでFrameworkElementにCloseという拡張メソッドを定義しているので、using SourceChord.Lighty;名前空間を設定していれば、Close()拡張メソッドでダイアログを閉じることができます。

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Lightyで定義している、ダイアログクローズ用の拡張メソッド
            // ※「using SourceChord.Lighty;」が必要
            this.Close();
        }

背景領域のクリック
デフォルトの状態だと、LightBox表示の背景部分をクリックすると、すべてのダイアログ表示を閉じるようになっています。
(後述するCloseOnClickBackgroundプロパティでカスタマイズ可能です。)

MVVMとの連携

MVVM的にVMから表示する方法とかは?って話ですが、その手のMVVMとの連携は用意していません。
ダイアログ表示のような動作は完全にView側の責務だと思うので、VMからライトボックス表示をさせる方法を提供するのは、レイヤーが異なる話になるかな、と。

通常のWindowをShow/ShowDialogするのと同じ感じで表示できるので、MVVMな開発スタイルと併用したい場合は、自分が使ってるMVVMフレームワークの作法に合わせて、LightyのShow/ShowDialog/ShowAsyncメソッドを呼び出してもらえばよいかと思います。

応用編

表示のカスタマイズ

LightyではXAMLの機能をフルに使って、表示方法を自由にカスタマイズできます。

表示方法は、WindowのリソースとしてLightBoxクラスのデフォルトスタイルを設定することでカスタマイズできます。
(Windowのリソースで定義すれば、ウィンドウ単位でのスタイルの設定になり、App.xamlで定義すればアプリケーション全体の設定として定義することができます。)

Property Description
Template LightBoxとして表示する領域全体の定義。LightBoxの背景部分などはここでカスタマイズします。
ItemsPanel 各ダイアログを格納するItemsPanelを設定します。StackPanelなどを併用することで、複数ダイアログを並べて表示することもできます。
ItemContainerStyle 各ダイアログを格納するコンテナのスタイルを設定します。ダイアログに共通のデザインを適用したい場合などは、ここで設定できます。
CloseOnClickBackground LightBoxの背景領域をクリックした際に、すべてのダイアログを閉じるか否かを設定します。(デフォルトはTrue)

こんな風にカスタマイズできます。

    <Window.Resources>
        <Style TargetType="{x:Type lighty:LightBox}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Grid Background="#88F5F5DC">
                            <ItemsPresenter />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </style>
        <Setter Property="CloseOnClickBackground" Value="False" />
    </Window.Resources>

f:id:minami_SC:20160403174920p:plain:w300

ビルトイン スタイル

いくつかのデザインやアニメーションを、ライブラリ側で標準で定義しています。
これらのビルトインスタイルを使えばお手軽に表示方法をカスタマイズできます。

準備
ビルトインスタイルを使うには、まずApp.xamlで以下のリソースディクショナリを読み込んでおきます。

<ResourceDictionary Source="pack://application:,,,/Lighty;component/Styles.xaml" />

するとLightBoxのデフォルトスタイルを設定する際に、StaticResourceでビルトインスタイルを指定できるようになります。

    <Window.Resources>
        <Style TargetType="{x:Type lighty:LightBox}">
            <Setter Property="Template" Value="{StaticResource DarkGlassTemplate}" />
            <Setter Property="ItemsPanel" Value="{StaticResource HorizontalPanel}" />
            <Setter Property="ItemContainerStyle" Value="{StaticResource PhotoCardStyle}" />
        </Style>
    </Window.Resources>

詳細は割愛しますが、現時点では以下のようなスタイル類を定義しています。

Property Name
Template DarkBackgroundTemplate
LightBackgroundTemplate
DarkGlassTemplate
LightGlassTemplate
ItemsPanel HorizontalPanel
VerticalPanel
ItemContainerStyle ClosableContainerStyle
PhotoCardStyle
Animations FadeInAnimation
FadeOutAnimation
ZoomInAnimation
ZoomOutAnimation

※ビルトインスタイルは、今後もう少し手を入れたり追加したりしていこうかと思ってます。

個人的におすすめのスタイルは、「DarkGlassTemplate/LightGlassTemplate」
iOS風のポップアップ表示みたいに背景に強めのブラーをかけてからダイアログ表示をします。
f:id:minami_SC:20160403175016p:plain

以前↓で書いた方法を併用して、ウィンドウ全体に強烈なぼかしをかけても、パフォーマンスが落ちないように工夫してます。

アニメーション

LightBoxを表示したり閉じたりする際のアニメーションもカスタマイズ可能です。
以下のようなプロパティを用意しています。

Property Description
InitializeStoryboard LightBoxの背景部分を最初に表示する際のアニメーション
DisposeStoryboard LightBoxの背景部分を消す際のアニメーション
OpenStoryboard LightBoxとして各ダイアログを表示する際のアニメーション
CloseStoryboard LightBoxとして各ダイアログを閉じる際のアニメーション

こんな風に設定をします。

    <Window.Resources>
        <Style TargetType="{x:Type lighty:LightBox}">
            <Setter Property="InitializeStoryboard" Value="{StaticResource ZoomInAnimation}" />
            <Setter Property="DisposeStoryboard" Value="{StaticResource ZoomOutAnimation}" />
            <Setter Property="OpenStoryboard" Value="{StaticResource ZoomInAnimation}" />
            <Setter Property="CloseStoryboard" Value="{StaticResource ZoomOutAnimation}" />
        </Style>
    </Window.Resources>
アニメーションの並列実行

デフォルトでは以下のアニメーションは、シーケンシャルに順次実行します。

  • InitializeStoryboard⇒OpenStoryboard
  • CloseStoryboard⇒DisposeStoryboard

(背景を暗くして、そのアニメーションが終わったら、各ダイアログを表示するためのアニメーションを開始する、、など。)

これらを並列で同時に実行したい場合には以下のプロパティを設定します。

Property Description
IsParallelInitialize InitializeStoryboard & OpenStoryboardを並列に実行するか否かを指定します。(デフォルトはFalse)
IsParallelDispose CloseStoryboard & DisposeStoryboardを並列に実行するか否かを指定します。(デフォルトはFalse)

複数ダイアログの同時表示

Lightyでは、複数のダイアログを同時表示することもできます。

以下のようにItemsPanelをStackPanelなどにしておき、Showメソッド複数回呼び出すと複数ダイアログの表示をすることができます。

    <Window.Resources>
        <Style TargetType="{x:Type lighty:LightBox}">
            <Setter Property="ItemsPanel" Value="{StaticResource HorizontalPanel}" />
        </Style>
    </Window.Resources>
        private async void button_Click(object sender, RoutedEventArgs e)
        {
            LightBox.Show(this, new SampleDialog());

            await Task.Delay(1000);
            LightBox.Show(this, new SampleDialog());

            await Task.Delay(1000);
            LightBox.Show(this, new SampleDialog());
        }

f:id:minami_SC:20160403175049p:plain:w300

その他

MahApps.Metroなどのライブラリと組み合わせて使うこともできます。
↓こんな感じ。
f:id:minami_SC:20160403175108p:plain:w300

こういうライブラリと組み合わせることで、お手軽にかっこいいUIを作れるのでは、と思います。