エクスプローラのビュアの拡大機能についてわかったこと

  • 上にマウスホイールを回すと拡大する
  • ウィンドウに表示しきれなくなるとスクロールバーが付く
  • ホイールボタンを押しながらカーソルを動かすと移動できる(スクロールバーが付いてる時)
  • 画像のサイズはホイールを回すごとに1.2倍になる(コントロールパネルのスクロールする行数の設定に関係なく)
  • 原寸大表示から16回以上は拡大できない(=1.2^16≒18倍以上拡大できないということ?)
  • 縮小はできない(窓ぴったりには縮小してくれるけど、それより小さくはできない。)

スライドショーやら画像の回転やらも付いてますが、果たしてこれを超えるものが作れるのか。

09/03/17

最小化してからもとのサイズに戻すと、画像が表示されるまでに時間がかかることがある。
ただし元々ウィンドウより小さい画像の場合ではすぐに表示される。
つまりいちいち縮小しなおしているらしい。StretchBltを使ってるのだと思う。

ミューティックスの所有権を同じスレッドから何度も得ようとするとどうなるか

題名からしてわかりにくいので、まずはそれについて説明。
Mutex(ミューティックス)は同期オブジェクト(synchronization object)の一つです。
これの所有権を得るにはWaitFunctionsと呼ばれる関数(*1)を呼び出し、所有権を解放するときにはReleaseMutex関数を呼び出します。
じゃあこの待機する関数(WaitFunctions)を、同じmutexに対して2回呼び出してしまったらどうなるか。
例えばある関数が所有権を得て、さらにその関数が呼び出した別の関数でも所有権を得ようとした場合など。

結論

どうなるか? → 何回呼んでも所有権を得られる(限界はあるだろうけど)
解放の方法 → 待機する関数(WaitFunctions)を呼んだ回数だけReleaseMutex関数を呼び出す
これ以上に簡単な説明は無いです。

少し詳しく

既にそのスレッドが所有権を得ているmutexを指定して、さらに待機関数を呼ぶことができます。
なので既に自分に所有権のあるmutexを、解放されるまで待とうとして固まってしまうことはない。
だけど所有権を解放するために、スレッドはReleaseMutex関数を、待機関数を呼び出した数だけ呼ばなければいけません。
要はWaitForSingleObject関数やWaitForMultipleObjects関数を呼んだ数だけReleaseMutexも呼ばなければいけない。
(CreateMutex関数で、作った瞬間に所有権を得た場合はそれも数える。)
(MSDN EN Mutex Objects)

簡単な話、ブロックの初めと終わりに所有権を得る関数と解放する関数を置けばいい。(そして最後にはCloseHandleする。)
ただしその場合、ブロックの途中でbreakやreturnしてはいけないので、そうではなく、デストラクタで自動的に解放処理を行ってくれるクラスがあるといいです。
そんなことがEffective C++ 第3版に書いてありました。また、mutexそのものや同期オブジェクトについての情報は「APIで学ぶWindows徹底理解」という書籍もどき(いわゆるムック)から。

APIで学ぶWindows徹底理解 (日経BPパソコンベストムック)

APIで学ぶWindows徹底理解 (日経BPパソコンベストムック)

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

*1:SignalObjectAndWait, WaitForSingleObject, WaitForSingleObjectEx, WaitForMultipleObjects, WaitForMultipleObjectsEx, MsgWaitForMultipleObjects, MsgWaitForMultipleObjectsEx関数がある

マウスのホイールの回転とWM_MOUSEWHEELウィンドウメッセージ

わりとメモのような位置づけです。
ソースはMSDN。正確でより詳しい情報を知りたい場合はそちらをお読みください。

概要

WM_MOUSEWHEELはマウスホイールが回されたときにフォーカスを持つウィンドウに送られる。
DefWindowProc関数(デフォルトウィンドウプロシジャ)は、それの親ウィンドウにメッセージを知らせる。

wParam

