このページはNetscapeNavigator 2.0以降及びMicrosoft Internet Exploler 3.0以降で正常に見られます。
Solly,Japanese only

★ BB研究所 ★
MFCベースのスクリーンセーバー

1.はじめに

スクリーンセーバーについては、結構資料が出ているので今まで触れていませんでしたが、このたび新作ソフトを作るにあたり、その初期段階をテンプレートとしてまとめましたので、ここで公開したいと思います。
VBやDelphiユーザには大変申し訳ありませんが、今回もまたまた,VisualC++(ver6.0)を対象としています。

2.スクリーンセーバー用ライブラリ

VisualC++でスクリーンセーバーを扱う場合、専用のライブラリ( Scrnsave.lib/Scrnsavw.lib )が用意されています。これはMFCを使わずWIN32に直接アクセスするタイプのアプリケーション用です。
オンラインヘルプ(MSDNライブラリ)で、キーワード(N)を「Screen Saver Library」にしてサーチすれば、使用方法が載っています。
但し、英文のため、英語の苦手な人には(私も含め)ちょっととっつきにくいかもしれません。そのような人は、昔のVisualC++(ver2.0)のヘルプに日本語で書いてあったので、持っている人からその部分を見せてもらうのも一つの手です。
※この方法は私自身結構気に入っていて、拙作「ヴィデオ・スクリーン」はこの方法で作成しています。

3.MFCからスクリーンセーバーを使う方法

MFC(Microsoft Foundation Classes)は、私自身あまり好きではないのですが、OCXが使い易く、各種ライブラリもMFC用に作成されたものが多くなってきたので、最近は避けては通れなくなってきました。
使い方については、オンラインヘルプ(MSDNライブラリ)の、
以下のページに記述されています。
+技術文書
 +Tecnical Articles
  +Windows Platform
   +Windows Managiment
    +Creating 32-Bit Screen Savers with Visual C++ and MFC

しかしここに載っているサンプルは力技的なところがあり、あまり美しくありません(特に、WindowProcを使っているあたり)。
また、これとは別にVC++のCD−ROMには、MFCを使ったスクリーンセーバーのサンプルソースも含まれています。このソースは綺麗なのですが、かなり完成度が高く、またオリジナルの描画クラスを親クラスとしてスクリーンセーバクラスを作成しているため、少々見づらくなっています。
このサンプルソースを参考に、まったく新規にテンプレートを作成しました。
このテンプレートは、新作ソフトを作るにあたりその初期段階を一般的にまとめたものです。


4.テンプレートの説明

まずテンプレートを、ここからダウンロードしてください。
テンプレートの説明の前に、スクリーンセーバ起動時のスイッチについて説明します。

