はじめようWindows 8世代のアプリ開発

第3回Windowsストア アプリで設定チャームと連携

はじめに

前回からだいぶ間が空いてしまいましたが、Windowsストア アプリ開発の2回目です。

今回は、設定チャームから表示できる設定ウィンドウ図1をアプリで使えるようにしてみましょう。設定ウィンドウの実装はアプリの認定(審査)にも関わる重要な機能ですので押さえておきましょう(なぜ重要かは後述します⁠⁠。

図1 SkyDriveの設定ウィンドウ
図1 SkyDriveの設定ウィンドウ

また、前回の内容の発展としてSkyDriveなどにアクセスするためのMicrosoftアカウントのサインイン機能を設定ウィンドウで実装します。

設定ウィンドウと設定ポップアップ

Windowsストア アプリでは、アプリの全般的な設定を、設定チャームからアクセスする設定ウィンドウというWindowsで用意されている共通のUIから行えます。

設定ウィンドウ

図1はSkyDriveの設定ウィンドウです。

ウィンドウ上部にアプリの設定項目が表示されています。Windowsストアからインストールしたアプリは、⁠アクセス許可」「評価とレビュー」が既定で用意されています。それ以外の「オプション⁠⁠、⁠バージョン情報⁠⁠、⁠ヘルプ」の項目はSkyDriveが用意したものです。

ユーザーが設定ウィンドウを開いた時、アプリの設定を表示するようアプリ側でコーディングします。

設定ウィンドウ下部は、PCに関する設定が表示されます。

設定ポップアップ

設定ウィンドウに表示されるアプリの設定項目は、エントリポイントとして使い、アプリ画面の表示切り替えや設定ポップアップを表示して、より詳細な設定画面を表示します。

SkyDriveの「オプション」は、図2のように設定ウィンドウから設定ポップアップが開きます。

図2 SkyDriveのオプションの設定ポップアップ
図2 SkyDriveのオプションの設定ポップアップ

設定ウィンドウや設定ポップアップに、どのような項目を用意すべきかなど詳しくは、アプリ設定のガイドライン (Windows ストア アプリ) (Windows)を参照してみてください。

設定ウィンドウが重要な理由

冒頭で触れた設定ウィンドウがアプリ認定に重要な理由ですが、ほぼすべてのアプリはプライバシーポリシーを用意する必要があります[1]⁠。そして、プライバシーポリシーは、アプリの設定からアクセスできるようにする必要があります。

このため、設定ウィンドウでアプリのプライバシーポリシーにアクセスできるようにすることは、アプリ開発でほぼ必須の作業となっています。

ちなみに、プライバシーポリシーを記載したWebページも必要です。アプリ登録時にプライバシーポリシーのURLを指定します。

設定ウィンドウにアプリ設定項目の追加

それでは実際にコーディングしていきましょう。

プロジェクトの作成

Visual Studioでプロジェクトを作成します。今回も前回と同じく、C#の一番シンプルな「新しいアプリケーション(XAML⁠⁠」を選択します図3⁠。図は、Visual Studio 2012 Express for Windows 8の画面です。前回のプロジェクトを引き続き使用しても構いません。

図3 プロジェクトの作成
図3 プロジェクトの作成

アプリの設定項目の追加

さっそく、設定ウィンドウにアプリ独自の項目を追加します。

設定ウィンドウのオブジェクトSettingsPaneオブジェクト)CommandsRequestedイベントで項目を追加します。ここでは、ユーザーがアプリの起動した時にその処理を書きます。App.xaml.csのOnLaunchedメソッド内に次のようにコードを追記しましょう。

// using Windows.UI.ApplicationSettings; の追加が必要です

bool EventRegistered;
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
    if (!this.EventRegistered)
    {
        SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested;
        this.EventRegistered = true;
    }

    // (省略)
}

OnCommandsRequestedの内容は次のようになります。ここで実際に項目を設定ウィンドウに追加しています。項目となるSettingsCommandオブジェクトを生成してApplicationCommands配列に追加します。

ここでSettingsCommandオブジェクト生成時に指定している値は、⁠policy⁠というコマンドのID(任意に決めれます)と設定ウィンドウに表示される「プライバシーポリシー」の文字、そして項目を選択したときに呼ばれるハンドラーです。