上位ワードはホイールが回転した距離(回転量)を示す。WHEEL_DELTA(120)の倍数の形で表される。
正の値は前(奥)、負の値は後ろ(手前)へ回された事を意味する。
(wParamの上位ワードをWHEEL_DELTAで割れば、何回「カクッ、カクッ」と回されたかがわかる・・・らしい。要検証。)

下位ワードはどのキーが押されてるかを示す。

MK_CONTROL Ctrlキー
MK_LBUTTON 左のマウスボタンが押されてる
MK_MBUTTON 真ん中のマウスボタンが押されてる
MK_RBUTTON
MK_SHIFT Shiftキー
MK_XBUTTON1 Windows 2000/XP: 最初のXボタンが押されてる (?)
MK_XBUTTON2 Windows 2000/XP: 次のXボタンが押されている (?)

上位/下位ワードを分けるマクロもあります。

fwKeys = GET_KEYSTATE_WPARAM(wParam);
zDelta = GET_WHEEL_DELTA_WPARAM(wParam); // この値は負になることもある

fwKeysはintかDWORDかunsigned shortがいいのかな。
zDeltaは負になることもあるので、intがいいかな。

lParam

マウスカーソルの、スクリーンの左上からの相対座標。
下位ワードはX座標。上位ワードはY座標。便利なマクロもあります。

xPos = GET_X_LPARAM(lParam); 
yPos = GET_Y_LPARAM(lParam); 

xPos, yPosはint型がいいのかな。
また、MAKEPOINTSマクロを使えばlParamをPOINTS構造体へ変換できます(後述)。

戻り値

アプリケーションがこのメッセージを処理したときにはゼロを返すべき。

ウィンドウ上のマウスの座標を求める方法(09/02/27)

万能なやり方
#include <assert.h>
#include <windows.h>

// 指定したウィンドウ上でのカーソルの座標を得る
bool GetClientCursorPos( HWND hWnd, POINT *dest)
{
	assert(hWnd);
	assert(dest);

	if(GetCursorPos( dest)) {
		if(ScreenToClient( hWnd, dest))
			return true;
	}
	return false;
}

これはWM_MOUSEWHEELメッセージが来た時以外でも使える万能なやり方です。
そのままコピペしないで、もうちょっといいエラーチェックを行ってください。

WM_MOUSEWHEELメッセージが来たとき

MAKEPOINTSマクロは

#define MAKEPOINTS(l) (*((POINTS FAR *)&(l)))

と定義されています。lParamがカーソル座標を表すので以下の通りにできます。

POINTS cursors;			// 画面上のカーソル座標
cursors = MAKEPOINTS( lParam);	// これは実際には構造体同士のコピー
POINT cursor;			// 特定のウィンドウ上のカーソル座標
cursor.x = cursors.x;
cursor.y = cursors.y;
ScreenToClient( hWnd, &cursor);

ちなみにPOINTS構造体とPOINT構造体は別物です。
POINTSはX,Y座標それぞれがSHORT型です。だからポインツではなくポイントエスとかポイントショートとか読んだほうがいいのでしょうか。英語の人は好き勝手に読むらしいですけど。
POINT型はそれぞれLONG型です。だから常にPOINTS型はPOINTに変換できると。多分。
この二つの構造体はWinDef.hに、MAKEPOINTSマクロはWinGDI.hで定義されています。

これら2つのやり方の違い

上のほうのやり方は「その瞬間のマウスカーソルの位置」を知りたいときに使い、
下のほうは「WM_MOUSEWHEELメッセージが渡された瞬間の位置」を知りたいときに使ってください。
実際、下のやり方と上のやり方を同時に使って値を比較すると、ずれる事が多々あります。
(メッセージが来た瞬間と、GetClientCursorPosを呼ぶ間にマウスが動かされた場合に)

基盤は出来上がりました

