コマンドラインからURIで起動できるアプリを作るには?[ユニバーサルWindowsアプリ開発]:WinRT/Metro TIPS
プロトコルアクティベーションを利用して、コマンドプロンプトや他のアプリからユニバーサルアプリを起動する方法を解説する。
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を使っている。
- SLAT対応のPC*1
- 2014年4月のアップデート*2適用済みの64bit版Windows 8.1 Pro版以上*3
- Visual Studio 2013 Update 2(またはそれ以降)*4を適用済みのVisual Studio 2013(以降、VS 2013)*5
*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。
- 起動またはアクティブ化: ms-windows-store:
- 特定の提供元のアプリ一覧: ms-windows-store:Publisher?name=BluewaterSoft
- キーワードに基づく検索結果: ms-windows-store:Search?query=@IT%20RSS%20BluewaterSoft
このURIを、Windows 8.1のコマンドラインではstartコマンドの引数として指定する(次の画像)。Windows 8.1の「ファイル名を指定して実行」では、このURIだけを与える。Windows 8.1のWindowsランタイムアプリでは、このURIからUriオブジェクト(System名前空間)を作り、Launcherクラス(Windows.System名前空間)のLaunchUriAsyncメソッドに引数として渡す。すると、Windowsストアのアプリが起動し、2番目/3番目の例では指定した動作が実行される。
コマンドラインからWindowsストアのアプリを起動する(Windows 8.1)
コマンドプロンプトを開き、「start ms-windows-store:Search?query=@IT%20RSS%20BluewaterSoft」と入力してエンターキーを押すと、Windowsストアのアプリに切り替わり、「@IT RSS BluewaterSoft」を検索した結果が表示される(右上の検索ボックスにその文字列が見える)。
プロトコルアクティベーションに与えるURI中のパラメーターはURLエンコードされていなければならないのだが、自動的にエンコードされるので通常は気にしなくてよい(この例では「@」は全角だがエンコードせずに入力している)。ただし、startコマンドの引数に与えるときには空白を含められないので、そこだけは「%20」にエンコードしておく必要があった。
なお、URIの長さは256bytesまでなので、あまり複雑な情報は渡せない。また、LauncherクラスのLaunchUriAsyncメソッドで起動した場合でも、起動されたアプリでの処理結果を起動元のアプリが受け取ることはできない*8。
注意が必要なのは、プロトコルとアプリとの関連付けをエンドユーザーが変更できることだ(次の画像)。「ms-windows-store:」プロトコルでWindowsストアのアプリが必ず起動されるとは限らないのである*9。冒頭で「間接的に」と書いたことには、そういう意味もあるのだ。
プロトコルとアプリとの関連付けを設定するダイアログ(Windows 8.1のコントロールパネル)
[説明]の列に「URL:」と表示されているものがプロトコルである(画像では見えていないが、このダイアログではファイル拡張子との関連付けも表示される)。ご覧の通り、筆者の環境ではかなり多くのプロトコルが登録されている。ここでプロトコルを選択し、右上の[プログラムの変更]ボタンをクリックすると、アプリの関連付けを変更するためのダイアログが出てくる。
なお、このダイアログは、Windows 8.1のコントロールパネルで[プログラム]−[既定のプログラム]−[ファイルの種類またはプロトコルのプログラムへの関連付け]とリンクをたどって表示する。
*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」とする)。
- 起動またはアクティブ化: metrotips-108cs:
- 画面に表示する文字列を指定: metrotips-108cs:Greet?msg=Hello!
指定した文字列(上の2番目の例では「Hello!」)を画面に表示する部分の実装は、本稿では説明しない。別途公開のサンプルには実装してあるので、参照していただきたい。
プロトコルを宣言するには?
マニフェストエディターの[宣言]タブで[使用可能な宣言]として[プロトコル]を追加し、プロトコル名(今回の例では「metrotips-108cs」または「metrotips-108vb」、「:」は含まない)を入力して保存する(次の画像)。必須ではないが、[ロゴ]の画像も追加した方がよい*10。この作業は、ユニバーサルプロジェクトであっても、WindowsとWindows Phoneのプロジェクトそれぞれで行う。
マニフェストにプロトコルの宣言を追加する(VS 2013のマニフェストエディター)
これはWindows用のプロジェクトのもの。Windows Phone用では少々異なるが、入力すべきところは同じだ。
マニフェストエディターで[宣言]タブを選び((1))、[使用可能な宣言]ドロップダウンで[プロトコル]を選び((2))、[追加]ボタンをクリックする((3))。すると右側の入力欄が表示されるので、そこの[名前]テキストボックスにプロトコル名を入力する((4))。入力するプロトコル名には「:」を含めず、全て小文字とする。
*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 のアクティブ化を処理する方法」を参照。
OnActivatedメソッドをオーバーライドするには?
通常起動時には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
プロトコルアクティベーションに対応するには、Appクラス(「App.xaml.cs」/「App.xaml.vb」ファイル)にこのようなOnActivatedメソッドを追加する(VS 2013にメソッドのオーバーライドを自動生成させた場合は引数名が「e」ではなく「args」になるが、OnLaunchedメソッドに合わせて引数名を修正する)。
メソッド末尾の省略部分には、プロジェクト作成時に自動生成されたOnLaunchedメソッドの中身を丸ごとコピー&ペーストすればよい。ただし、MainPage画面を表示する部分は、このコードのように変更する(プロジェクトテンプレートによっては変更箇所のコードが少し異なっている場合もあるが、「rootFrame.Navigate」を探してほしい)。なお実際には、丸ごとコピー&ペーストするのではなく、共通のメソッドにくくり出すのがよいだろう。
このコードだけで、プロトコルアクティベーションによってアプリが起動/アクティベートされるようになる。
上のコードで、プロトコルアクティベーションによってアプリが起動/アクティベートされるようになった。しかしまだ、画面に表示する文字列が指定されていても対応できていない。
例えば、「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
このようにして取り出した文字列を、先に示したコードではMainPage画面へ遷移するときの引数として渡している。別途公開のサンプルでは、引数として渡された文字列をMainPage画面で表示するようにしている。
これで、プロトコルアクティベーションによって起動されたときは、望みの動作をさせられるようになった。しかし、すでに起動している状態でプロトコルアクティベーションを受けても何も起こらない。それに対処するには、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
アプリがすでに実行中の場合は、新しくフレームを作ったりせず、単にMainPage画面へ遷移すればよい。ただし、Windows Phoneの場合は、ユーザビリティを考えると、画面の遷移履歴を消去した方がよい。
プロトコルアクティベーションをデバッグするには?
すでに起動している状態でプロトコルアクティベーションを受けた場合をデバッグするには、普通にデバッグ実行を開始した後に、Windowsではコマンドラインからstartコマンドを使ってアクティベーションすればよい。Windows Phoneでは、アクティベーションするアプリを用意しておいてそれを使う(別途公開のサンプルには、そのプロジェクトもC#版のみだが含めてある)。
起動していない状態でプロトコルアクティベーションを受けた場合をデバッグするには、まずプロジェクトのプロパティ画面で[開始動作]を[起動しないが、開始時にコードをデバッグ]に変更しておく(次の画像)。そしてデバッグ実行を開始してから、アクティベーションを行う。
起動されるのを待ってデバッグに入るための設定(VS 2013)
プロジェクトのプロパティを表示し、左端で[デバッグ]を選び(この画像では青くなっている)、右の[開始動作]の[起動しないが、開始時にコードをデバッグ]チェックボックスにチェックを入れる。こうすると、[F5]キーなどでデバッグ実行を指示してもアプリは起動せず、Visual Studioがデバッグ開始を待機している状態になる。他からアプリを起動すると(例えばスタート画面でタイルをタップしたり、別のアプリからプロトコルアクティベーションを実行したりすると)、デバッグが始まる。
まとめ
プロトコルアクティベーションは、すでに多くのアプリで使われている(本文中の「プロトコルとアプリとの関連付けを設定するダイアログ」画像を参照)。Windows 10では、起動したアプリから処理結果を返してもらえるようにもなる。この機会に、プロトコルアクティベーションの活用を考えてみてはいかがだろうか。
Copyright© Digital Advantage Corp. All Rights Reserved.