プログラマから見たWindows 10 #4 ~ 最新情報とプログラミングTips

開発の橋本孔明です。

「プログラマから見たWindows 10」第4回は、2つの最新情報

  • 「UWP Bridge の進行状況」
  • 「Insider Preview 新ビルドの提供開始」

と、2つのプログラミングTips

  • 「ウィンドウのサイズと枠線についての仕様変更」
  • 「タスクトレイ アイコンのバルーン ツールチップがトーストに統合」

をお届けします。

UWP Bridge の進行状況

前回紹介した「UWP Bridge」シリーズですが、その後「for Android」と「for iOS」に進展がありました。

for Androidについては、クローズドな開発コミュニティが設置され、招待者によるレビューが開始されています。筆者も参加しましたが、内容については守秘対象とされており公開することができないようです。ただ、現在のバージョンでは動作検証にWindows 10 Mobile Insider Previewのインストールされた実機が必要とのことで、MADOSMA(Windows 10 Mobile Insider Previewには非対応)しか所有していない現状では試すことができませんでした。

これに対し、for iOSのほうは積極的な情報公開が行われています。GitHubに「Windows Bridge for iOS」、コードネーム「WinObjc」というリポジトリが設置され、オープンソースでの開発が進められることになりました。現状では制限も多いものの、誰でも動作検証や開発への参加が行える状態です。

こちらを試したところ、純粋にObjective-Cのみで書かれた小さなアプリは問題なく変換することができました。うまくいかなかった例としては、ロジック部を他プロダクトと共通化するため、純粋なC言語(POSIX API)やC++(C++11)、Boost C++ Librariesなどを使って書いていたもので、Windows上で動作するLLVM Clangとの相性が良くないのかビルドが通らない部分が多々見受けられました。このあたりは別のDLLとしてビルド・リンクするように修正する必要が出てくるかもしれません。

Insider Preview 新ビルドの提供開始

すでに製品版のWindows 10にもWindows Updateを通じて各種の更新の提供が行われていますが、Insider Previewプログラム継続者にはさらに新しいビルドの提供が始まっています。

9月1日時点での最新ビルドは8月末に公開された「10532」で、8月中旬に提供された「10525」に続くものです。

目に見える変化もすでに確認されています。例えば10525ではウィンドウ タイトルバーへの着色機能が実装されました。これは以前の連載で「改良してほしい」と書いた部分で、フィードバック プログラムに寄せられた意見を反映したものとのことです。続く10532では日本語版での提供が遅れている音声認識アシスタント「Cortana」の日本語プレビュー版も実装されました。こちらはまだまだ発展途上とのことで対応機能も限られており、正式提供にはまだしばらく時間がかかりそうです。

ウィンドウ タイトルバーへの着色機能

ウィンドウ タイトルバーへの着色機能

新ビルド バージョン情報

新ビルド バージョン情報

音声認識アシスタント「Cortana」の日本語プレビュー版

音声認識アシスタント「Cortana」の日本語プレビュー版

なお、Insider Previewは場合によっては実用に耐えない恐れのある問題も既知の不具合として含まれたまま公開される可能性があります。あくまでプレビューであり、実務PCにインストールするべきものではないことに注意してください。

ウィンドウのサイズと枠線についての仕様変更

まず始めに、Windows Vistaがリリースされたときにウィンドウ関連で話題になった仕様変更の話を復習します。

Windows VistaでAeroGlassを有効にしていると、サイズ固定(リサイズ不可)のウィンドウに対してGetWindowRect() APIを発行した際、ウィンドウの見た目より若干小さい寸法が返ってくるという問題がありました。これは、AeroGlassが有効な場合、ウィンドウ フレームの見た目と内部的なサイズ(GetSystemMetrics() APIで取得するもの)に差が生じるようになったため、XPまでとの互換性を維持するための施策として導入された仕様でした。

この問題の解決策の一つとして、プログラム ファイルのヘッダの対応OSバージョンの数字が「6」以上であれば、GetWindowRect() APIは見た目通りの寸法を返すようになっていました。しかし、当時はまだWindows XPとの相互運用がほぼ必須であったため、この手法は採用しにくく、代わりにVistaで導入されたDWM(Desktop Window Manager)APIを使用する手法が紹介されました。見た目上のウィンドウ領域(位置とサイズ)を取得できるコード(サイズ可変のウィンドウでも動作可能)は下記のようになります。

RECT rect;
DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect));
  • DWM APIの使用には、dwmapi.hのインクルードとdwmapi.libへのリンク指定が必要です
  • AeroGlassが無効(Windows クラシック スタイル)となっている場合には動作しないので、条件分岐が必要です
  • Windows XPには搭載されていないため、XP対応の場合はDLLへの動的リンクが必要です

ウィンドウ サイズが可変の場合は上記の問題が発生しないため、結局当時もそれほど大きな混乱を生じることはありませんでした。また、最近ではVisual Studio 2012以降のツールセットを用い、Windows XPへの対応を考慮しないプログラム開発が主流となってきました。この場合、プログラム ヘッダの対象OSバージョンは自動的に6以降となるため、上記の問題そのものが発生せず、特に気にしなくても良くなっていました。

ちなみに、Visual Studio 2012以降の場合、プロジェクトのプロパティで「プラットフォーム ツールセット」を「Visual Studio 2012 – Windows XP (v110_xp)」のようなXP対応バージョンに切り替えると、Windows 7やWindows 8.1においても上記の問題(固定サイズ ウィンドウで、見た目とGetWindowRect()でのサイズが一致しない)を確認することができます。