アニメGIFを表示できるようになり、ドラッグアンドドロップにも対応して、これから感覚で操作できる自由な拡大機能を実装する予定。スクロールで拡大縮小、原寸大表示(クリックしたままドラッグすることで移動できる)など。
画像を表示している部分がディスプレイの外にはみ出したときや、他のウィンドウが重なったとき、リサイズしたときに極端に重くなっていたのですが、それはダブルバッファリングにしてみたらあっさり解決しました。
比を保ったまま拡大,縮小できるようにしたら一つのサンプルとして公開することにします。
この、何度もヤスリをかけて「完成」に可能な限り近づける芸術に近い作業が大好きです。主にバグ取りですが。


このお馬さんは軽井沢の牧場っぽいところのだったと思います。顔ながっ!って感じだった。
ウィンドウいっぱいに表示するため、もっと細長くする事も可能ですが、明日には比を保ったままの拡大,縮小しかできなくなります。

マルチスレッドSDIとは何なのか

SDIについて

WTLのアプリケーションウィザードでSDI, MDI, マルチスレッドSDI, 他色々を選択できます。
IT用語辞典 e-Wordsによれば、SDIはメモ帳で、MDIはExcelのようなウィンドウです。Excelは1つの親ウィンドウが、複数の子ウィンドウそれぞれに1枚ずつシートを表示させていますが、メモ帳は1つのウィンドウにつき1つのテキストファイルを表示するだけです。
SDIとMDIは、見た目にはそれだけの違いだと思います。その違いで操作方法も少し変わりますけど。

マルチスレッドSDI(日記調)

WTLのアプリケーションウィザードで、マルチスレッドSDIアプリケーションの、ビューの種類を通常ウィンドウにしてプロジェクトを作成した。その結果、C+プロジェクト名+Viewという名の付いたクラスが作られていた。どうやらそのウィンドウがビューウィンドウ(*1)らしい。
フレームウィンドウはいくつも作ることができるようだ。つまりビューは、1つのプロセスでいくつも作れる。フレームウィンドウを作る際には「スレッドマネージャ」と呼ばれるクラスのインスタンスを経由する。


「スレッドマネージャ」のインスタンスはWinMainで1つだけ作る。それのAddThreadメソッドは、呼び出されるごとに1つのスレッドを作る。そのスレッドは1つのフレームウィンドウを作り、そのウィンドウが消えるまで待つ。ウィンドウが消えると、待っていたスレッドも即座に終了する。「スレッドマネージャ」のRunメソッドはそれらのスレッドのどれか1つが終了するのを待っていて、スレッドが終了したらその事後処理を行う(スレッドハンドルを閉じる)。もしその時点でスレッドが1つも無くなったら(=ウィンドウが1つも無くなったら)、プロセスそのものが終了となる。

これは、IE6の事がそのまま当てはまる。

  1. ダブルクリックやスタートメニューから起動すると、IEXPLORER.exeというプログラムが起動する。
  2. 「驚愕」で検索して、目に付いたページを「右クリック→リンクを新しいウィンドウで開く」で片っ端から開く。
  3. 開きまくったウィンドウを片っ端から、見ては閉じ、見ては閉じを繰り返す。
  4. 検索結果のページも含め、全てのページを閉じると、IEXPLORER.exeというプログラムそのものが終了する。

