ポップアップが表示されているかを調べるには?[Windows 8.1ストアアプリ開発]WinRT/Metro TIPS

Win 8.1で追加された「現在表示中のポップアップを取得できる機能」の使い方を解説。ポップアップと対話操作をしているエンドユーザーの邪魔をしたくないケースなどで役立つ。

» 2014年03月20日 14時29分 公開
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次

 画面にポップアップが出ているかどうか、知りたいと思ったことはないだろうか? 例えば、エンドユーザーがComboBoxコントロール(Windows.UI.Xaml.Controls名前空間)のドロップダウンを開いてまさに選択しようとしているときに、そのドロップダウンのデータを更新するのは避けたいだろう。あるいは、利用しているライブラリが独自に出しているポップアップを邪魔したくないときもあるだろう。Windows 8.1(以降、Win 8.1)に追加された機能でそれが可能になったので、本稿ではその使い方を解説する。なお、本稿のサンプルは「Windows Store app samples:MetroTips #67(Windows 8.1版)」からダウンロードできる。

事前準備

 Win 8.1用のWindowsストアアプリを開発するには、Win 8.1とVisual Studio 2013(以降、VS 2013)が必要である。本稿ではOracle VM VirtualBox上で64bit版Windows 8.1 Pro(日本語版)とVisual Studio Express 2013 for Windows(日本語版)*1を使用している。

*1 マイクロソフト公式ダウンロードセンターの「Microsoft Visual Studio Express 2013 for Windows」から無償で入手できる。


ポップアップとは?

 本稿での「ポップアップ」にはPopupコントロール(Windows.UI.Xaml.Controls.Primitives名前空間)だけでなく、Flyoutコントロール/ToolTipコントロール/AppBarコントロール(いずれもWindows.UI.Xaml.Controls名前空間)なども、また、ComboBoxコントロール/DatePickerコントロール(どちらもWindows.UI.Xaml.Controls名前空間)などが出すポップアップも含まれる。

 幾つかのコントロールには、ポップアップが出たときや閉じたときに発生するイベントがある(例えば、ComboBoxコントロールのDropDownOpenedイベントとDropDownClosedイベント)。そのようなコントロールでは、イベントを拾ってデータの更新を遅らせるといった対処が可能だ。ところが、例えばDatePickerコントロールなどは、そのようなイベントを持たない。また、利用しているライブラリが独自にポップアップを出すこともある。例えば「Windows 8アプリ内広告」は設定チャームから独自のフライアウトを出すのだが、そのときのイベントは用意されていない。

 そのような状況で、ポップアップと対話操作をしているエンドユーザーの邪魔をしたくないなら、表示されているポップアップを取得できる機能が欲しいだろう。

表示されているポップアップを取得するには?

 VisualTreeHelperクラス(Windows.UI.Xaml.Media名前空間)のGetOpenPopupsメソッドを使えばよい。

 このクラスはWindows 8からあるのだが、GetOpenPopupsメソッドはWin 8.1で新設されたものだ。ちなみに、同名のメソッドがSilverlightではバージョン4から存在しているので、SilverlightやWindows Phone 7.1での開発経験がある方はWindows 8のWindowsストアアプリではこのメソッドが存在しないことに戸惑っただろうと思う。

 GetOpenPopupsメソッドを呼び出すと、Popupオブジェクトの読み取り専用コレクションが返される(次のコード)。

IReadOnlyList<Popup> popups = VisualTreeHelper.GetOpenPopups(Window.Current);

Dim popups As IReadOnlyList(Of Popup) = VisualTreeHelper.GetOpenPopups(Window.Current)

