子ウィンドウにWM_MOUSEWHEELが来ない、受け取れない、処理できない時

チャイルドウィンドウにWM_MOUSEWHEEL(マウスホイールが回されたときのウィンドウメッセージ)が渡されない事がある。
親ウィンドウには届くんだけど、本命の子ウィンドウには届かない、そんな時。

結論

SetFocus( HWND); を使って、子ウィンドウにフォーカスを移しましょう。
あるいは親に渡されたメッセージを子ウィンドウへ丸投げするなど。
SetCapture〜ReleaseCaptureは必要なさそうです。

もっと! もっともっと便利に

WM_MOUSEWHEELを受け取りたい窓が複数ある場合、どの窓にどうやってフォーカスを設定するか

(複数ある子ウィンドウのうち、特定の子ウィンドウにフォーカスを設定したい時)
僕のやりたい事とは関係ないのでほんのメモ程度に。

  1. ChildWindowFromPointExでマウスポインタの真下にある窓を調べる
  2. その窓にSetFocus

WM_MOUSEMOVEの時にこれをやるといいと思う。
でもWM_ACTIVATEのたびに親ウィンドウをSetCaptureしないと、子ウィンドウにWM_MOUSEMOVEが行っちゃうかも。
やってみないとよくわかりません。

WM_MOUSEWHEELを必要とする窓がひとつしかない場合

WM_ACTIVATEのたびにSetFocusすればいいと思います。
ただしこのメッセージはアクティブになった時だけでなく、非アクティブ状態になったときも送られてくるので注意。

// WTLのやり方でやってみる (m_ImgViewは子ウィンドウ)
LRESULT OnActivate(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	if(WA_INACTIVE == LOWORD(wParam)) // 非アクティブ状態になった時
		return 0;
	if(m_ImgView.IsWindow())
		m_ImgView.SetFocus();
	return 0;
}
// SDKのやり方でやってみる (ウィンドウプロシジャにて)
case WM_ACTIVATE:
	if(WA_INACTIVE == LOWORD(wParam))
		break;
	if(IsWindow(hView))
		SetFocus(	hView);
	break;

こんなところでしょうか。

注意

ChildWindowFromPointExの2番目の引数は, 親ウィンドウからの相対座標です。

CreateCompatibleBitmapの限界とCreateDIBSection

前置き

わりとメモのような位置づけです。自分でもあまり理解していません。
CreateCompatibleBitmapでオフスクリーンバッファを作っていたが、大きいビットマップを作れないから、CreateDIBSectionを利用する事にした。
CreateDIBSectionなら巨大なビットマップを作ることもできた。

CreateCompatibleBitmapが失敗する原因がわからない。
失敗したときにGetLastErrorで調べてみたら、8が返され、メモリ不足が原因とされた。
メモリには空きがあるのでそれはありえないはず。結局わからない。

見つけた情報

自分ではわからないので、ヒントになりそうなサイトをここに書いておきます。

2004年頃の質問

http://homepage1.nifty.com/MADIA/vc/vc_bbs/200403/200403_04030034.html
とりあえず自己解決していらっしゃいます。これは尊敬します。
CreateDIBitmapがダメだったから、CreateDIBSectionを使ったらしい。

DIBとかDDBとか(DIB=BMPはデバイスに依存しない, DDBは依存する)

BMP形式入門 - BMPファイルの中身をちょっと覗いてみる
http://www.geocities.co.jp/Playtown-Knight/6845/sd_doc/format_windib.html
標準 Windows API (100〜116番あたり)
http://wisdom.sakura.ne.jp/system/winapi/win32/index.html

おまけ

// 大きいメモリデバイスコンテキストを作る(べつに小さくてもOK)
BITMAPINFO bmpinfo;
ZeroMemory(&bmpinfo, sizeof(bmpinfo));
bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpinfo.bmiHeader.biWidth = x;  // サイズ
bmpinfo.bmiHeader.biHeight = y; //
bmpinfo.bmiHeader.biPlanes = 1;
bmpinfo.bmiHeader.biBitCount = 32; // 32ビットカラー
bmpinfo.bmiHeader.biCompression = BI_RGB;

hNewBmp = CreateDIBSection( NULL, &bmpinfo, DIB_RGB_COLORS, NULL, NULL, NULL);

// 今作ったビットマップを、メモリデバイスコンテキストに選択する
SelectObject( hNewDC, hNewBmp);

