Win 8.1で追加された「現在表示中のポップアップを取得できる機能」の使い方を解説。ポップアップと対話操作をしているエンドユーザーの邪魔をしたくないケースなどで役立つ。
powered by Insider.NET
画面にポップアップが出ているかどうか、知りたいと思ったことはないだろうか? 例えば、エンドユーザーが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メソッドから返されたPopupオブジェクトのコレクションには、画面に表示されているポップアップが全て入っている。ただし、それらはアプリから表示されたものだけであって、システムが出しているポップアップは含まれない。
GetOpenPopupsメソッドで取得できるものとできないものを、実験して調べてみよう。次の図のようなUIを用意した。
先に結論を書いておくと、次のようにまとめられる。
以上のことを確かめるために、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
さて、上で作った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
以上で、GetOpenPopupsメソッドで取得したポップアップの情報がListBoxコントロールに自動的に表示されるようになった。実際にいろいろなUIを作り込んで試してみてほしい。次の画像は、Windows 8アプリ内広告の設定フライアウト(=右端から出るフライアウト)とアプリバーなどを同時に出しているところだ。このように、同時に表示されている複数のポップアップの情報を取得できるのである。
VisualTreeHelperクラスに新設されたGetOpenPopupsメソッドを使うと、画面に表示されている全てのポップアップを取得できる。ただし、システムが出しているコンテキストメニューやフライアウトなどは取得できない。
Copyright© Digital Advantage Corp. All Rights Reserved.