SourceChord

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

UWPのPopup/Flyout/Dialog系のコントロール

UWPで何らかのポップアップのようなUI要素を表示するコントロールの使い方をまとめてみました。

Tooltip/Popup系

まずは、そんなに使用頻度高くないかもしれないけど、ちょっとしたコントロールから。

ToolTip

これは、WPFなどでもあった要素ですね。カーソル乗ったときにポコっと表示してくれるもの。

        <Button Margin="10"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Content="Button"
                ToolTipService.ToolTip="ボタンの説明" />

f:id:minami_SC:20150919231703p:plain
使い方も、WPFのものとほぼ同じ感じ。
Buttonなどの各コントロールに直接ToolTipプロパティは用意されてないので、必ずToolTipServiceクラスの添付プロパティを使って設定するのが違いかな。

この要素は積極的に使うようなコントロールではないです。
ToolTipやFlyout、MessageDialogなど、ほかのコントロールで事足りるときにはそちらのコントロールを使うべきですね。
てことで省略。

PopupMenu

f:id:minami_SC:20150919231807p:plain
こんな風に、右クリック時に出てくるメニューを作ったりするのに使ってたコントロール。
Win8系ではフライアウト メニューの表示などでも使ってたみたい。
ただし、このコントロールはXAML上で定義できず、コードビハインドで表示内容を定義しなければいけないので、使い勝手は微妙。。。
Win8.1でFlyoutMenuというコントロールが追加されたので、基本的にはFlyoutMenuを使うことになるかと思います。

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var menu = new PopupMenu();
            menu.Commands.Add(new UICommand("menu1"));
            menu.Commands.Add(new UICommand("menu2"));

            var selected = await menu.ShowForSelectionAsync(GetElementRect((FrameworkElement)sender));
        }

        public static Rect GetElementRect(FrameworkElement element)
        {
            var buttonTransform = element.TransformToVisual(null);
            var point = buttonTransform.TransformPoint(new Point());
            return new Rect(point, new Size(element.ActualWidth, element.ActualHeight));
        }

Flyout系

Win8.1からあるFlyout系のコントロール。
表示する内容をXAML上で定義できるので、PopupMenuなどより使い勝手がいいです。
任意のXAML要素をポップアップ表示したい場合はFlyoutコントロール。メニュー的な表示をしたい場合にはMenuFlyoutでだいたいの用途には事足りそうです。

Flyout/MenuFlyout

まとめて使ってみます。

ボタン押下時にFlyoutを表示する。

ButtonなどのFlyoutプロパティを持つコントロールでは、このプロパティにFlyout系コントロールを設定するだけで表示することができます。
ボタンを押したらメニューを出す、というような場合にはこの方法を使います。

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Margin="10"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Content="Flyoutの表示">
                <Button.Flyout>
                    <Flyout>
                        <StackPanel>
                            <TextBlock Text="hoge" />
                            <Button Content="ボタン"/>
                        </StackPanel>
                    </Flyout>
                </Button.Flyout>
            </Button>
            <Button Margin="10"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Content="FlyoutMenuの表示">
                <Button.Flyout>
                    <MenuFlyout>
                        <MenuFlyoutItem Text="メニュー1" />
                    </MenuFlyout>
                </Button.Flyout>
            </Button>
        </StackPanel>

f:id:minami_SC:20150919231820p:plain
f:id:minami_SC:20150919231831p:plain

任意のタイミングでFlyoutを表示する

続いて、ボタン押下のタイミングではなく、その他任意のタイミングでフライアウトを表示したい場合の実装方法です。

FlyoutBase.AttachedFlyoutという添付プロパティがあるので、フライアウトを出したいコントロールにこの添付プロパティを設定し、この中でFlyoutの定義をします。
そして、コードビハインドから以下のように呼び出します。

        <Rectangle Width="100"
                   Height="50"
                   Margin="75"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Top"
                   Fill="Gray" DoubleTapped="Rectangle_DoubleTapped">
            <FlyoutBase.AttachedFlyout>
                <MenuFlyout>
                    <MenuFlyoutItem Text="メニュー1" />
                    <MenuFlyoutItem Text="メニュー2" />
                    <MenuFlyoutItem Text="メニュー3" />
                </MenuFlyout>
            </FlyoutBase.AttachedFlyout>
        </Rectangle>
        private void Rectangle_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
        {
            Flyout.ShowAttachedFlyout(sender as FrameworkElement);
        }

↓こんな使い方するメリットはないかと思いますが、ダブルタップするとフライアウトを表示します。
f:id:minami_SC:20150919232015p:plain

Flyoutコントロールに名前を付けて直接制御

Flyoutコントロールに名前を付けておけば、コードビハインドから直接操作することができます。
以下のように、ShowAt/Hideメソッドで表示/非表示を切り替えられます。