さて、Windows 8.1までは、ウィンドウの外枠はある程度太い見た目になっていました。Windows 10では、外見上のウィンドウの外枠は1ドットだけになってしまいましたが、その外側数ドットまでがサイズ変更カーソルの当たり判定となっていますので、枠が掴みにくいといった操作性の低下はありません。

ここで、ごく普通にCreateWindowEx() APIを用いて、デスクトップ左上隅から縦100ドット・横100ドットの位置に400×300ドットのサイズ可変ウィンドウを2枚並べて作成するプログラムを作成し、Windows 7および8.1で動かしてみます。すると、隙間なく並んだ2つのウィンドウが作成されます。GetWindowRect() APIでウィンドウ サイズを取得すると、いずれも400×300ドットとなり、これはAlt+PrintScreenキーを用いてウィンドウをキャプチャした場合に取得される画像のサイズとも一致しました。ところが、Windows 10で動かしてみると、GetWindowRect()の結果は400×300ドットのままですが、Windows 8.1よりも少し小さな見た目のウィンドウが作成され、隙間が開いて並べられます。左上の座標も(100, 100)にはなっていないようです。Alt+PrintScreenキーでこのウィンドウをキャプチャしてみると、386×293ドットというサイズになりました。どうやら、ウィンドウの見た目上の外枠が1ドットになっても、カーソルの当たり判定がある部分を含めた寸法をウィンドウ サイズとして計算されているようです。

Windows7のウィンドウサイズ

Windows 7のウィンドウサイズ

Windows8.1のウィンドウサイズ

Windows 8.1のウィンドウサイズ

Windows10のウィンドウサイズ

Windows 10のウィンドウサイズ

では、この「外見上の寸法」である386×293という値はどうやって取得すればよいのでしょうか。そうです。ここで先ほど復習した、DWM APIを用いた手法が再度活躍します。

さきほどのプログラムを修正し、ウィンドウのクライアント領域のサイズおよびウィンドウの位置とサイズ(GetWindowRect() API)に加え、DwmGetWindowAttribute() APIで取得した情報を表示するようにしてみます。クライアント領域のサイズがわかりやすいよう、緑色での着色も行いました。
これをWindows 7・8.1・10でそれぞれ実行した結果が下記となります。

Windows7_DWM APIの手法で取得したウィンドウのクライアント領域のサイズおよびウィンドウの位置とサイズ

Windows 7での実行結果

Windows8.1_DWM APIの手法で取得したウィンドウのクライアント領域のサイズおよびウィンドウの位置とサイズ

Windows 8.1での実行結果

Windows10_DWM APIの手法で取得したウィンドウのクライアント領域のサイズおよびウィンドウの位置とサイズ

Windows 10での実行結果

DwmGetWindowAttribute() APIを用いれば、Windows 10においても外見上のウィンドウサイズが取得できることがわかります。また、位置についてはX方向のみがフレームの寸法変更に応じてオフセットし、クライアント領域のサイズは7から10まで変わっていないこともわかります。おそらく、これこそが仕様変更の目的で、7や8.1から10にアップグレードした際、従来インストールされていたアプリのウィンドウ サイズ復元処理が働いても、ウィンドウの表示内容であるクライアント領域の寸法が変わらないようにしたかったのではないでしょうか。

なお、確認した限りでは、Win32 APIだけではなく、.NET Framework上のWinFormsやWPFでウィンドウ サイズを指定した場合も同様の結果となりました。UWPアプリについては未確認です。

このように、Windows 10では、従来の手法でウィンドウサイズを指定すると、設定しようとした値と外観に違いが生じることになり、しかもサイズ可変ウィンドウも対象となるため、本現象に遭遇するケースが多発しそうです。任意のウィンドウを隙間なく並べたり、別のウィンドウにくっつく形で表示したりといった動作をWindows 10上でも実現する場合には特に対応が必要となるでしょう。

なお、残念ながら、いまのところ外見上のサイズを指定して一発でウィンドウのサイズ・位置を指定できるようなAPIは用意されていないようです。このような場合、DwmGetWindowAttribute()とGetWindowRect()の結果を計算して差分を算出し、その結果でMoveWindow()やSetWindowPos()などのAPIを呼び出す必要があります。

タスクトレイ アイコンのバルーン ツールチップがトーストに統合

Windows 8.1までのタスクトレイ アイコンには、吹き出し形のツールチップ(バルーン チップ)を表示する機能がありました。簡単な操作で呼び出せるため、特に常駐系アプリからの情報通知に多用されています。

Windows7のバルーンチップ

Windows 7のバルーンチップ

Windows8.1のバルーンチップ

Windows 8.1のバルーンチップ

Windows 8からは、新たな通知手法として「トースト」が追加されました。トーストは主にストア アプリからの呼び出しを想定したものですが、Win32 ネイティブ アプリからの呼び出しも少し複雑ながら可能です(方法は今回は省略します)。

Windows8.1のトースト

Windows 8.1のトースト

Windows 10においては、この両者の外観および動作がある程度統一されました。Windows 8.1まではトーストは画面右上に表示されましたが、Windows 10では画面右下に出るように変更され、またバルーン チップもトースト型の外見で表示されるようになりました。効果音もトースト用のものが使用されます。

Windows10のバルーンチップ

Windows 10のバルーンチップ

Windows10のトースト

Windows 10のトースト

この仕様変更、操作や互換性の上ではそれほど問題を生じることもないと思われますが、例えば説明文に「このバルーンをクリックして~」といったような文言がある場合、Windows 10上では違和感を生じることになります。「この通知を~」などの文言にするのが良いでしょう。

タグ , , , , | 2020/06/16 更新 |