// using Windows.UI.Popups; の追加が必要です
void OnCommandsRequested(SettingsPane settingsPane, SettingsPaneCommandsRequestedEventArgs eventArgs)
{
    var handler = new UICommandInvokedHandler(OnSettingsCommand);

    var policyCommand = new SettingsCommand("policy", "プライバシーポリシー", handler);
    eventArgs.Request.ApplicationCommands.Add(policyCommand);
}

続いて、設定項目が選択された時の処理を書きます。次のようにSettingsCommandオブジェクトに指定したIDを使って処理を分岐できます(今はひとつしか項目を追加していませんが⁠⁠。

ここではWebページを表示するようにしています。

void OnSettingsCommand(IUICommand command)
{
    var settingsCommand = (SettingsCommand)command;
    switch (settingsCommand.Id.ToString())
    {
        case "policy":
            ShowPrivacyPolicy();
            break;
    }
}


// プライバシーポリシーの表示
// using Windows.System; の追加が必要です
async void ShowPrivacyPolicy()
{
    Uri uri = new Uri("http://example.jp/privacypolicy");
    await Launcher.LaunchUriAsync(uri);
}

ここまでを実行してみましょう。設定チャームから設定ウィンドウを開くと追加した項目が表示されたでしょうか図4⁠。

図4 設定ウィンドウにプライバシーポリシーの追加
図4 設定ウィンドウにプライバシーポリシーの追加

以上で、Webページを表示するような簡単な設定は作れるようになりました。

設定ポップアップの表示

続いて、設定ポップアップを表示してみましょう。設定ポップアップとなるページをプロジェクトに追加します。

メニューの「プロジェクト」ー「新しい項目の追加」から空白のページを選びます。ここでは、SettingsFlyout.xamlという名前にしています。

図5 空白のページの追加
図5 空白のページの追加

XAMLの内容を変更します。ひとまず動作がわかるよう文字を表示するだけの簡単な内容にしておきます。

SettingsFlyout.xaml
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock>サンプル</TextBlock>
</Grid>

項目の追加

設定ポップを表示するための項目を設定ウィンドウに追加しましょう。OnCommandsRequestedメソッドにコードを追記します[2]⁠。

void OnCommandsRequested(SettingsPane settingsPane, SettingsPaneCommandsRequestedEventArgs eventArgs)
{
    var handler = new UICommandInvokedHandler(OnSettingsCommand);

    var accountCommand = new SettingsCommand("account", "アカウント", handler);
    eventArgs.Request.ApplicationCommands.Add(accountCommand);

    var policyCommand = new SettingsCommand("policy", "プライバシーポリシー", handler);
    eventArgs.Request.ApplicationCommands.Add(policyCommand);
}

OnSettingsCommandメソッドは、次のように変更し処理を分岐します。

void OnSettingsCommand(IUICommand command)
{
    var settingsCommand = (SettingsCommand)command;
    switch (settingsCommand.Id.ToString())
    {
        case "policy":
            ShowHelp();
            break;
        case "account":
            ShowSettingsFlyout();
            break;
    }
}

設定ポップアップ処理

ShowSettingsFlyoutメソッドに設定ポップアップを表示するコードを記述します。

手順は、設定ポップアップとなるPopupオブジェクトを生成し、ChildプロパティにSettingsFlyoutオブジェクトを指定します。位置や大きさ、アニメーションの設定など細々と行っていますが、最後にPopup. IsOpenプロパティにtrueを指定すれば設定ポップアップが表示されます。

private Popup popup;
void ShowSettingsFlyout()
{
    var bounds = Window.Current.Bounds;
    var width = 646; // 横幅は UI ガイドラインによると 346 または 646

    // Popupオブジェクトの生成
    popup = new Popup();
    popup.IsLightDismissEnabled = true;
    popup.Width = width;
    popup.Height = bounds.Height;

    // アニメーション設定
    popup.ChildTransitions = new Windows.UI.Xaml.Media.Animation.TransitionCollection();
    popup.ChildTransitions.Add(new Windows.UI.Xaml.Media.Animation.PaneThemeTransition()
    {
        Edge = (SettingsPane.Edge == SettingsEdgeLocation.Right) ?
               EdgeTransitionLocation.Right :
               EdgeTransitionLocation.Left
    });

    // SettingsFlyout オブジェクトを生成し、Popup.Child プロパティに指定
    var pane = new SettingsFlyout();
    pane.Width = width;
    pane.Height = bounds.Height;
    popup.Child = pane;

    // ポップアップの位置を調整し、表示
    popup.SetValue(Canvas.LeftProperty, SettingsPane.Edge == SettingsEdgeLocation.Right ? (bounds.Width - width) : 0);
    popup.SetValue(Canvas.TopProperty, 0);
    popup.IsOpen = true;
}

以上で、設定ポップアップを表示するだけのところまではできました。ここまで実行して動作を確認してみましょう。

アカウント設定ポップアップの作成

さて、次は図6のようにもう少しいい感じの見た目に変更します。ポップアップの上部にヘッダーを用意し、戻るボタンを実装します。

図6 アカウント設定ポップアップ
図6 アカウント設定ポップアップ

例としてMicrosoftアカウントへサインイン・サインアウトを行うアカウント設定を作ります。Microsoftアカウントでのサインインについては前回の内容を参照してください。

サインインUXのガイドライン

ここでサインインに関するガイドラインを確認しましょう。

SkyDriveなどMicrosoftアカウントを使ってデータにアクセスするWindowsストア アプリは、ガイドラインに従うと設定ウィンドウに「アカウント」および「プライバシーポリシー」に関する項目を設ける必要があります。⁠アカウント」設定では、Microsoftアカウントへのサインイン・サインアウト機能を提供します。

サインインするタイミングは、アプリによって異なります。サインインしなければ機能しないアプリの場合は、起動時が適切です。

サインインしなくても使えるアプリの場合は、必要になった時点でサインインを行います。また、アカウント設定からもサインインを行えるようにします。

詳しくは、Microsoft アカウントのサインイン エクスペリエンスのガイドライン (JavaScript と HTML を使った Windows ストア アプリ) (Windows)を参照してください。

設定ポップアップのデザインと動作

それでは、作っていきましょう。まず設定ウィンドウのXAMLを変更します。ポップアップの左上に戻るボタンから設定ウィンドウに戻れるよう動作も実装します。

見た目の部分は、少し長いコードですがXAMLの<Page>要素内を以下のように変更します。設定ポップアップの上部のヘッダー部分とサインイン用のUIを記述しています。コードのほとんどは戻るボタンの定義です。

SettingsFlyout.xaml
<UserControl.Resources>
<Style x:Key="SettingsBackButtonStyle" TargetType="Button">
    <Setter Property="MinWidth" Value="0"/>
    <Setter Property="FontFamily" Value="Segoe UI Symbol"/>
    <Setter Property="FontWeight" Value="Normal"/>
    <Setter Property="FontSize" Value="26.66667"/>
    <Setter Property="AutomationProperties.AutomationId" Value="BackButton"/>
    <Setter Property="AutomationProperties.Name" Value="Back"/>
    <Setter Property="AutomationProperties.ItemType" Value="Navigation Button"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid x:Name="RootGrid" Width="30" Height="30">
                    <Grid Margin="-6,-6,0,0">
                        <TextBlock x:Name="BackgroundGlyph" Text="&#xE0D4;" Foreground="Transparent"/>
                        <TextBlock x:Name="NormalGlyph" Text="{StaticResource BackButtonSnappedGlyph}" Foreground="White"/>
                        <TextBlock x:Name="ArrowGlyph" Text="&#xE0C4;" Foreground="#00b2f0" Opacity="0"/>
                    </Grid>
                    <Rectangle
                        x:Name="FocusVisualWhite"
                        IsHitTestVisible="False"
                        Stroke="{StaticResource FocusVisualWhiteStrokeThemeBrush}" 
                        StrokeEndLineCap="Square"
                        StrokeDashArray="1,1"
                        Opacity="0"
                        StrokeDashOffset="1.5"
                        />
                    <Rectangle
                        x:Name="FocusVisualBlack"
                        IsHitTestVisible="False"
                        Stroke="{StaticResource FocusVisualBlackStrokeThemeBrush}" 
                        StrokeEndLineCap="Square"
                        StrokeDashArray="1,1"
                        Opacity="0"
                        StrokeDashOffset="0.5"
                        />
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundGlyph" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource BackButtonPointerOverBackgroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundGlyph" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="White"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <DoubleAnimation
                                    Storyboard.TargetName="ArrowGlyph"
                                    Storyboard.TargetProperty="Opacity"
                                    To="1"
                                    Duration="0"/>
                                    <DoubleAnimation
                                    Storyboard.TargetName="NormalGlyph"
                                    Storyboard.TargetProperty="Opacity"
                                    To="0"
                                    Duration="0"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <DoubleAnimation
                                    Storyboard.TargetName="FocusVisualWhite"
                                    Storyboard.TargetProperty="Opacity"
                                    To="1"
                                    Duration="0"/>
                                    <DoubleAnimation
                                    Storyboard.TargetName="FocusVisualBlack"
                                    Storyboard.TargetProperty="Opacity"
                                    To="1"
                                    Duration="0"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused" />
                            <VisualState x:Name="PointerFocused" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
</UserControl.Resources>

<Border BorderBrush="#00b2f0" BorderThickness="1,0,0,0">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="80"/>
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid Background="#00b2f0" Grid.Row="0">
        <StackPanel Orientation="Horizontal" Margin="40 32 17 13">
            <Button Click="BackButton_Click"  Style="{StaticResource SettingsBackButtonStyle}"/>
            <TextBlock Margin="7 2 0 0"  FontSize="26" Text="アカウント" Foreground="White" />
        </StackPanel>
    </Grid>
    <Grid Margin="40,33,40,39" VerticalAlignment="Top" Grid.Row="1">
        <StackPanel x:Name="FlyoutContent">
            <TextBlock x:Name="MessageTextBlock" Margin="0 24 0 0" Text="サインインしていません" TextWrapping="Wrap" Style="{StaticResource BasicTextStyle}" />
            <Button x:Name="SignInButton" Margin="0 24 0 0" Content="サインイン" Click="SignInButton_Click" IsEnabled="False" />
            <Button x:Name="SignOutButton" Margin="0 24 0 0" Content="サインアウト" Click="SignOutButton_Click" Visibility="Collapsed" />
        </StackPanel>
    </Grid>
</Grid>
</Border>

次にプロジェクトのデフォルトでは、黒い背景に白い文字の濃色のテーマカラーとなっていますが、淡色のテーマカラーに変更しましょう。App.xamlのApplication要素に属性RequestedTheme="Light"を次のように追加します(x:Class属性部分はプロジェクトによって異なります⁠⁠。

App.xaml
<Application
    x:Class="GihyoSampleApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    RequestedTheme="Light">

加えて、本題とは関係のない注意点です。もし、CommonフォルダーにあるStandardStyles.xamlに下記のコードがある場合はマウスMouseに修正します。これは日本語テンプレートのバグの修正です。

StandardStyles.xaml
<ToolTipService.Placement>マウス</ToolTipService.Placement>

次に、戻るボタンの動作を実装しましょう。戻るボタンのClickイベントの処理です。設定ポップアップを非表示にし、設定ウィンドウを表示します。

SettingsFlyout.xaml.cs
// using Windows.UI.ApplicationSettings; の追加が必要です
private void BackButton_Click(object sender, RoutedEventArgs e)
{
    // ポップアップの非表示
    Popup parent = this.Parent as Popup;
    if (parent != null)
    {
        parent.IsOpen = false;
    }

    // アプリがスナップされていない場合にのみ設定ウィンドウを表示
    if (Windows.UI.ViewManagement.ApplicationView.Value != Windows.UI.ViewManagement.ApplicationViewState.Snapped)
    {                
        SettingsPane.Show();
    }
}

以上で、より実用的な設定ポップアップの見た目と動作ができました。

サインイン・サインアウト処理

最後にサインイン・サインアウト処理を実装して完了です。ここからはLive Connect APIの内容です。実装方法は多様ですが、ここでは次のようにしました。

まず、アプリのメインのコードと設定ポップアップとで認証状態を共有できるようApp.xaml.csにLiveAuthClientのstatic変数を追加します[3]⁠。

App.xaml.cs
static public LiveAuthClient AuthClient;

設定ポップアップのコードは次のように編集します。サインインとサインアウト機能を実装し、サインイン状態のときはユーザー名を取得し表示します。

SettingsFlyout.xaml.cs
// using Microsoft.Live; の追加が必要です

// コンストラクタを以下のように修正します
public SettingsFlyout()
{
    this.InitializeComponent();

    // 初期表示
    if (App.AuthClient == null || App.AuthClient.Session == null)
    {
        ShowSignedOutUI();
    }
    else
    {
        // ポップアップ表示時にサインインしている可能性がある場合、情報の取得を試行して表示を変更する
        ShowSignedInUI();
    }
}

// サインインボタン クリック時
private async void SignInButton_Click(object sender, RoutedEventArgs e)
{
    if (App.AuthClient == null)
    {
        // LiveAuthClient オブジェクトが生成されていない場合
        App.AuthClient = new LiveAuthClient();
    }

    this.SignInButton.IsEnabled = false;
    try
    {
        // サインイン
        var authResult = await App.AuthClient.LoginAsync(new string[] { "wl.basic" });
        if (authResult.Status == LiveConnectSessionStatus.Connected)
        {
            ShowSignedInUI();
        }
        else
        {
            this.SignInButton.IsEnabled = true;
        }
    }
    catch (LiveConnectException)
    {
        ShowSignedOutUI();
    }
}

// サインアウトボタン クリック時
private void SignOutButton_Click(object sender, RoutedEventArgs e)
{
    if (App.AuthClient != null)
    {
        if (!App.AuthClient.CanLogout)
        {
            return;
        }
        App.AuthClient.Logout();
        App.AuthClient = null;
    }
    ShowSignedOutUI();
}

// サインアウトしている状態の時の UI 表示
private void ShowSignedOutUI()
{
    this.MessageTextBlock.Text = "サインインしていません";
    this.SignOutButton.Visibility = Visibility.Collapsed;
    this.SignInButton.Visibility = Visibility.Visible;
    this.SignInButton.IsEnabled = true;
}

// サインインしている状態の時の UI 表示
private async void ShowSignedInUI()
{
    try
    {
        var connectClient = new LiveConnectClient(App.AuthClient.Session);

        // サインインしているユーザー名の取得
        LiveOperationResult opMeResult = await connectClient.GetAsync("me");
        dynamic meResult = opMeResult.Result;

        this.MessageTextBlock.Text = "こんにちは! " + meResult.name;
        this.SignInButton.Visibility = Visibility.Collapsed;

        // サインアウト可能な場合にのみサインアウトボタン表示
        if (App.AuthClient.CanLogout)
        {
            this.SignOutButton.Visibility = Visibility.Visible;
            this.SignOutButton.IsEnabled = true;
        }
        else
        {
            this.SignOutButton.Visibility = Visibility.Collapsed;
        }
    }
    catch (LiveConnectException)
    {
        ShowSignedOutUI();
    }
}

WindowsにMicrosoftアカウントでサインインしている場合、サインアウトできません。ローカルアカウントの場合にのみサインアウトボタンが表示されます。

おわりに

今回はここまでです。いかがでしたか。Windowsストア アプリで設定チャームと連携方法を紹介しました。

Visual Studioのプロジェクトテンプレートから作成したアプリには設定チャームの実装があらかじめ記述されていませんが、アプリ認定に重要な内容です。ここで、もう少し実践的なサンプルを紹介しておきます。

日本マイクロソフトが提供しているWindows 8アプリ開発体験テンプレートは、設定ウィンドウ部分が実装されています。サンプルコードを利用してアプリ開発ができます。

また、設定ウィンドウ・設定ポップアップのみのサンプルコードが、デベロッパーセンターで提供されています。今回の内容はこのサンプルをよりシンプルにした形で紹介しています。

次回もまたWindowsストア アプリの開発について紹介する予定です。

おすすめ記事

記事・ニュース一覧