前回のプログラムを見て、自分でScreenSaverConfigureDialog
関数を書いてもうまく、ダイアログボックスが表示されずに
苦労した方はいませんか。実は、ちょっとしたことで
なかなかわからないバグが生じます。
ダイアログボックスのデザインをリソースエジタで作って
そのまま使うと失敗します。
ダイアログボックスのIDをDLG_SCRNSAVECONFIGUREとしますが、
普通にリソースエジタを使うとこのIDに勝手な整数が
割り振られてしまいます。こうなると、なかなか間違いが
わかりません。DLG_SCRNSAVECONFIGUREには2003が割り振られないといけません。
これ以外の整数が割り振られると「設定ボタン」を押しても
ダイアログボックスは出てきません。この値はどうしてわかるかというと
scrnsave.hを見れば書いてあります。その他のシンボル値も
勝手に番号が振られては都合が悪いわけです。
ま、これでうまく表示されるはずです。では、今回のプログラムを 見てみましょう。1.リソースエジタでダイアログボックスのデザインをする 2.リソーススクリプトの不要部分を削除 3.リソース・スクリプトにwindows.h, scrnsave.hをインクルード 4.シンボル値などは自分でヘッダファイルを作る 5.自分で作ったヘッダファイルはソースファイル、リソース・スクリプトの 両方にインクルードする
リソース・エジタでダイアログボックスをデザインして 不要部分を全部きれいさっぱり削除しました。// saver02.rc #include <windows.h> #include <scrnsave.h> #include "saver02.h" ///////////////////////////////////////////////////////////////////////////// // // Bitmap // MYBMP1 BITMAP DISCARDABLE "cat1.bmp" MYBMP2 BITMAP DISCARDABLE "cat2.bmp" ///////////////////////////////////////////////////////////////////////////// // // Dialog // DLG_SCRNSAVECONFIGURE DIALOG DISCARDABLE 0, 0, 187, 93 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "設定" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,130,7,50,14 PUSHBUTTON "キャンセル",IDCANCEL,130,24,50,14 EDITTEXT IDC_EDIT1,5,19,82,18,ES_AUTOHSCROLL LTEXT "表示する文字列",IDC_STATIC,6,7,48,8 LTEXT "スピード(1-5000)",IDC_STATIC,7,47,50,8 EDITTEXT IDC_EDIT2,5,59,82,18,ES_AUTOHSCROLL END ///////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE PRELOAD DISCARDABLE BEGIN idsIniFile "saver02.ini" idsAppName "Screen Saver.saver02" END
ビットマップは前回使用したものと同じものを使います。
また、このスクリーンセーバーの名前と、設定を保存するファイル名を 文字列テーブルに設定します。idsIniFile, idsAppNameは scrnsave.hで定義されています。
スクリーンセーバーの名前は16ビット時代の名残で "Screen Saver.名前"と言うよう設定します。ピリオドより右側が このセーバーの固有の名前です。Windowsのディレクトリに control.iniというファイルがありますが、これをのぞいてみると [Screen Saver.*****]というセクションがいくつかあると思います。 そこに自分のセーバーの固有の設定を保存していました。 Windows95ではレジストリに保存するよう勧められています。 しかし、ユーザーの中にはレジストリに書き込まれるのを 極端に嫌う人もいます。そこで今回は自分のiniファイル(saver02.ini)を作って これに保存することにします。不要になったらこのファイルを そのまま消してしまえばよいわけです。
さて、今回はresource.hを使いませんからscrnsave.hで定義されているもの 以外のシンボル値は自分で設定します。
さて、これだけではイメージがわかないので実際のダイアログボックスを 示します。// saver02.h #define IDC_STATIC -1 #define IDC_EDIT1 2001 #define IDC_EDIT2 2003 #define ID_TIMER 32767
「表示する文字列」に適当な文字列を入力します。 ビットマップの他にこの文字列もランダムな位置に表示されます。
「スピード」にはWM_TIMERメッセージを発生させる時間間隔(ミリ秒)
を入力します。間違って2バイト文字を入力するとまずいのですが
プログラムを簡単にするために特に、入力文字の制限などは付けていません。
エジットボックスではなくてスライダーなどを付けるのが望ましいでしょう。
では、このスクリーンセーバーが実際に動いているときの様子を示します。
大してきれいなものではありません。いろいろ工夫してみてください。
では、ソースファイルを見てみます。
スクリーンセーバーに表示する文字列と、スピードをグローバル変数に してあります。// saver02.cpp #include <windows.h> #include <scrnsave.h> #include "saver02.h" char szText[40]; //スクリーンセーバーに表示する文字列 int nSpeed; //SetTimerにセットする値 LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { RECT rc; HDC hdc, hdc_mem; static int wx, wy; int x1, x2, y1, y2, r, g, b, bmp_w, bmp_h; HBRUSH hBrush, hOldBrush; HBITMAP hBitmap; BITMAP bmp_info; switch(msg) { case WM_CREATE: LoadString(hMainInstance, idsAppName, szAppName, APPNAMEBUFFERLEN); LoadString(hMainInstance, idsIniFile, szIniFile, MAXFILELEN); GetPrivateProfileString(szAppName, "TEXT", "粂井康孝 制作", szText, sizeof(szText), szIniFile); nSpeed = GetPrivateProfileInt(szAppName, "SPEED", 500, szIniFile); GetClientRect(hWnd, &rc); wx = rc.right - rc.left; wy = rc.bottom - rc.top; SetTimer(hWnd, ID_TIMER, nSpeed, NULL); break; case WM_TIMER: r = rand() % 256; g = rand() % 256; b = rand() % 256; hdc = GetDC(hMainWindow); x1 = rand() % wx; y1 = rand() % wy; TextOut(hdc, x1, y1, szText, strlen(szText)); hBrush = CreateSolidBrush(RGB(r, g, b)); hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); x1 = rand() % wx; y1 = rand() % wy; x2 = rand() % wx; y2 = rand() % wy; Rectangle(hdc, x1, y1, x2, y2); if (r % 2 == 0) hBitmap = LoadBitmap(hMainInstance, "MYBMP1"); else hBitmap = LoadBitmap(hMainInstance, "MYBMP2"); GetObject(hBitmap, sizeof(BITMAP), &bmp_info); bmp_w = bmp_info.bmWidth; bmp_h = bmp_info.bmHeight; hdc_mem = CreateCompatibleDC(hdc); SelectObject(hdc_mem, hBitmap); x1 = rand() % wx; y1 = rand() % wy; x2 = rand() % wx; y2 = rand() % wy; BitBlt(hdc, x1, y1, bmp_w, bmp_h, hdc_mem, 0, 0, SRCCOPY); DeleteDC(hdc_mem); DeleteObject(hBitmap); SelectObject(hdc, hOldBrush); DeleteObject(hBrush); ReleaseDC(hMainWindow, hdc); break; case WM_DESTROY: KillTimer(hWnd, ID_TIMER); PostQuitMessage(0); return 0; default: break; } return DefScreenSaverProc(hWnd, msg, wParam, lParam); }
WM_CREATEメッセージが来たら文字列テーブルからアプリケーション名、 INIファイル名を呼び出しています。szAppName, APPNAMEBUFFERLEN, szIniFile, MAXFILELENはscrnsave.hで定義されています。 LoadString関数は第64章を参照してください。 これで、アプリケーション名とINIファイル名がszAppName, szIniFileに 格納されました。次にこれらの名前を使ってINIファイルから前回の 設定を呼び出します。
これは、長い名前の関数ですが説明はあんまり必要ないですね。 セクション名は[***]というようにかっこで囲まれています。DWORD GetPrivateProfileString( LPCTSTR lpAppName, // セクション名 LPCTSTR lpKeyName, // キー名 LPCTSTR lpDefault, // デフォルト文字列 LPTSTR lpReturnedString, // 文字列を受け取るバッファ DWORD nSize, // バッファサイズ LPCTSTR lpFileName // ファイル名 );
キー名とはセクション名の下にある設定項目です。
デフォルト文字列は、指定したファイルがないときなど 失敗したときにバッファに入れる文字列を指定します。
この関数が成功したときはコピーした文字数(最後のヌル文字は 含みません)
INIファイルは
というような感じになっています。[section] key1=文字列1 key2=文字列2 ...
ま、これもあんまり説明がいりませんが、得られた整数値が 戻り値となります。戻り値はUINT型であることに注意してください。 さて、これでINIファイルから表示する文字列とスピードが 読み込まれました。UINT GetPrivateProfileInt( LPCTSTR lpAppName, // セクション名 LPCTSTR lpKeyName, // キー名 INT nDefault, // キーが見つからないときのデフォルト LPCTSTR lpFileName // ファイル名
WM_TIMERメッセージが来たときの処理は前回とほぼ同じです。 テキストを表示する部分が増えただけです。 WM_DESTROYメッセージのところでタイマーを殺すのを忘れないで ください。
ScreenSaverConfigureDialogプロシージャは普通のダイアログボックスの プロシージャとほぼ同じです。BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { char szTemp[32]; switch (msg) { case WM_INITDIALOG: LoadString(hMainInstance, idsAppName, szAppName, APPNAMEBUFFERLEN); LoadString(hMainInstance, idsIniFile, szIniFile, MAXFILELEN); GetPrivateProfileString(szAppName, "TEXT", "粂井康孝 制作", szText, sizeof(szText), szIniFile); SetDlgItemText(hDlg, IDC_EDIT1, szText); nSpeed = GetPrivateProfileInt(szAppName, "SPEED", 500, szIniFile); wsprintf(szTemp, "%d", nSpeed); SetDlgItemText(hDlg, IDC_EDIT2, szTemp); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: GetDlgItemText(hDlg, IDC_EDIT1, szText, sizeof(szText)); WritePrivateProfileString(szAppName, "TEXT", szText, szIniFile); nSpeed = GetDlgItemInt(hDlg, IDC_EDIT2, NULL, FALSE); wsprintf(szTemp, "%d", nSpeed); WritePrivateProfileString(szAppName, "SPEED", szTemp, szIniFile); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; } BOOL WINAPI RegisterDialogClasses(HANDLE hInst) { return TRUE; }
しかし、注意する点があります。通常のプログラムであれば 親ウィンドウのWM_CREATEメッセージでグローバル変数に値を 設定したならダイアログボックスのプロシージャからもこの 値を読めるはずです。しかし、スクリーンセーバーではこのプロシージャで もう一度ファイルから値を読み出しておく 必要があります。
WM_INITDIALOGメッセージのところで改めてINIファイルから 前回の設定を読み出しています。そして、エジットボックスに 表示しています。スピードも文字列としてエジットボックスに 表示している点に注意してください。
OKボタンが押されたときは、ダイアログボックスの 各入力を読み出して、INIファイルに書き込んでいます。
これも、あんまり説明は必要ないですね。 成功したらTRUEを失敗したらFALSEを返します。BOOL WritePrivateProfileString( LPCTSTR lpAppName, // セクション名 LPCTSTR lpKeyName, // キー名 LPCTSTR lpString, // 書き込む文字列 LPCTSTR lpFileName // ファイル名 );
ところで、WritePrivateProfileIntという関数は ありません。数値も文字列として書き込みます。
キャンセルボタンが押されたら何もせずにダイアログ ボックスを閉じます。
RegisterDialogClasses関数はダイアログボックスに 特別な設定をしない限り単にTRUEを返すだけです。
さて、今回でやっとスクリーンセーバーらしくなってきました。 ランダムに表示されるだけでなくアニメーションの ようにビットマップが画面を動き回るものに作り替えてみて ください。
Update 04/Jun/1998 By Y.Kumei