しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

いつでも呼び出せるビジーインジケータを作る

ストアアプリには ProgressRing というインジケータ表示に便利なコントロールがあります

http://i.msdn.microsoft.com/dynimg/IC536137.png

ただし、このコントロールはアニメーション表示を行うだけなので、インジケータ表示中にユーザー操作をブロックする機能は持っていません

というわけでどこでも呼び出せる画面操作を抑止するビジーインジケータを作ってみました!

<UserControl x:Class="DialogApp.Controls.BusyIndicator"
             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:DialogApp.Controls"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             d:DesignHeight="768"
             d:DesignWidth="1366"
             mc:Ignorable="d">

    <Border Background="#CC000000">
        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <ProgressRing Width="92"
                          Height="92"
                          IsActive="True" />
            <TextBlock x:Name="Message_Part"
                       Grid.Row="1"
                       Margin="20,40,20,0"
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       FontSize="20"
                       Foreground="White"
                       TextWrapping="WrapWholeWords" />
        </Grid>
    </Border>
</UserControl>

XAML は背景の半透過カバーと ProgressRing、メッセージだけとシンプルです

コントロールのコードビハインドは次の通り

/// <summary>
/// ビジーインジケータ
/// </summary>
public sealed partial class BusyIndicator : UserControl
{
    #region Privates

    /// <summary>
    /// 現在のページ
    /// </summary>
    private Page currentPage;

    /// <summary>
    /// 自分が乗るPopup
    /// </summary>
    private Popup popup = new Popup();

    #endregion //Privates

    #region Message 依存関係プロパティ
    /// <summary>
    /// Message 依存関係プロパティ
    /// </summary>
    public static readonly DependencyProperty MessageProperty
        = DependencyProperty.Register(
        "Message",
        typeof(string),
        typeof(BusyIndicator),
        new PropertyMetadata(
            null,
            (s, e) =>
            {
                var control = s as BusyIndicator;
                if (control != null)
                {
                    control.OnMessageChanged();
                }
            }));

    /// <summary>
    /// Message 変更イベントハンドラ
    /// </summary>
    private void OnMessageChanged()
    {
        this.Message_Part.Text = this.Message;
    }

    /// <summary>
    /// Message
    /// </summary>
    public string Message
    {
        get { return (string)this.GetValue(MessageProperty); }
        set { this.SetValue(MessageProperty, value); }
    }
    #endregion //Message 依存関係プロパティ

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public BusyIndicator()
    {
        this.InitializeComponent();

        this.Width = Window.Current.Bounds.Width;
        this.Height = Window.Current.Bounds.Height;

        this.Loaded += this.OnLoaded;
        this.Unloaded += this.OnUnloaded;
    }

    /// <summary>
    /// 読み込み完了イベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private async void OnLoaded(object sender, RoutedEventArgs e)
    {
        await this.Dispatcher.RunAsync(
            CoreDispatcherPriority.Normal,
            () =>
            {
                var rootrame = Window.Current.Content as Frame;
                if (rootrame == null)
                {
                    return;
                }
                this.currentPage = rootrame.Content as Page;
                if (this.currentPage == null)
                {
                    return;
                }

                if (this.currentPage.TopAppBar != null)
                {
                    this.currentPage.TopAppBar.Opened += this.OnAppBarOpend;
                }
                if (this.currentPage.BottomAppBar != null)
                {
                    this.currentPage.BottomAppBar.Opened += this.OnAppBarOpend;
                }
            });
    }

    /// <summary>
    /// 表示
    /// </summary>
    public void Show()
    {
        this.popup.Child = this;
        this.popup.IsOpen = true;
        this.popup.Closed += this.OnClosed;
        Window.Current.SizeChanged += this.OnWindowSizeChanged;

        if (this.currentPage == null)
        {
            return;
        }

        if (this.currentPage.TopAppBar != null)
        {
            this.currentPage.TopAppBar.IsOpen = false;
        }
        if (this.currentPage.BottomAppBar != null)
        {
            this.currentPage.BottomAppBar.IsOpen = false;
        }
        this.currentPage.IsEnabled = false;
        this.currentPage.IsHitTestVisible = false;
    }

    /// <summary>
    /// ウィンドウサイズ変更イベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private async void OnWindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
    {
        await this.Dispatcher.RunAsync(
                        CoreDispatcherPriority.Normal,
                        () =>
                        {
                            this.Width = Window.Current.Bounds.Width;
                            this.Height = Window.Current.Bounds.Height;
                        });
    }

    /// <summary>
    /// 非表示
    /// </summary>
    public void Hide()
    {
        this.popup.IsOpen = false;

        if (this.currentPage == null)
        {
            return;
        }

        this.currentPage.IsEnabled = true;
        this.currentPage.IsHitTestVisible = true;
    }

    /// <summary>
    /// 非表示イベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnClosed(object sender, object e)
    {
        this.popup.Child = null;
        this.popup = null;
    }

    /// <summary>
    /// インスタンス破棄時の処理
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private async void OnUnloaded(object sender, RoutedEventArgs e)
    {
        this.Unloaded -= this.OnUnloaded;
        this.Loaded -= this.OnLoaded;

        this.popup.Closed -= this.OnClosed;
        this.popup.Child = null;
        this.popup = null;
        Window.Current.SizeChanged -= this.OnWindowSizeChanged;

        await this.Dispatcher.RunAsync(
            CoreDispatcherPriority.Normal,
            () =>
            {
                if (this.currentPage == null)
                {
                    return;
                }
                if (this.currentPage.TopAppBar != null)
                {
                    this.currentPage.TopAppBar.Opened -= this.OnAppBarOpend;
                }
                if (this.currentPage.BottomAppBar != null)
                {
                    this.currentPage.BottomAppBar.Opened -= this.OnAppBarOpend;
                }
                this.currentPage = null;
            });
    }

    /// <summary>
    /// アプリバー表示イベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnAppBarOpend(object sender, object e)
    {
        var appBar = sender as AppBar;
        if (appBar == null)
        {
            return;
        }

        // ビジー中は抑止する
        if (this.popup != null)
        {
            appBar.IsOpen = false;
        }
    }
}

アプリバーや背面の画面操作を抑止して Popup 上にインジケータをのせて表示しています

var indicator = new BusyIndicator()
{
    Message = "ただいま処理中です。しばらくお待ちください...",
};
indicator.Show();

こんなコードを書けば次のように表示されるはず・・・

f:id:matatabi_ux:20140403201858j:plain