プロトコルアクティベーションを利用して、コマンドプロンプトや他のアプリからユニバーサルアプリを起動する方法を解説する。
powered by Insider.NET
Windows 8.1のコマンドラインからWindowsランタイムアプリを起動できたらよいのにと思ったことはないだろうか? あるいは、Windows 8.1/Windows Phone 8.1のWindowsランタイムアプリから別のアプリを起動したいことはないだろうか? Windowsランタイムアプリを直接起動することはできないのだが、プロトコルアクティベーションに対応しているアプリならばOSに特定のURIを渡すことでそれが間接的に可能になる。本稿では、プロトコルアクティベーションに対応したアプリの実装方法を解説する。なお、本稿のサンプルは「Windows Store app samples:MetroTips #108」からダウンロードできる。
ユニバーサルプロジェクトを使ってWindows 8.1/Windows Phone 8.1用のユニバーサルWindowsアプリを開発するには、以下の開発環境が必要である。本稿では、無償のVisual Studio Community 2013 with Update 4を使っている。
*1 SLAT対応ハードウエアは、Windows Phone 8.1エミュレーターの実行に必要だ。ただし未対応でも、ソースコードのビルドと実機でのデバッグは可能だ。SLAT対応のチェック方法はMSDNブログの「Windows Phone SDK 8.0 ダウンロードポイント と Second Level Address Translation (SLAT) 対応PCかどうかを判定する方法」を参照。なお、SLAT対応ハードウエアであっても、VM上ではエミュレーターが動作しないことがあるのでご注意願いたい。
*2 事前には「Windows 8.1 Update 1」と呼ばれていたアップデート。スタート画面の右上に検索ボタンが(環境によっては電源ボタンも)表示されるようになるので、適用済みかどうかは簡単に見分けられる。ちなみに公式呼称は「the Windows RT 8.1, Windows 8.1, and Windows Server 2012 R2 update that is dated April, 2014」というようである。
*3 Windows Phone 8.1エミュレーターを使用しないのであれば、32bit版のWindows 8.1でもよい。
*4 マイクロソフトのダウンロードページから誰でも入手できる(このURLはUpdate 4のもの)。
*5 本稿に掲載したコードを試すだけなら、無償のExpressエディションやCommunityエディションで構わない。Visual Studio Express 2013 with Update 4 for Windows(製品版)はマイクロソフトのページから無償で入手できる。Expressエディションはターゲットプラットフォームごとに製品が分かれていて紛らわしいが、Windowsランタイムアプリの開発には「for Windows」を使う(「for Windows Desktop」はデスクトップで動作するアプリ用)。また、Visual Studio Community 2013 with Update 4(製品版)もマイクロソフトのページから無償で入手できる。Communityエディションは本稿執筆時点では英語版だけなので、同じ場所にあるVisual Studio 2013 Language Packの日本語版を追加インストールし、[オプション]ダイアログで言語を切り替える必要がある。
Visual Studio 2013 Update 2(Update 3/4も)では、残念なことにVB用のユニバーサルプロジェクトのテンプレートは含まれていない*6。そのため、本稿で紹介するVBのコードはユニバーサルプロジェクトではなく、PCL(ポータブルクラスライブラリ)を使ったプロジェクトのものである。
*6 VB用のユニバーサルプロジェクトは、2015年の夏にリリースされるといわれているVisual Studio 2015(開発コード「Visual Studio 14」)からの提供となるようだ。プレビュー版で、すでに共有プロジェクトは利用可能になっている。「特集:次期Visual Studioの全貌を探る:Visual Basic 14の新機能ベスト10〜もう「VBだから」とは言わせない!」参照。
例えば、Windowsストアのアプリはプロトコルアクティベーションに対応しており、次のようなURIを受け付ける*7。
このURIを、Windows 8.1のコマンドラインではstartコマンドの引数として指定する(次の画像)。Windows 8.1の「ファイル名を指定して実行」では、このURIだけを与える。Windows 8.1のWindowsランタイムアプリでは、このURIからUriオブジェクト(System名前空間)を作り、Launcherクラス(Windows.System名前空間)のLaunchUriAsyncメソッドに引数として渡す。すると、Windowsストアのアプリが起動し、2番目/3番目の例では指定した動作が実行される。
注意が必要なのは、プロトコルとアプリとの関連付けをエンドユーザーが変更できることだ(次の画像)。「ms-windows-store:」プロトコルでWindowsストアのアプリが必ず起動されるとは限らないのである*9。冒頭で「間接的に」と書いたことには、そういう意味もあるのだ。
*7 ms-windows-storeプロトコルには、まだ他にもパラメーターがある。また、Windows Phone 8.1ではパラメーターの与え方が異なる。詳しくは「WinRT/Metro TIPS:アプリからストアを開くには?[ユニバーサルWindowsアプリ開発]」をご覧いただきたい。
*8 Windows 10では改良される。「特集:次期Visual Studioの全貌を探る:徹底予習! Windows 10のユニバーサルアプリ開発」の「4. アプリ間連携の強化」を参照してほしい。
*9 Windows 10ではLauncherOptionsクラス(Windows.System名前空間)にTargetApplicationPackageFamilyNameプロパティが追加される。これをLauncherクラスのLaunchUriAsyncメソッドの引数に追加することで、起動されるアプリを特定できるようになるようだ。
マニフェストでプロトコルを宣言し、AppクラスでOnActivatedメソッドをオーバーライドすればよい。
本稿では、次のようなURIを受け付けるようにしてみよう(以下のURIはC#版のもので、VB版ではプロトコル名を「metrotips-108vb」とする)。
指定した文字列(上の2番目の例では「Hello!」)を画面に表示する部分の実装は、本稿では説明しない。別途公開のサンプルには実装してあるので、参照していただきたい。
マニフェストエディターの[宣言]タブで[使用可能な宣言]として[プロトコル]を追加し、プロトコル名(今回の例では「metrotips-108cs」または「metrotips-108vb」、「:」は含まない)を入力して保存する(次の画像)。必須ではないが、[ロゴ]の画像も追加した方がよい*10。この作業は、ユニバーサルプロジェクトであっても、WindowsとWindows Phoneのプロジェクトそれぞれで行う。
*10 [ロゴ]の画像は、OSが表示するアプリの関連付けを選ぶダイアログなどで使用される。
[ロゴ]の画像を指定しなかったときは、アプリの小さいロゴ(Windowsではスケール100で30×30ピクセル、Windows Phoneでは同じく44×44ピクセル)が使われるようだ。
[ロゴ]の画像を指定する場合は、Windowsでは16×16/32×32/48×48/256×256ピクセルの画像を、Windows Phoneでは63×63/129×129/336×336ピクセルの画像を用意する。それらの画像のファイル名にはtargetsize修飾子を含め(例:「ProtocolLogo.targetsize-16.png」)、マニフェストにはtargetsize修飾子なしで指定する(例:「ProtocolLogo.png」)。MSDN「URI のアクティブ化を処理する方法」を参照。
通常起動時にはAppクラスのOnLaunchedメソッドが呼び出されるが、プロトコルアクティベーションで起動/アクティブ化されたときにはOnActivatedメソッドが呼び出される。プロトコルアクティベーションに対応するには、OnActivatedメソッドをオーバーライドして、プロトコルに対応した処理を追加する。
OnActivatedメソッドのオーバーライドは、概要を示すと次のコードのようになる。
protected override void OnActivated(IActivatedEventArgs e)
{
base.OnActivated(e); // 先に継承元のメソッドを処理する
string argMessage = string.Empty; // Uriから取り出した文字列を格納する変数
if (e.Kind == ActivationKind.Protocol)
{
// プロトコルによって起動されたときは、e.KindがActivationKind.Protocolになっている。
// このときの引数eはProtocolActivatedEventArgsクラスなので、キャストして使用する
ProtocolActivatedEventArgs eventArgs = e as ProtocolActivatedEventArgs;
// 画面表示前にプロトコルを処理するなら、ここで行う
}
// 以降は、通常起動時(OnLaunchedメソッド)とほぼ同じコードを記述する。
// 引数eの型が違うことに注意! 少なくとも、そこは修正する必要がある
// ……省略……
//if (!rootFrame.Navigate(typeof(MainPage), e.Arguments))
// OnLaunchedメソッドでは上のようになっていた。しかし、IActivatedEventArgsインターフェースには、
// Argumentsプロパティがない。そこで、ここでは上で作成したargMessage変数を渡すことにする
if (!rootFrame.Navigate(typeof(MainPage), argMessage))
// ……省略……
}
Protected Overrides Sub OnActivated(e As Windows.ApplicationModel.Activation.IActivatedEventArgs)
MyBase.OnActivated(e) '先に継承元のメソッドを処理する
Dim argMessage As String = String.Empty ' Uriから取り出した文字列を格納する変数
If (e.Kind = ActivationKind.Protocol) Then
' プロトコルによって起動されたときは、args.KindがActivationKind.Protocolになっている。
' このとき、引数eはProtocolActivatedEventArgsクラスなので、キャストして使用する
Dim eventArgs As ProtocolActivatedEventArgs = TryCast(e, ProtocolActivatedEventArgs)
' 画面表示前にプロトコルを処理するなら、ここで行う
End If
' 以降は、通常起動時(OnLaunchedメソッド)とほぼ同じコードを記述する。
' 引数eの型が違うことに注意! 少なくとも、そこは修正する必要がある
' ……省略……
'If Not rootFrame.Navigate(GetType(MainPage), e.Arguments) Then
' OnLaunchedメソッドでは上のようになっていた。しかし、IActivatedEventArgsインターフェースには、
' Argumentsプロパティがない。そこで、ここでは上で作成したargMessage変数を渡すことにする
If Not rootFrame.Navigate(GetType(MainPage), argMessage) Then
' ……省略……
End Sub
上のコードで、プロトコルアクティベーションによってアプリが起動/アクティベートされるようになった。しかしまだ、画面に表示する文字列が指定されていても対応できていない。
例えば、「metrotips-108cs:Greet?msg=Hello!」というUriでアクティベートされたときに「Hello!」という文字列を取り出すには、上のコードで「画面表示前にプロトコルを処理するなら、ここで行う」とコメントしてある部分に次のコードのように追加する。
string argMessage = string.Empty; // Uriから取り出した文字列を格納する変数
if (e.Kind == ActivationKind.Protocol)
{
// プロトコルによって起動されたときは、e.KindがActivationKind.Protocolになっている。
// このときの引数eはProtocolActivatedEventArgsクラスなので、キャストして使用する
ProtocolActivatedEventArgs eventArgs = e as ProtocolActivatedEventArgs;
// 画面表示前にプロトコルを処理するなら、ここで行う
// ↓追加:ここから
// 引数eをProtocolActivatedEventArgsにキャストすると、そのUriプロパティには、
// アクティベートされたときのUriが入っている
Uri uri = eventArgs.Uri;
// Uriが "metrotips-108cs:Greet?msg=Hello!" の場合、AbsolutePathには"Greet"が入っている
if (uri.AbsolutePath.Equals("GREET", StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(uri.Query))
{
// uri.Queryには"?msg=Hello!"が入っている。
// これを、WwwFormUrlDecoderクラス(Windows.Foundation名前空間)を使って分解する
var decoder = new WwwFormUrlDecoder(uri.Query);
// クエリをデコードした結果(=decoder)には、Name="msg",Value="Hello!"が入っている。
// Dictionaryの方が扱いやすいので、変換してから利用する
Dictionary<string, string> dic = decoder.ToDictionary(x => x.Name, x => x.Value);
dic.TryGetValue("msg", out argMessage);
}
// ↑追加:ここまで
}
Dim argMessage As String = String.Empty ' Uriから取り出した文字列を格納する変数
If (e.Kind = ActivationKind.Protocol) Then
' プロトコルによって起動されたときは、args.KindがActivationKind.Protocolになっている。
' このとき、引数eはProtocolActivatedEventArgsクラスなので、キャストして使用する
Dim eventArgs As ProtocolActivatedEventArgs = TryCast(e, ProtocolActivatedEventArgs)
' 画面表示前にプロトコルを処理するなら、ここで行う
' ↓追加:ここから
' 引数eをProtocolActivatedEventArgsにキャストすると、そのUriプロパティには、
' アクティベートされたときのUriが入っている。
Dim uri As Uri = eventArgs.Uri
' Uriが "metrotips-108vb:Greet?msg=Hello!" の場合、AbsolutePathには"Greet"が入っている
If (uri.AbsolutePath.Equals("GREET", StringComparison.OrdinalIgnoreCase)) Then
If (Not String.IsNullOrWhiteSpace(uri.Query)) Then
' uri.Queryには"?msg=Hello!"が入っている。
' これを、WwwFormUrlDecoderクラス(Windows.Foundation名前空間)を使って分解する
Dim decoder = New WwwFormUrlDecoder(uri.Query)
' クエリをデコードした結果(=decoder)には、Name="msg",Value="Hello!"が入っている。
' Dictionaryの方が扱いやすいので、変換してから利用する
Dim dic As Dictionary(Of String, String) _
= decoder.ToDictionary(Function(x) x.Name, Function(x) x.Value)
dic.TryGetValue("msg", argMessage)
End If
End If
' ↑追加:ここまで
End If
これで、プロトコルアクティベーションによって起動されたときは、望みの動作をさせられるようになった。しかし、すでに起動している状態でプロトコルアクティベーションを受けても何も起こらない。それに対処するには、OnLaunchedメソッドからコピー&ペーストしてきた部分の末尾に、次のようにコードを追加する。
if (rootFrame.Content == null)
{
// ……省略……
}
// ↓追加:実行中にプロトコルアクティベーションされた場合
else
{
if (e.Kind == ActivationKind.Protocol)
{
rootFrame.Navigate(typeof(MainPage), argMessage);
#if WINDOWS_PHONE_APP
// Windows Phoneでは画面遷移履歴を消す。
// すると、[戻る]ボタンで起動元のアプリに戻れるようになる
rootFrame.BackStack.Clear();
#endif
}
}
// ↑追加:ここまで
// 現在のウィンドウがアクティブであることを確認します
Window.Current.Activate();
}
If rootFrame.Content Is Nothing Then
' ……省略……
' ↓追加:実行中にプロトコルアクティベーションされた場合
Else
If (e.Kind = ActivationKind.Protocol) Then
rootFrame.Navigate(GetType(MainPage), argMessage)
' 【次のコードはWindows Phoneだけに記述】
' Windows Phoneでは画面遷移履歴を消す。
' すると、[戻る]ボタンで起動元のアプリに戻れるようになる
rootFrame.BackStack.Clear()
End If
' ↑追加:ここまで
End If
' 現在のウィンドウがアクティブであることを確認します
Window.Current.Activate()
End Sub
すでに起動している状態でプロトコルアクティベーションを受けた場合をデバッグするには、普通にデバッグ実行を開始した後に、Windowsではコマンドラインからstartコマンドを使ってアクティベーションすればよい。Windows Phoneでは、アクティベーションするアプリを用意しておいてそれを使う(別途公開のサンプルには、そのプロジェクトもC#版のみだが含めてある)。
起動していない状態でプロトコルアクティベーションを受けた場合をデバッグするには、まずプロジェクトのプロパティ画面で[開始動作]を[起動しないが、開始時にコードをデバッグ]に変更しておく(次の画像)。そしてデバッグ実行を開始してから、アクティベーションを行う。
プロトコルアクティベーションは、すでに多くのアプリで使われている(本文中の「プロトコルとアプリとの関連付けを設定するダイアログ」画像を参照)。Windows 10では、起動したアプリから処理結果を返してもらえるようにもなる。この機会に、プロトコルアクティベーションの活用を考えてみてはいかがだろうか。
Copyright© Digital Advantage Corp. All Rights Reserved.