2のファンが、ついにというか、時々軸音を発するようになったので、どんなものか見てみたら埃まみれになっていたので、外してみた。2の筐体はバックパネルの部分が外せそうだとは分かっていたが、実際に外したことはなかったので、自分にしてはやや慎重に4本のネジを外すと、パカっと。以下は、既に埃を落とした状態。SATAコネクタのあるバックプレーンを裏側から見たことはなかったので、少し新鮮。このPCBに"FOXCONN"の文字があったので、改めて見たらメインのPCBにも"FOXCONN"の文字が入っていて、ついでにこれらを繋ぐコネクタもFoxconn製で、つまり高い確率でReadyNASの製造はFoxconnだったんだと、今更ながら了解。Hai製の電子製品は日常生活の至るところに溢れているわけだが、その一つだったと。ファンの方はDeltaのAFB0912HHで、それは初めから見えていたのだが、このブレードの排気側の縁が、くいっと曲がっているのに初めて気づいた。静音上の意味があるのだろうが、後発メーカーが先発メーカーとの差別化のために変わったことをするのは珍しくないにしても、ファンにかけては老舗のDeltaが、しかも直接ユーザーにアピールする必要のないアプライアンス向けの製品でこういう工夫をしているのは、正直意外だった。それはそれとして、掃除した結果、また静かな状態に戻ったので満足。交換用に用意していたファンの出番は、結局なしで終わりそうな気がしてきた。DPIの要素のうちWM_DPICHANGEDメッセージに変更があったので、これへの対応について。WM_DPICHANGEDが来るタイミングRTMでは、OSからWM_DPICHANGEDが送られてくるタイミングが以下のように増えている。FormのLoadイベントが起こる前(おそらくコントロールが初期化される前)ウィンドウが属するモニターのDPIが変更されたときウィンドウがモニター間をまたいだとき(常に)1.は初期化時にDPIが分かった方がいいというケースのためかと思うが、Loadイベントを起点にスケーリング処理を開始するアルゴリズムの場合、スルーするようにする必要があるかもしれない。2.は純粋な改善で、モニターのDPI変更をトリガーとして、直ちにアプリがスケーリングすることが可能になった。3.が本題で、Previewではモニター間をまたぐときに(途中でウィンドウをリサイズしても)1回しかWM_DPICHANGEDは来なかったが、RTMではまたぐ度に忠実に来るようになった。これが何を意味するかというと、自分が観察した限りではOSはウィンドウとモニターの重なり合う面積が最大のものをそのウィンドウが属するモニターと見なし(Win32のMonitorFromWindow関数と同じ)、これに従ってWM_DPICHANGEDを送ってくるようなので、以下のループが発生し得る。ウィンドウがモニター間を移動するとき、移動元のモニターに重なる面積と、移動先のモニターに重なる面積を比べて、後者が大きくなったとき、すなわち属するモニターが変わったときにWM_DPICHANGEDが来る。これを受けて直ちにウィンドウをリサイズすると、リサイズはウィンドウの位置(左上角の位置)を固定して行われるので、移動元のモニターに重なる面積と、移動先のモニターに重なる面積が変化し、前者が大きくなることが起こり得る。結果として、属するモニターが移動元のモニターに一旦戻る形で変わるので、再度WM_DPICHANGEDが来る(以後、これが起こらなくなる位置までウィンドウが移動するまでループ)。Previewではこのループが起こらないようOS側でWM_DPICHANGEDを抑止していたのではないかと思うが、それでは何か不都合があったのか、RTMではアプリ側に対応を投げてきたということだと思う。いずれにせよ、このループを回避するには、ウィンドウをリサイズしたときに移動先のDPIと同じになるような方策が必要となる。具体策やり方は色々あり得るが、大きく二つに分けられると思う。リサイズ後に移動先のDPIと同じになる位置に積極的にウィンドウを移動させた後、リサイズする。リサイズはせず、リサイズ後に移動先のDPIと同じになる位置までウィンドウが移動するまで待ってから、リサイズする。即時型の方がフラグ管理が不要なのでシンプルになるが、ドラッグ中にウィンドウの位置が飛ぶのは嬉しくないかもしれない。したがって、基本は遅延型になると思うが、ドラッグ中でない静止時には使えないので、ケースバイケース。遅延型まず遅延型のアルゴリズムの例。ウィンドウの移動開始(Form.ResizeBegin)と移動終了(Form.ResizeEnd)イベントを捉えて、移動中か否かをチェックできるようにしておく。WM_DPICHANGEDが来たときは、それに含まれる移動先のDPIと、ウィンドウの現在のDPIを比べ、これが違うときで、移動中のときは待機に入り、移動中でないときは直ちにリサイズする。逆にDPIが同じときは、待機を解除する。ウィンドウの移動(Form.Move)イベントを捉え、待機中のときは、その位置でウィンドウをリサイズした場合と同じ長方形を生成する。この長方形が属するモニターをMonitorFromRect関数で得て、スクリーン外でないことを確認の上、そのモニターのDPIをGetDpiForMonitor関数で得る。これが移動先のDPIと一致するときはリサイズを実行し、待機を解除する。DPIが一致しないときはそのまま待機を続ける(以後、DPIが一致する位置に移動するまで繰り返し)。待機中に移動元のモニターに戻ったときは、またWM_DPICHANGEDが来るので、2.によって待機は解除される。dpiOldは現在のウィンドウのDPI(この値は別途入れている)、dpiNewはWM_DPICHANGEDに含まれるDPI、AdjustWindowはウィンドウをリサイズするメソッド、W32はWin32のP/Invokeのためのクラス。Functionリサイズした場合の長方形の生成時には、タイトルバーを含めてクローム部分はDPIによって変わらないので、クライアント領域の分だけ元のウィンドウからサイズを増減しないと微妙にズレが出る点に注意。次に即時型のアルゴリズムの例。WM_DPICHANGEDが来たときは、現在のウィンドウの4つの角のそれぞれについて、その位置でウィンドウを固定してリサイズした場合と同じ長方形を生成する。この4つの長方形が移動先の候補となる。この長方形が属するモニターをMonitorFromRect関数で得て、スクリーン外でないことを確認する。この長方形の左上角と右上角のそれぞれについて、その位置を含むモニターをMonitorFromPoint関数で得て、少なくとも一方がスクリーン外でないことを確認する(タイトルバーがスクリーン外に行かないようにするため)。長方形が属するモニターのDPIをGetDpiForMonitor関数で得る。これがWM_DPICHANGEDに含まれる移動先のDPIに一致するときは、この長方形の位置にウィンドウを移動させた後、リサイズする。この4つの長方形の模式図。イメージ的には固定された角の対角にある角が移動して縮小・拡大する感じ。これらを候補とするのは、モニターの位置関係、ウィンドウの移動方向、DPIの大小関係にかかわらず、この中に必ず移動先のモニターに属するものがあると思われるから(厳密に数学的に正しいかは知らない)。移動先のモニターと同じDPIのモニターが他にもある場合、そのモニターに属する長方形となる可能性もあるが、同じDPIならそのまま移動できるので問題にはならないと思う。これらを調べる順番は、左上角に近い方から進めた方がタイトルバーが移動する可能性が減るので、ユーザーのストレスを抑えられると思う。変数名などは遅延型と同じ。SubP/Invokeが多いのは、標準にはないことをしている以上、まあ仕方ない。デモアプリ以上を引っくるめて修正したデモアプリ。VBに加えてC#でも作成した。(LOGPIXELSX)"は常に同じ(はず)。8.1では、"GetDpiMonitor"にこのウィンドウが属しているモニターのDPIが表示される。(Latest)"に最新のメッセージに含まれるDPIが表示される。中央のボックスにはWM_DPICHANGEDが来た時刻、そのwParamに含まれるDPI、lParamに含まれるRECTの位置・サイズが表示される。VB版でDEBUGビルドすると、追加情報も表示される。ラジオボタンで即時型"Immediate"と遅延型"Delayed"の切り替えができる。遅延型の場合、その横に現在の待機状況が表示され、"Waiting"が待機中、"Resized"がリサイズしたこと、"Aborted"が待機に入ったが取りやめたことを示す。Button"は動的にコントロールを作成する例として。通知領域のあるモニターのDPIモニター間の移動によるものでない、モニター設定によるDPIの変更は、そのモニターに属するウィンドウのみにWM_DPICHANGEDで知らされる。それはそれで正しいのだが、通知領域アイコンを置いている場合で、アイコンの属するウィンドウが通知領域のあるモニターに現在表示されてない場合、そのモニターのDPIに変更があってもWM_DPICHANGEDは来ない。具体的には、そのウィンドウを最小化している場合や別のモニターに表示させている場合で、こういう場合は通知領域アイコンに付けたメニューなどをリサイズするトリガーがないことになる。何かのタイミングに引っ掛けて、例えばそのメニューが表示されるときのイベント(ContextMenuStripのVisibleChangedイベントなど)を捉えてDPIをチェックする方法が考えられるが、統一性には欠ける。DPI"となっている。ウィンドウのリサイズに利用できそうな感じがするが、実際に来ているものを見ると、関係があるっぽいのは分かるが正確に何を意味しているのかは分からないという......。別になくても困らないが、謎。[追記]通知領域のあるモニターのDPIに関する説明を増やした。Communitywww.flickr.comコメントについてコメントは承認制です。承認まで時間がかかるときがあります。感想、間違いの指摘など歓迎します。間違いについては、その場所と内容を明記してください。エントリと関係がないもの、不適当と思われるものは承認しません。プロフィールEMOGitHub詳しくないプロフィール当てにならないカスタマイズ。