(Flyoutは、どこか別の要素をクリックしてフォーカス外れると自動で消えるので、この例ではわざわざHideメソッド用意する必要ないですが。)

        <StackPanel Margin="75" Orientation="Horizontal">
            <Rectangle x:Name="rect1"
                       Width="100"
                       Height="50"
                       Fill="Gray">
                <FlyoutBase.AttachedFlyout>
                    <MenuFlyout x:Name="flyout1">
                        <MenuFlyoutItem Text="メニュー1" />
                        <MenuFlyoutItem Text="メニュー2" />
                        <MenuFlyoutItem Text="メニュー3" />
                    </MenuFlyout>
                </FlyoutBase.AttachedFlyout>
            </Rectangle>
            <Button Margin="10"
                    Click="ShowFlyout"
                    Content="Show" />
            <Button Margin="10"
                    Click="HideFlyout"
                    Content="Hide" />
        </StackPanel>
        private void ShowFlyout(object sender, RoutedEventArgs e)
        {
            this.flyout1.ShowAt(this.rect1);
        }

        private void HideFlyout(object sender, RoutedEventArgs e)
        {
            this.flyout1.Hide();
        }

f:id:minami_SC:20150919232146p:plain


Dialog系

最後はダイアログ系の表示について。

MessageDialog

まずはダイアログの基本のMessageDialogクラス。
WPFでMessageBox使ってたような感覚で、こんな風にコードビハインドから呼び出したりできます。
(MVVMとかを意識してコーディングするときには、こういうのを安易に仕込むとビューとロジックが入り乱れるきっかけになっちゃったりするんで注意は必要かと思いますが。)

以下のように、インスタンスを作ってから、ShowAsyncメソッドで表示します。
UIの応答を待つメソッドになるので、Asyncな呼び出し方だけ用意されてるあたりが今風ですね。

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            var dlg = new MessageDialog("MessageDialogのサンプル", "タイトル");
            await dlg.ShowAsync();
        }

f:id:minami_SC:20150919232224p:plain

ボタンを追加する

CommandsプロパティにUICommandのインスタンスを追加していくと、ダイアログに表示するボタンをカスタマイズすることができます。

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            var dlg = new MessageDialog("MessageDialogのサンプル", "タイトル");
            dlg.Commands.Add(new UICommand("はい"));
            dlg.Commands.Add(new UICommand("いいえ"));
            await dlg.ShowAsync();
        }

f:id:minami_SC:20150919232351p:plain

デフォルト、キャンセル用のボタンを設定する

DefaultCommandIndex/CancelCommandIndexというプロパティで、ダイアログのデフォルト/キャンセル用のボタン設定を行うことができます。
ボタンは、Commandプロパティに追加した順に0から始まるインデックス値で指定します。

DefaultCommandIndexに設定すると、下のキャプチャ画像のようにボタンの色が変わります。
CancelCommandIndexを設定すると、ダイアログ表示時にESCキーを押したりしたときに、このボタンが選択されるようになります。

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            var dlg = new MessageDialog("MessageDialogのサンプル", "タイトル");
            dlg.Commands.Add(new UICommand("ボタン1"));
            dlg.Commands.Add(new UICommand("デフォルト用"));
            dlg.Commands.Add(new UICommand("キャンセル用"));
            dlg.DefaultCommandIndex = 1;
            dlg.CancelCommandIndex = 2;
            var result = await dlg.ShowAsync();
            System.Diagnostics.Debug.WriteLine(result.Label);
        }

f:id:minami_SC:20150919232426p:plain


ShowAsyncメソッドの戻り値

ダイアログ表示で使うShowAsyncメソッドは以下のような定義となっていて、戻り値として選択されたボタンのUIComamandのインスタンスが取得できるようになっています。

public IAsyncOperation<IUICommand> ShowAsync();

そこで、
UICommandのコンストラクタの第3引数には、コマンドのIDとしての値をobject型で任意の値を設定することができます。
ShowAsyncの戻り値でこのIDをチェックすれば、どちらのボタンが押されたかチェックできます。
以下の例では、IDにそれぞれtrue/falseを入れてチェックしてます。

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            var dlg = new MessageDialog("MessageDialogのサンプル", "タイトル");
            dlg.Commands.Add(new UICommand("はい", null, true));
            dlg.Commands.Add(new UICommand("いいえ", null, false));
            var selectedCommand = await dlg.ShowAsync();
            var result = (bool)selectedCommand.Id;
            if(result)
            {
                // 「はい」が選択された場合の動作
                System.Diagnostics.Debug.WriteLine(selectedCommand.Label);
            }
        }

(↓のサンプルでは、デバッグ出力にログを吐いてるだけなので、実行時には特に変化ありませんが。。。)

各ボタン押下時のイベント処理を定義する

UICommandのコンストラクタでは、コマンドが選択されたときの処理を定義することもできます。

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            var dlg = new MessageDialog("MessageDialogのサンプル", "タイトル");
            dlg.Commands.Add(new UICommand("はい", (cmd) => { System.Diagnostics.Debug.WriteLine("はい"); }));
            dlg.Commands.Add(new UICommand("いいえ", (cmd) => { System.Diagnostics.Debug.WriteLine("いいえ"); }));
            await dlg.ShowAsync();
        }
