1. 条件
このエントリへの質問に対するRohit Agrawal(WPFチームの人)の回答と
- .NET Blog: Announcing .NET Framework 4.6.2
まとめると、Built-in Scalingの前提条件は以下のとおりです。
- OSがWindows 10 Anniversary Update(Redstone 1)以降であること
- Target Frameworkが.NET Framework 4.6.2以降であること
- マニフェストのdpiAwarenessがPerMonitorであること
つまり、Windows 8.1については従来どおり独自対応で行くしかないという方向で確定と。まあMicrosoft全社挙げてWindows 10推しの現況では、それ以前のOSに対してリソースの割り当てがないのだろうと思います。今となってはWindows 8.1は過渡期のOSであることは否定できない感じはします。
次に新しいdpiAwarenessについて。従来のdpiAwareに対して、新しくマニフェスト(app.manifest)に導入されました。この二つは併用が可能で、OSがWindows 10 Anniversary Update以降で、かつdpiAwarenessが指定されていれば、この指定の方が有効になる、ということのようです。
例として、サンプルでの指定方法。
上のdpiAwarenessではPer-Monitor DPI Aware、下のdpiAwareではSystem DPI Awareの指定になっています。この場合、コメントにあるとおり、Windows 10 Anniversary Update以降ではPer-Monitor DPI Awareとなりますが、Windows 8.1ではdpiAwareのみが効いてSystem DPI Awareとなり、仮想スケーリングがかかります。
なぜわざわざdpiAwarenessを追加したのかを推測するに、dpiAwareでtrue/PM(またはPer-Monitor)で指定する方法のままだと、(独自対応なしでは)Windows 8.1上で全くスケーリングがかからなくなってしまうので、それを避けたかったのかなと思いますが、何か要らぬ手間がかかっている気がします。
また、これらの基本条件に対して、コンフィグ(App.config)の方でSwitch.System.Windows.DoNotScaleForDpiChangesを指定すると、その値によって設定をオーバーライドできます。
- True - 上記の条件を満たしていてもBuilt-in Scalingを無効にする
- False - Target Frameworkが4.6.2より前であってもBuilt-in Scalingを有効にする
これの使いどころとしては、Target Frameworkは4.6.2に上げたくないけど、Built-in Scalingは使いたいような場合でしょうか。DPIに応じて変化するような独自コントロールがあるのでもなければ、4.6.2の機能がなくても困りませんし。
条件としては以上です。
2. 独自対応との兼ね合い
結局、Windows 8.1上で仮想スケーリングに甘んじたくなければ、引き続き独自対応が必要になります。あるいは、Windows 10以降も独自対応で通したい場合もあるかもしれません。
独自対応との兼ね合いでありそうな問題としては、
- Built-in Scalingが不要な場合の抑止
これは実は簡単で、WM_DPICHANGEDが来たときのハンドラー内で「handled = true」として処理済みにしてしまえば、Built-in Scalingは発動しないようです。
- DPI変化の伝播ルートの統一
.NET Framework 4.6.2より前では、VisualTreeのルートたるWindowから傘下のコントロールにDPI変化を伝えるためには、独自に伝播ルートを張る必要がありました。これが4.6.2以降でBuilt-in Scalingが発動したときは、各コントロールのOnDpiChangedメソッドかImage等のDpiChangedイベントで自動的に伝播されます。
であれば、独自対応の場合でもこのルートを利用した方が楽ですが、この伝播はVisualTreeHelper.SetRootDpiメソッドで発生させることができます。したがって、独自対応でスケーリングさせると同時に、これを実行するようにすることで伝播ルートを統一できます。
自作ライブラリでは、XAMLから指定可能なWillForbearScalingIfUnnecessaryプロパティでこれが可能なようにしています。
3. Non-client areaの新API
やや蛇足になりますが、Anniversary Updateの後にNon-client area(NCA、ウィンドウの枠のクローム部分)のスケーリングに関してエントリが出ています。
- Ask the Core Team: Display Scaling changes for the Windows 10 Anniversary Update
WPFに関しては、
For the Windows 10 Anniversary Update WPF is being updated to support automatic NCA scaling.
とありますが、実際に試してみると「いや、効いてないし……」という状態。
そこで、NCAのスケーリングを有効にする新APIのEnableNonClientDpiScalingを実行しようとすると、WPFでウィンドウハンドルを取得できるタイミングとしてはおそらく最早のWindow.OnSourceInitializedメソッド内では成功しないし、エラーコードにも有意な情報がなく。
以下のスレッドのコメントによれば、WM_NCCREATEが来たときのハンドラーで実行すればいいらしいですが、
WPFでウィンドウメッセージを捕捉するにはウィンドウハンドルが必要なので、いずれにしてもWindow.OnSourceInitializedメソッドより早くはできなくて、このときにはこのメッセージは通り過ぎた後らしくて捕捉できない、と手が出せない状態。
まあ出せるものから出していくという姿勢は否定しませんが、既にとっ散らかる兆候を見せているので、出来上がったときにはなるべくまとまった形になっているといいな、というのがささやかな希望です。
[追記]
EnableNonClientDpiScalingの効果は、WinFormsであれば簡単に確認できます。