/s, -s ,sスクリーンセーバー
/p, -p ,pプレビュー
/c, -c ,c設定ダイアログ表示(親ウィンドウのある場合
現行アクティブなウィンドウが親ウィンドウ
無し 設定ダイアログ表示(親ウィンドウの無い場合)
上記スイッチは、"__argv[1]"に渡されます。

さて、テンプレートの説明に移ります。
ダウンロードしたファイルを展開して、「DsScr.scr」を「\windows\system」の下にコピーして下さい。
そして「プレビュー」で実行してみてください
このテンプレートは、一定間隔(0.5秒)で、赤系から始まり、いろんな色で画面全体を塗りつぶすスクリーンセーバーになっています。
MFC AppWizerdによりダイアログボックスベースで作成しました。
本来作成されるダイアログボックスは、スクリーンセーバーの設定用ダイアログに使用しますが、このテンプレートではウィザードそのままで、特に何かを設定できるわけではありません。

スクリーンセーバーには、上記のように、スクリーンセーバーモードの他に、設定時の小さな画面に表示するためのプレビュー、設定ダイアログ表示(親ウィンドウ有り/無し)の4種類があります。それぞれ渡される起動スイッチが異なります。
テンプレートでは、CDsScrApp::InitInstance()の部分でこれを行っています。
BOOL MatchOption(LPTSTR lpsz, LPTSTR lpszOption)
{
	if (lpsz[0] == '-' || lpsz[0] == '/')
		lpsz++;
	if (_memicmp(lpsz, lpszOption,1) == 0)
		return TRUE;
	return FALSE;
}


BOOL CDsScrApp::InitInstance()
{
	// 標準的な初期化処理
	// もしこれらの機能を使用せず、実行ファイルのサイズを小さくしたけ
	//  れば以下の特定の初期化ルーチンの中から不必要なものを削除して
	//  ください。

#ifdef _AFXDLL
	Enable3dControls();			// 共有 DLL 内で MFC を使う場合はここをコールしてください。
#else
	Enable3dControlsStatic();	// MFC と静的にリンクする場合はここをコールしてください。
#endif
	if (__argc == 1 || MatchOption(__argv[1], _T("c"))){


		CDsScrDlg dlg;
		m_pMainWnd = &dlg;
		int nResponse = dlg.DoModal();
		if (nResponse == IDOK)
		{
			// TODO: ダイアログが  で消された時のコードを
			//       記述してください。
		}
		else if (nResponse == IDCANCEL)
		{
			// TODO: ダイアログが <キャンセル> で消された時のコードを
			//       記述してください。
		}
	}
	else if(MatchOption(__argv[1], _T("p"))){
		// プレビュー

		return TRUE;	// TRUEで戻るらしい
	}
	else if (MatchOption(__argv[1], _T("s")))
	{
		CSaver* pWnd = new CSaver;
		pWnd->CreateEx();
		m_pMainWnd = pWnd;
		return TRUE;	// TRUEで戻るらしい
	}
	return FALSE;
}
上記のCDsScrApp::MatchOption()が文字列を比較する部分です。
CDsScrApp::InitInstance()で、これを呼び出しスイッチの切り分けをしています
上記例では、スイッチ無し及び"/c"のいずれのときも親ウィンドウ無しで設定ダイアログを作成していますが、より正確を期すのなら、"/c"の場合のみ、ウィンドウオブジェクト作成時にアクティブウィンドウ(CWnd::GetActiveWindow())を指定して作成すると良いでしょう。

設定ダイアログボックスの作成は、通常のダイアログボックスの作成とまったく同じですので説明を省略します

スクリーンセーバウィンドウは、CSaverクラス(Saver.cpp)で記述しています
この内容についても、ソースを見ていただくことで説明は省略します
スクリーンセーバーの機能としては、少し特殊なウィンドウを作成し、マウス移動等のイベントが発生したら、ウィンドウを破棄するようになっています。
このテンプレートはそれだけではさびしいので、一応タイマーを作って、一定期間毎に画面を塗りつぶしなおしています。

BOOL CSaver::CreateEx() 
{
	CRect rect(0, 0, ::GetSystemMetrics(SM_CXSCREEN),
		::GetSystemMetrics(SM_CYSCREEN));

	// カーサーを設定せずにクラスを登録します
	if (m_lpszClassName == NULL)
	{
    	m_lpszClassName = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW,
			::LoadCursor(AfxGetResourceHandle(), 
			MAKEINTRESOURCE(IDC_NULLCURSOR)));
	}

	 BOOL fRet=CWnd::CreateEx(WS_EX_TOPMOST, m_lpszClassName, _T(""), WS_VISIBLE|WS_POPUP, 
		rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 
		NULL, NULL, NULL );

	 SetTimer(1, 500, NULL);	// 500ms(0.5秒)毎にイベントの発生するタイマーを設定

	 return fRet;
}
・・・
void CSaver::OnDestroy() 
{
	KillTimer(1);
	PostQuitMessage(0);
	CWnd::OnDestroy();
}
・・・
void CSaver::OnPaint() 
{
	CPaintDC dc(this); // 描画用のデバイス コンテキスト
	
	// TODO: この位置にメッセージ ハンドラ用のコードを追加してください

	// 以下画面を塗りつぶすルーチン
	CRect rect;
	GetClientRect(&rect);
	dc.FillSolidRect(&rect,m_stBackColor);

	// 描画用メッセージとして CWnd::OnPaint() を呼び出してはいけません
}

void CSaver::OnTimer(UINT nIDEvent) 
{
	// TODO: この位置にメッセージ ハンドラ用のコードを追加するかまたはデフォルトの処理を呼び出してください
	if(nIDEvent){
		// 背景色をいろんな色に設定する
		m_stBackColor+=0x10;
		if(m_stBackColor>0xffffff)
			m_stBackColor=0;
		// 背景を塗りつぶす
		CRect rect;
		GetClientRect(&rect);
		InvalidateRect(FALSE);

	}
	
	CWnd::OnTimer(nIDEvent);
}



【注意】
プロジェクトの設定(「プロジェクト(P)」→「設定(S)」)で、「デバック」タブの「プログラムの引数」に「/s」を入れると、スクリーンセーバーモードで起動されるので、この部分のデバックをすることが出来ます。
しかし、テンプレートそのままでは、スクリーンセーバ起動時のデバックが出来ません。
ブレークポイントを張ると、その続行直後にスクリーンセーバーが終了してまうからです。
これを回避するためには、条件コンパイルとして、デバック時はPostMessage(WM_CLOSE);の行をコンパイルしないようにします(しかし、全てをコメントアウトするとプログラムが終了しなくなるので、 CSaver::OnLButtonDown()くらいはそのままにしておきましょう)。また、CWnd::CreateWindowEx()呼び出し時のWS_EX_TOPMOST指定を0にする必要があります。これはスクリーンセーバウィンドウが最前に来るとブレークしても、VisualC++の画面が最前にこれないからです。


5.おわりに

今回、テンプレートの提示だけで、あまりスクリーンセーバー自身については解説していません。
しかし、ポイントは起動時のスイッチの切り分け及び、テンプレート通りのウィンドウが作成されれば、殆ど普通のウィンドウソフトと何ら作りは代わりません(キー入力やマウス入力が出来ないので、タイマーイベントにより、自発的に動作させる必要はありますが…)。
この、テンプレートを元に又は参考にして、オリジナルのスリーンセーバーをどんどん作成していきましょう!

[ホームへ戻る]
【改訂記録】
2000/05/27版:公開


Copyright (c) 1997-2000, BearBeetle, Allrights reserved.