MessageDialogのプロパティまとめ

MessageDialogで使う主なプロパティは以下の通り。

プロパティ名 内容
Title ダイアログのタイトルを設定。コンストラクタで指定するものと同じ
Content ダイアログの本文を設定。コンストラクタで指定するものと同じ
Options ダイアログボックスのオプションをMessageDialogOptions型で指定。あんま使わなそう。
Commands ボタンを追加したい場合には、ここにUICommandのインスタンスをAddしていく。
DefaultCommandIndex デフォルトのボタンにする要素をインデックス値で指定
CancelCommandIndex キャンセル用のボタンにする要素をインデックス値で指定

これらを設定してからShowAsyncで表示、ということだけ覚えれば、なんとなく使えるかと思います。

ContentDialog

ダイアログ系その2
こちらはダイアログの描画内容をカスタマイズできるタイプのものです。
ダイアログの表示内容をXAMLで定義できるので、複雑な情報を表示したり、いろんなUIコントロールを張り付けて置いたりと、いろいろ使えます。

表示内容は、以下の二通りの方法で定義することができます。

  1. 呼び出し元のページ上に直接定義
  2. 別ファイルに独立したXAMLファイルとして定義
元のページ上に定義

まずは、表示内容を呼び出し元のPage上に定義する方法から。
以下のようにPage内に直接ContentDialog要素を定義します。
ですが、このコントロールは普段は表示されておらず、ContentDialogクラスのShowAsyncメソッドを呼び出すことで表示されます。

・MainWindow.xaml

<Page x:Class="LayoutTest.MainPage"
      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:local="using:LayoutTest"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ContentDialog x:Name="dlg1"
                       Title="タイトル"
                       IsPrimaryButtonEnabled="True"
                       IsSecondaryButtonEnabled="True"
                       PrimaryButtonText="OK"
                       SecondaryButtonText="Cancel">
            <Grid HorizontalAlignment="Stretch" Width="Auto">
                <TextBlock Text="ContentDialogのテスト" />
            </Grid>
        </ContentDialog>

        <Button Margin="10"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Click="Button_Click_1"
                Content="ダイアログを表示" />
    </Grid>
</Page>

・MainWindow.xaml.cs

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            await this.dlg1.ShowAsync();
        }

f:id:minami_SC:20150919232545p:plain:w450


また、このShowAsyncメソッドでは、ダイアログでどのボタンを押したか、という情報をContentDialogResult型で取得できます。
以下のようにして、ダイアログでどのボタンが押されたかを判断することができます。

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            var result = await this.dlg1.ShowAsync();
            if (result == ContentDialogResult.Primary)
            {
                System.Diagnostics.Debug.WriteLine("Primary");
            }
            else if (result == ContentDialogResult.Secondary)
            {
                System.Diagnostics.Debug.WriteLine("Secondary");
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("None");
            }
        }
別のXAMLファイル上に定義

ContentDialogの表示内容は別ファイルに定義することもできます。
メニューの追加⇒新しい項目のダイアログで、「コンテンツダイアログ」を選びます。
f:id:minami_SC:20150919232824p:plain:w450
で、プロジェクトに追加されたXAMLファイルを編集します。

<ContentDialog
    x:Class="LayoutTest.SampleContentDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:LayoutTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="TITLE"
    PrimaryButtonText="Button1"
    SecondaryButtonText="Button2"
    PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
    SecondaryButtonClick="ContentDialog_SecondaryButtonClick">
    <Grid>
        <TextBlock Text="別ファイルに定義したContentDialog" />
    </Grid>
</ContentDialog>

表示方法は、ほぼ同じ感じですね。

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            var dlg = new SampleContentDialog();
            await dlg.ShowAsync();
        }

f:id:minami_SC:20150919232938p:plain:w450

ContentDialogのプロパティ

最後に主なプロパティを一通りまとめておきます。

プロパティ名 内容
Title ダイアログのタイトルを設定
FullSizeDesired Trueにすると、ダイアログをフルスクリーンで表示します
IsPrimaryButtonEnabled PrimaryButtonの表示/非表示を設定
PrimaryButtonText PrimaryButtonの表示文字列を設定
PrimaryButtonCommand PrimaryButton押下時に実行するコマンドを設定
PrimaryButtonCommandParameter ↑のコマンドに渡すパラメータを設定
IsSecondaryButtonEnabled SecondaryButtonの表示/非表示を設定
SecondaryButtonText SecondaryButtonの表示文字列を設定
SecondaryButtonCommand SecondaryButton押下時に実行するコマンドを設定
SecondaryButtonCommandParameter ↑のコマンドに渡すパラメータを設定
TitleTemplate Titleの表示方法をカスタマイズするためのDataTemplateを設定