バイスコンテキストの情報を得る

MSDN
http://msdn.microsoft.com/ja-jp/library/cc428670.aspx

ピクセルあたりのビット数を調べます(1ピクセルの色を何ビットで表すかを調べる)

int n = GetDeviceCaps( hdc, BITSPIXEL);

あまりにも大雑把だけど画像ビュアーになった


追加した機能

拡大している際にスクロールバーをつけた

こうしないと、マウスでつかんで移動するときに、どこまで動かせるのかわかりずらい。

タイトルバーにファイル名を表示するようにした

どうせならすぐ上のディレクトリ名も表示した方が使いやすいかも。

左右の方向キーで次のファイルへ移動するようにした

一番の手抜きはここです。見た目には動いてますが。

ウマ耳画像あります

画像の拡大と縮小とスクロールができるようになりました

クリックして画像をつかみ、カーソルを動かす事によってスクロールができます。
またマウスホイールで拡大縮小ができるようになりました。
ちょっとやりにくいですが、それらを同時に行うことも可能です。

苦労の痕跡があちこちに残ったコードになりました。
ばこばことスレッドを立て、気づいたら使わないイベントオブジェクトまで定義していたり、
一度ある処理を行ってから、それと同じ事をするメソッドを呼び出していたり。

実装とか

ウィンドウメッセージが来るごとにいちいち拡大していては重すぎるので、倍率が変更されるごとに、とりあえずバッファへ描きます。
そのあとWM_PAINTのハンドラでバッファから窓へBitBltします。


バッファの大きさは最低でもデスクトップと同じで、それよりも大きく拡大したときは、デスクトップよりも大きくなります。
拡大しまくると65000x50000くらいになります。それ以上はエラーになりますが。


バッファと、バッファ上にある画像と、窓へBitBltする範囲の関係は下の図のようになります。
バッファのサイズとバッファ上の画像のサイズは、必ずしも一致しません。
また窓のサイズは変わりますから、下の赤い枠の大きさも変わります。
それと青い枠の中心(*1)は、バッファの中心と必ず一致します。

赤枠は、青枠をはみ出しません。つまり青枠の外がBitBltされることはありません。
ただし例外として、青枠が赤枠よりも小さい場合は、青枠の外も余白としてBitBltされます。(下の図)

ウマ耳

*1:対角線の交点かな

クリティカルセクションに出たり入ったりするクラス

Effective C++ 第3版のを真似たつもりです。あの本ではミューティックスですが。

ソース
#include <windows.h>

// クリティカルセクションに出たり入ったりするクラス
class CLock
{
public:
	CLock( CRITICAL_SECTION *c)
		: m_critsec(c), m_unlocked(false)
	{
		::EnterCriticalSection( m_critsec);
	}
	~CLock()
	{
		UnLock();
	}
	void UnLock()
	{
		if(! m_unlocked) {
			::LeaveCriticalSection( m_critsec);
			m_unlocked = true;
		}
	}

private:
	CRITICAL_SECTION *m_critsec;
	bool m_unlocked;
};
使い方の例
CRITICAL_SECTION critsec;

// 初期化処理などは省略

void func()
{
    CLock cs( &critsec);

    //
    // 途中でスレッドが切り替わると困る処理はここに書く
    //
}

LeaveCriticalSection関数を呼び忘れたとしても、CLockクラスがデストラクタでやってくれます。
途中でそれを呼びたい場合は、UnLockメソッドを呼び出します。

ちらつき防止 BitBlt winapi クラス化

BitBltとはそもそも

BIT BLock Transfer から、BitBltだそうです。*1

使い方

この質問サイトに書いてあるサンプルが一番いいでしょうか。
http://oshiete1.goo.ne.jp/qa3052870.html
こちらのを改造して使っています。実を言うとよく理解していません。

ちょっとクラス化してみた

2つ以上のまとったデータと処理が出てきたのでクラスにしてみました。まだ使いにくいです。

// Buffer.h
#pragma once
#include <windows.h>

// メモリデバイスコンテキストの機能をまとめたクラス.
class CBuffer
{
public:
	// コンストラクタ
	CBuffer()
	  : m_hdc(NULL),
		m_hBmp(NULL),
		m_BufIsCreated(false)
	{ }

	// デストラクタ
	~CBuffer()
	{
		Destroy();
	}

