同じチームの Matteo さんが書いてくれてた記事に ConditionalAttribute が使ってあって、あぁこういう機能あったなぁと思ったのでメモがてら記事をなぞってやってみました。
Desktop Bridge – Enhancing a desktop application with the UWP APIs – App Consult Team
WPF や Windows Forms とかで書かれたアプリから UWP の API は呼べます。UwpDesktop という NuGet パッケージを追加するのがお手軽ではあります。 まぁでも UwpDesktop が何をやってくれるかというのを確認するためにも手動の方法を確認してみましょう。
因みに、今回の方法を使っても全 API が呼べるわけではなく以下にある API がサポートされている API です。
パッケージ デスクトップ アプリで利用可能な UWP API (デスクトップ ブリッジ) - UWP app developer | Microsoft Docs
全部がサポートされてないのは、例えばですが UWP のコントロールを WPF でそもそも出せないとか、そういう色んな制約のせいですね。
手動でやる方法
C:\Program Files (x86)\Windows Kits\10\UnionMetadata\Windows.winmd
を参照に追加します。
ただ、デフォルトで dll などの拡張子しか表示しないようになっているので「すべてのファイル」を選択しないと winmd の拡張子のファイルは表示されないので気を付けてください。
参照先の Blog 記事がトーストを表示してみようという内容だったのでそれにならってやってみましょう。名前空間を省略せずに書くとこのようになります。
public void ShowNotification() { var xml = @"<toast> <visual> <binding template='ToastGeneric'> <text>Desktop Bridge</text> <text>The file has been created</text> </binding> </visual> </toast>"; Windows.Data.Xml.Dom.XmlDocument doc = new Windows.Data.Xml.Dom.XmlDocument(); doc.LoadXml(xml); Windows.UI.Notifications.ToastNotification toast = new Windows.UI.Notifications.ToastNotification(doc); Windows.UI.Notifications.ToastNotificationManager.CreateToastNotifier().Show(toast); }
そして、このメソッドを画面に適当に置いたボタンのクリックイベントから呼び出すとエラーになります。(元記事もそうですね)
UWP の機能の中でも、いくつかは appx という UWP の形式にパッケージングしないと呼べないものがあります。 ということで、以下のような記事の手順で appx にする必要があります。
- インストーラー形式のファイルがある人向け(インストール用バッチファイルでもいいのですが特別な理由がない限りは選択しなくてもいいかも)
- Visual Studio で開発している人向け(普通はこれば一番手軽です。注意点としては 2018/03/29 時点で日本語版の記事は古いということです。英語で見ましょう。)
- 手動でやる(これは、どうしても超細かい制御をしたい人向け or 上記2つではだめな人向けで普通はしなくてもいいと思います)
Windows アプリケーション パッケージ プロジェクトを使って UWP として実行すると無事トーストが出ました。
appx じゃないバージョンもあるのですが…
これがメモしたかった内容なのですが(機能自体は知ってたけど使ってなかった)appx にパッケージングする方法でないと動かない機能があると困りますよね。既存の exe 配る方法などで。
そんな時は「ビルド→構成マネージャー」でUWP用の構成を作ります。
プロジェクトのプロパティで条件付きコンパイルシンボルに DesktopUWP
のようなものを定義しておきます。
あとは #if ディレクティブでくくるでもいいですし、見通しがいいのは元記事でも行われている ConditionalAttribute を使って UWP に依存する機能はメソッドにまとめておいて、DesktopUWP が定義されてないときは無視するようにするといいです。
private void Button_Click(object sender, RoutedEventArgs e) { ShowNotification(); } [Conditional("DesktopUWP")] public void ShowNotification() { var xml = @"<toast> <visual> <binding template='ToastGeneric'> <text>Desktop Bridge</text> <text>The file has been created</text> </binding> </visual> </toast>"; Windows.Data.Xml.Dom.XmlDocument doc = new Windows.Data.Xml.Dom.XmlDocument(); doc.LoadXml(xml); Windows.UI.Notifications.ToastNotification toast = new Windows.UI.Notifications.ToastNotification(doc); Windows.UI.Notifications.ToastNotificationManager.CreateToastNotifier().Show(toast); }
こうすると通常の Debug / Release でビルドしたときはトーストの処理が呼ばれなくなります。
DesktopUWP - Debug
や Release をもとに同じ手順で DesktopUWP - Reelase
とかを作っておくと appx にパッケージングするときに、その構成を選んで行うとトーストが出るようになります。
非同期メソッドを呼ぶには
UWP の API の多くは非同期な API です。そのため await をすることが多いです。
ただ、UWP の API の戻り値は Task ではありません。IAsyncOperator<T>
などになります。
そのため、何も考えずに以下のようなコードを書くと…。
public async Task GenerateWavFileAsync() { var speech = new SpeechSynthesizer(); var result = await speech.SynthesizeTextToStreamAsync("こんにちは世界"); var fileName = System.IO.Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "speech.wav"); using (var fs = File.Create(fileName)) { await result.AsStreamForRead().CopyToAsync(fs); await fs.FlushAsync(); } }
エラーになります。
await するためにはいくつかの条件を満たす必要があるのですが、これを満たしてないということです。 それらは拡張メソッドとして定義されているのですが、その拡張メソッドが定義されているのは以下の dll になるので、それを参照に追加してやる必要があります。
C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll
これでエラーが消えます。この SpeechSynthesizer クラスは appx にしなくても呼べるので ConditionalAttribute はいりません。
まとめ
最近は Windows 10 じゃないと使えない API も増えてきました。 そして Pure UWP じゃなくても UWP の API を使わないといけないケースがあると思います。
そんな時はここの手順を試してみてください。Desktop Bridge を使えば、トーストとかにも対応できます。