GetOpenPopupsメソッドの呼び出し方(上:C#、下:VB)
引数には、現在表示されているWindowオブジェクトを渡す。

 GetOpenPopupsメソッドから返されたPopupオブジェクトのコレクションには、画面に表示されているポップアップが全て入っている。ただし、それらはアプリから表示されたものだけであって、システムが出しているポップアップは含まれない。

取得できるもの/できないもの

 GetOpenPopupsメソッドで取得できるものとできないものを、実験して調べてみよう。次の図のようなUIを用意した。

実験のために作成した画面 実験のために作成した画面
画面左上の灰色の四角形はListBoxコントロール(Windows.UI.Xaml.Controls名前空間)で、「listBox1」という名前を付けた。ここに、GetOpenPopupsメソッドで取得したポップアップの情報を表示する。 この画像では見えていないが、下端にはアプリバーがある。また、画面に広告を配置することで、右端の設定チャームからWindows 8アプリ内広告のフライアウトも出せるようにしてある。 MessageDialogコントロールを出すコードは本稿では割愛しているので、サンプルコードをダウンロードして参照していただきたい。

 先に結論を書いておくと、次のようにまとめられる。

  • 取得できるもの
     = アプリから出たポップアップ: ツールチップ、Flyoutコントロール/MenuFlyoutコントロール/SettingsFlyoutコントロール(いずれもWindows.UI.Xaml.Controls名前空間)、ComboBoxコントロール/DatePickerコントロールなどが表示するドロップダウン、アプリバー、Popupコントロールを利用したフライアウトなど
  • 取得できないもの
     = システムが出すポップアップ: IMEのモード表示(キー入力時に出る[あ]/[A])、TextBoxコントロール(Windows.UI.Xaml.Controls名前空間)などに出るコンテキストメニュー、メッセージダイアログ、チャームから出る検索/共有/デバイス/設定の各フライアウトなど

 以上のことを確かめるために、GetOpenPopupsメソッドで取得したポップアップの情報をListBoxコントロールに表示するコードを、コードビハインドに記述した(次のコード)。

private void ShowPopupInformation()
{
  // 開いているポップアップを取得する
  IReadOnlyList<Popup> popups = VisualTreeHelper.GetOpenPopups(Window.Current);

  // 取得した全てのポップアップの情報をリストボックスに表示する
  IEnumerable<string> descriptions
    = popups.Select((p) => GetDescription(p));
  this.listBox1.ItemsSource = descriptions;
}

private string GetDescription(Popup p)
{
  var c = p.Child as FrameworkElement;
  if (c == null)
    return "Not FrameworkElement";

  // ポップアップの子要素の位置/サイズ/クラス名を合わせた文字列を作る
  var r = GetElementRect(c);
  return string.Format("{0}, {1}, {2}, {3}, {4}"
                       r.Left, r.Top, r.Width, r.Height, c.GetType().Name);
}

private Windows.Foundation.Rect GetElementRect(FrameworkElement element)
{
  // ポップアップの子要素の位置/サイズを取得する
  var buttonTransform = element.TransformToVisual(null);
  var point = buttonTransform.TransformPoint(new Windows.Foundation.Point());
  Windows.Foundation.Size size = new Windows.Foundation.Size(element.ActualWidth, element.ActualHeight);
  return new Windows.Foundation.Rect(point, size);
}

Private Sub ShowPopupInformation()
  ' 開いているポップアップを取得する
  Dim popups As IReadOnlyList(Of Popup) = VisualTreeHelper.GetOpenPopups(Window.Current)

  ' 取得した全てのポップアップの情報をリストボックスに表示する
  Dim descriptions As IEnumerable(Of String) _
    = popups.Select(Function(p) GetDescription(p))
  Me.listBox1.ItemsSource = descriptions
End Sub

Private Function GetDescription(p As Popup) As String
  Dim c = DirectCast(p.Child, FrameworkElement)
  If (c Is Nothing) Then
    Return "Not FrameworkElement"
  End If

  ' ポップアップの子要素の位置/サイズ/クラス名を合わせた文字列を作る
  Dim r = GetElementRect(c)
  Return String.Format("{0}, {1}, {2}, {3}, {4}",
                       r.Left, r.Top, r.Width, r.Height, c.GetType().Name)
End Function

Private Function GetElementRect(element As FrameworkElement) As Windows.Foundation.Rect
  ' ポップアップの子要素の位置/サイズを取得する
  Dim buttonTransform = element.TransformToVisual(Nothing)
  Dim point = buttonTransform.TransformPoint(New Windows.Foundation.Point())
  Dim size As Windows.Foundation.Size = New Windows.Foundation.Size(element.ActualWidth, element.ActualHeight)
  Return New Windows.Foundation.Rect(point, size)
End Function

GetOpenPopupsメソッドで取得したポップアップの情報をListBoxコントロールに表示するコード(上:C#、下:VB)
「MainPage.xaml.cs/.vb」ファイルに追記する。
取得したポップアップは全てPopupクラスで区別が付かないため、その子要素の情報を表示するようにした。

 さて、上で作ったShowPopupInformationメソッドを呼び出したいのだが、Buttonコントロール(Windows.UI.Xaml.Controls名前空間)などのUIを使うわけにはいかない。UIを操作しようとすると、ポップアップが閉じてしまうからだ。そこで、タイマーを使って定期的にShowPopupInformationメソッドを呼び出す(次のコード)。

private Windows.System.Threading.ThreadPoolTimer _periodicTimer;

public MainPage()
{
  ……省略……

  // タイマーをセットし、定期的にShowPopupInformationメソッドを呼び出す
  _periodicTimer 
    = Windows.System.Threading.ThreadPoolTimer.CreatePeriodicTimer(
        async (source) =>
        {
           await Dispatcher.RunAsync(
                  Windows.UI.Core.CoreDispatcherPriority.High, 
                  () => ShowPopupInformation()
                 ); 
        },
        TimeSpan.FromSeconds(1) 
      ); 
}

Private _periodicTimer As Windows.System.Threading.ThreadPoolTimer

Public Sub New()
  ……省略……

  ' タイマーをセットし、定期的にShowPopupInformationメソッドを呼び出す
  _periodicTimer _
    = Windows.System.Threading.ThreadPoolTimer.CreatePeriodicTimer( _
        Async Sub(source)
          Await Dispatcher.RunAsync( _
            Windows.UI.Core.CoreDispatcherPriority.High, _
            Sub()
              ShowPopupInformation()
            End Sub
          )
        End Sub, _
        TimeSpan.FromSeconds(1) _
      )
End Sub

タイマーを使って定期的にShowPopupInformationメソッドを呼び出すコード(上:C#、下:VB)
「MainPage.xaml.cs/.vb」ファイルに太字の部分を追加する。
タイマーの間隔は1秒とした。実験ということで、コンストラクターでタイマーをスタートさせてそれっきりにしている(実際のアプリでは、画面遷移やアプリの中断/再開でタイマーを制御すべきである)。

 以上で、GetOpenPopupsメソッドで取得したポップアップの情報がListBoxコントロールに自動的に表示されるようになった。実際にいろいろなUIを作り込んで試してみてほしい。次の画像は、Windows 8アプリ内広告の設定フライアウト(=右端から出るフライアウト)とアプリバーなどを同時に出しているところだ。このように、同時に表示されている複数のポップアップの情報を取得できるのである。

実行例 実行例
この画面では、アプリから4つのポップアップが出ている。
  • 設定チャームで[マイクロソフト アドバタイジング]を選択して右端から出したフライアウト(子要素のクラス名は「AdSettingsFlyout」)
  • [マイクロソフト アドバタイジング]フライアウトが閉じないようにその上で操作して出した下端のアプリバー(同「Grid」)
  • アプリバーのボタンで出したFlyoutコントロール(同「FlyoutPresenter」)
  • Flyoutコントロールから出したツールチップ(同「ToolTip」)

まとめ

 VisualTreeHelperクラスに新設されたGetOpenPopupsメソッドを使うと、画面に表示されている全てのポップアップを取得できる。ただし、システムが出しているコンテキストメニューやフライアウトなどは取得できない。

「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。