ホーム  ざれごと  ワシントン州  ツール  NT豆知識  Win32プログラミングノート  私的用語  ジョーク  いろいろ  ゲーム雑記  Favorites  掲示板   Mail

よくあるよくある

Last modified: Tue Dec 09 00:04:27 2003 PDT

一つ上へ

巷で見聞きした、あるいは自分で体験した「よくある」な感じの事柄をつれづれに。
あぁもっと早く始めておけばよかったよ。 ネタがたくさんあったろうに(笑)。

マルチモニタ

プログラマならマルチモニタの恩恵をよく知っていることだろう。 古めのディスプレイを拾ったりもらったり、最近のビデオカードはマルチヘッドだし、古い PCI のカードをどうにかして手に入れたりして自宅でも会社でもマルチモニタ環境。 私もそのうちの一人で、自分で書くコードのバグを知るためにも少々変なマルチモニタ環境にしている。

左右二つのモニタを並べている。 左が 17" CRT で、解像度は 1280x1024。 使っているビデオカードと可読性(ドットの大きさ・リフレッシュレート)の兼ね合いだ。
右は 20" LCD で、解像度は 1600x1200。 こちらは DVI なので非常に美しい。 もちろんこちらがメインのモニタ(プライマリ・モニタ)である。 タスクバーもこちらにある。 このほうが便利だからでもあるが、会社では左側のモニタにタスクバーを置き、一目でどこにいるのか分かるように…って、これは説得力がないか(笑)。
その上で、プライマリ・モニタのX原点が 0 になるように環境を設定している。

文章で書くよりコントロールパネルを見てもらったほうが早いだろう。 ということで、上を参照。 なお、モニタの配列に段が付いているのは、実際にモニタの台座の高さが違うためである。 この位置関係にすると、マウスの移動がもっとも自然なようだ。

なお、物理的な配置はこうである。

ウィンドウを中央に置く

WinDVD が、メインモニタの右寄りにウィンドウをオープンする。 起動時はいいのだが、再生が始まるときにウィンドウの位置・サイズを調整するらしい。 正確に言うと、右寄りというより右端に引き寄せられてそのまま突っ切ったと言おうか、はっきり言ってはみ出してしまっている。 仕方がないのでフルスクリーンにするか、毎回ウィンドウの位置を手で直さざるを得ない。

どうコーディングされているのか想像してみた。 全デスクトップの幅を取得あるいは計算して、その中央に位置させようとしているのではないだろうか。 残念ながら、私のデスクトップはマイナス座標から始まる。 (-1280, 0) - (1600, 1328)。 おまけにメインのモニタは右側なのである。 「幅」を元に座標を計算するということは、暗黙のうちに原点は0である=モニタの左端は座標で0を示す、という仮定がなされている。
昔からある、GetSystemMetrics(SM_CXSCREEN) はプライマリモニタの幅を返すだけだ。 全てのモニタをカバーする「デスクトップ」の全幅を取得するには、比較的新しい SM_CXVIRTUALSCREEN を使わなければならない。 つまりWinDVD が全くマルチモニタのことを知らないというわけではないのではないか。 全くマルチモニタのことを知らなければ、というかマルチモニタをまったく意識せずにプログラミングされていれば、ウィンドウが右によってしまうということは起きないだろうと思われるが、真実や如何に。
最も左上に位置するモニタが (0, 0) にあれば問題はないのだが。 やはり単なるテスト不足なのだろうか。

ではマルチモニタ環境では、負の座標にあるモニタも含めて中央のポジションを出すことが正解だろうか。
(なお、SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN でデスクトップの原点を取得できる)
否。 単純に中央に位置させてしまうと、ウィンドウが二つのモニタにまたがってしまう可能性が高い。 それでは使いにくいし、ハードウェアアクセラレーションも効かないだろう。 描画に使われるデバイスコンテクストのほか、内部的にはそれぞれのモニタに出力するためのデバイスコンテクストも用意する必要があるのだ。 そこで、どちらのモニタに出力するべきか考えなくてはならない。
というわけで、マルチモニタに正しく対応するのは結構面倒なのである。 ただ、MonitorFromPoint や MonitorFromWindow など、こちらの代わりに判断を行ってくれる API もある。 使いましょう。

さて、WinDVD はどこまで真面目にマルチモニタ対応を考えたのだろうか? 考えた節もある。と思う。 だが、簡単に実現可能なコンフィグレーションをきちんとテストされているようには見えない。

マルチモニタ対応でいえるのは、中途半端な対応は使い勝手を悪くするかもしれないということだ。 へたにマルチモニタ対応にするぐらいなら何もしないほうが良い、ということもありうる。 もちろん、負の座標で致命的誤動作さえ起こさなければ、という条件がつくが。

負の座標

とある有名な FTP クライアントを愛用している。 操作性もよく動作も速く安定していて申し分ないのだが、唯一マウスのホイールが使えればいいな、と思っていた。 あるときソースコードが公開されているのを知り中身を見てみると、ちゃんとホイール対応のコードが入っている。

ではなぜ私のマシンではホイールが使えなかったのか。 それは、私がそのウィンドウを左側のモニタで主に使っていたためである。 このソフトウェアは優秀で、前回のウィンドウの位置を記憶している。 そのため、一度左側のモニタで使い始めるとずっとそこで使うことになる。 実際、右側のメインモニタで文書を書き写真のレタッチをやり、左側のウィンドウで FTP でアップロードなど補助的な作業をするのは結構効率がいいのだ。

残念なことに、ほんのちょっとしたことで負の座標では動作しなくなっていた。
元のコードはこんな感じに書かれていた。

case WM_MOUSEWHEEL:
    Point.x = LOWORD(lParam);
    Point.y = HIWORD(lParam);
    hWndPnt = WindowFromPoint(Point);

WM_MOUSEWHEEL メッセージでは、lParam の上位ワードにX座標が、下位ワードにY座標が入っている。 上記のコードは、lParam から X, Y 座標のそれぞれを取り出し、マウスの直下に位置するウィンドウを取得しようとしているわけだ。

ここでのちょっとした陥穽は、LOWORD, HIWORD ともに unsigned の値を返すというところにあった。 つまり、lParam には負の座標が収められているが、それを unsigned として見ると大きな座標として解釈されてしまうわけだ。
ということで、これらの値を (short) などにキャストしてやって見事動くようになった。 もとのコードが良くできているので、たったこれだけの修正できちんと動作したわけだ。

case WM_MOUSEWHEEL:
    Point.x = (short)LOWORD(lParam);
    Point.y = (short)HIWORD(lParam);
    hWndPnt = WindowFromPoint(Point);

さらにもう少し見てみよう。 MSDN で WM_MOUSEWHEEL をサーチすると出てくるが、最近の winuser.h には、lParam から X, Y 座標を取り出すためのマクロが用意されている。 これを使うのが将来的(64ビット,128ビットとか)にもよさそうである。 マクロを使って書き直すとこうなる。

case WM_MOUSEWHEEL:
    Point.x = GET_X_LPARAM(lParam);
    Point.y = GET_Y_LPARAM(lParam);
    hWndPnt = WindowFromPoint(Point);

余談だが、もちろんこの件は作者にメールした。 マルチモニタ環境がないため気づかなかったそうだ。 好意的に受け取っていただいて助かった。


Since 1996

一つ上へ

ホーム  ざれごと  ワシントン州  ツール  NT豆知識  Win32プログラミングノート  私的用語  ジョーク  いろいろ  ゲーム雑記  Favorites  掲示板   Mail