	// サイズを指定してバッファを作る. 失敗たら元のバッファのままになる.
	bool Create( HWND hWnd, int x, int y)
	{
		// 新しいバージョン 失敗した場合はそのままにしておく。
		bool rc = false;
		HDC hNewDC;
		HBITMAP hNewBmp;
		HDC hdc = GetDC( hWnd);
		if(hdc) {
			hNewDC = CreateCompatibleDC( hdc);
			if(hNewDC) {
				hNewBmp = CreateCompatibleBitmap( hdc, x, y);
				if(hNewBmp) {
					SelectObject( hNewDC, hNewBmp);
					Erase();
					rc = true;
				}
				else {
					DeleteDC( hdc);
				}
			}
			ReleaseDC( hWnd, hdc);
		}
		if(rc) {
			// 作成に成功した場合
			Destroy();	// 今あるものを破棄する
			m_size.SetSize( x, y);
			m_hdc = hNewDC;
			m_hBmp = hNewBmp;
			m_BufIsCreated = true;
		}
		// 作成に失敗しても、前に作られたものがあれば、m_IsCreatedはtrueのまま.
		return rc;
	}

	// バッファを破棄する. 仮に失敗しても続行はできる.
	void Destroy()
	{
		if(m_hBmp) {
			::DeleteObject( m_hBmp);
			m_hBmp = NULL;
		}
		if(m_hdc) {
			::DeleteDC( m_hdc);
			m_hdc = NULL;
		}
	}

	// バッファの背景を白く塗りつぶす.
	bool Erase()
	{
		SelectObject( m_hdc, GetStockObject(NULL_PEN));
		SelectObject( m_hdc, CreateSolidBrush(RGB(0xFF,0xFF,0xFF)));
		Rectangle( m_hdc, 0, 0, m_size.cx, m_size.cy);
		DeleteObject( SelectObject( m_hdc, GetStockObject(WHITE_BRUSH)));

		return true;
	}

	// バッファのデバイスコンテキストハンドルを得る
	HDC hdc()
	{
		return m_hdc;
	}
	operator HDC()
	{
		return m_hdc;
	}

	// サイズを返す
	int cx() const
	{
		return m_size.cx;
	}
	int cy() const
	{
		return m_size.cy;
	}

	// バッファが作られているかどうか(初期化されているかどうか)を返す
	bool IsCreated() const
	{
		return m_BufIsCreated;
	}

private:
	HDC m_hdc;
	HBITMAP m_hBmp;
	CSize m_size;
	bool m_BufIsCreated;

	// コンパイラに自動生成させない
	CBuffer &operator=( const CBuffer &right);
	CBuffer( const CBuffer &);
};

Createメソッドでは、m_hdcを何度も作り直さなくてもいい時だってあるはず。でも面倒だからいいや。

ひどいミス

そっち系のひどいミスをここに書いていこうかと思います。

if( ) + セミコロン

if(m_rcSrc.Height <= 0);
	m_rcSrc.Height = 1;

もし高さが0以下なら、それを1にする予定でした。
しかしちゃんとインデントもされているのに、ifのカッコの終わりにセミコロンがついています。
このせいで2行目が必ず実行されるようになっていまいます。
何が一番の原因かって、doubleからfloatへの代入の警告に埋もれていた以下の警告を無視してしまったことでしょうね。
でも本当の理由はassert(条件式);のassertをifに置き換え、条件を逆にした際、セミコロンを取り忘れた事。
エラー処理しようとしてエラーを起こしてしまったわけでしょうか。
ちなみに以下が警告文。VC++2008EEより。

warning C4390: ';' : 制御が空の文が見つかりました。意図した記述でしょうか?

assertマクロ Release版にしたら変になった

assertの性質と本来の使い方、もっといえばassertという名前の意味さえも無視した使い方をしていました。以下を。

bool func(); // なにか重要な機能

int main()
{
    assert( func()); // funcでエラーが出た場合、停止する
}

assertというのは「断言」とか「主張」です。珠玉のプログラミング的にいえば「不変な表明」でしたっけ。
これをRelease版にしたら、Debug版とは違い、assertが、その引数もろとも消えてしまいます。
だからfuncが実行されなかったんです。だからウィンドウの中身が空っぽだったんですよ。

珠玉のプログラミング―本質を見抜いたアルゴリズムとデータ構造

珠玉のプログラミング―本質を見抜いたアルゴリズムとデータ構造