この、「1つのプロセスで同じウィンドウをたくさん」と「最後のウィンドウが消えたらプログラムが終了する」が、ぴったり当てはまっている。
この方法で実装されてるとは限らないけど、この方法でも実装できる。(それと、同じ種類のウィンドウじゃなくても従属関係のない複数のトップレベルウィンドウなら同じ事が言えるはず。*2
だからマルチスレッドSDIとは「一つのプロセス、多数のトップレベルウィンドウ」って事ですね。語呂悪いけど。

*1:メインウィンドウ(=フレームウィンドウ)にすっぽり納まる子ウィンドウ

*2:ちなみにトップレベルウィンドウとは、親ウィンドウを持たないか、デスクトップウィンドウが親であるようなウィンドウのこと。ソースはMSDN - about windwsより

GDI+の初期化と終了 その2

要約

以前に書いたやつにエラーチェックを加えました。
このクラスはコンストラクタで初期化処理を行い、デストラクタで終了処理を行ってくれる優れものです。

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

アイディアそのものはこの本で知りました。
また、エラーチェックという、後回しにした挙句忘れてしまいがちな処理を思い出させてくれたのは以下のロシア製の掲示板。
ttp://www.gamedev.ru/code/forum/?id=87148

ソース
// GdiplusInit.h
#pragma once
//#include <windows.h>
//#include <gdiplus.h>
using namespace Gdiplus;

class CGdiplusInit {
private:
	ULONG_PTR		m_gdiplusToken;
	Status		m_res;			// Result

public:
	// Initialize 初期化
	CGdiplusInit()
	{
		GdiplusStartupInput gdiplusStartupInput;
		m_res = GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
	}

	// Shutdown 終了
	~CGdiplusInit()
	{
		if( Result())
			GdiplusShutdown( m_gdiplusToken);
	}

	// Get .... qawsedrftgyhjikl. 初期化の結果を得る
	bool Result()
	{
		return m_res == Ok;
	}
};

あと、gdiplus.libをリンクするのも忘れずに。VC++2008EEなら

#pragma comment(lib, "gdiplus.lib")

と書くだけでもいい。

使い方
INT WinMain(...)
{
    // いろいろ

    CGdiplusInit gdiplusInit;
    if(! gdiplusInit.Result()) {
        MessageBox( NULL, _T("GDI+の初期化に失敗しました。"),
            _T("エラー"), MB_OK);
        return 1;
    }

    // いろいろ

     return 0;
}
注意

以前にも書きましたが、CGdiplusInitのデストラクタが呼ばれてGDI+の終了処理が行われる前に(WinMainが終わる前に)、全てのGDI+オブジェクト(GDI+のクラスのインスタンス)を解放してください。
つまりグローバル変数や静的変数に注意してください。そのような変数を使う場合はポインタにしてnew/deleteを行うようにすれば、とりあえず問題ないです。

IEのアニメGIFの再生速度について

IEは表示時間を微調整する

GIFアニメ再生速度テスト用の画像を作成の下のほうに色々とまとめられています。
活動漫画館式動画講座GIFアニメの速度についてを見ると、やはりIEは待機時間(delay)に限度を設けているらしい。(やっぱ独自仕様ってやつね。昨日の記事でこれはIEのバグだ、とか言わなくてよかった。)
よくよく考えてみればどのソフトも待機時間が遅いのには理由があるはず。きっと秘密裏にみんなで決めたんでしょう。
そう思って調べた結果、animated GIFs(Ragnarok Online, SUMOU, ANIME etc.)というページを発見。
どうやら、待機時間(delay)が0.05秒以下なら、勝手に0.1秒にするらしいです。変なの。
みなさんありがとうございます。

実装

自分でも後々調べなきゃいけないとは思いますが、こちらを参考に実装してみます。
追加分はたったの2行ですが。

// アニメGIF再生(IE式)
if(pause <= 50)	// 待機時間が0.05秒以下なら
	pause = 100;	// 0.1秒に変えておく

こうすると、見た感じIE6と再生速度が同じになりました。でも「0.05未満を0.05にする」ほうが自然な気がして気持ち悪いです。
ユーザーに「IE式再生モード」と「通常再生モード」のどちらかを選択させるようにすればいいってことですね。

ドラッグ&ドロップに対応

色々な画像ファイルでテストするために、まずD&D機能を追加しました。
そこで参考にしたのがこちら、画像表示ソフトを作る::第11回 ドラッグ・アンド・ドロップで画像を開くです。毎度お世話になります。
この部分をほとんどコピペさせていただきました。まだWTLは使ってないですけど。

色々とテストしてみた

ドラッグ&ドロップで表示させても一瞬です。おかしなバグをいくつも駆除しました。
調子に乗ってスレッドを3つも作ったのがそもそも悪かったのだと思います。