月刊ソフト作り!
Make The Software For VisualBasic


『スクリーンセーバーを作る(上級編その2)』
2002.May.15

 Presented by kouta_y
感想等は掲示板、苦情はメールへ。

会社
スクリーンセーバーを作る完結編です。


前回のあらすじ

さて前回は「パスワード機能の付加」までやりました。
う〜ん、なんだかAPIの宣言がいっぱいいっぱいだ〜。と筆者自身そう思いました。
C言語なんかだと、宣言などは全てインクルードファイルというものに宣言されているんですが、VBではインクルードという概念はないのでしょうがありません。
APIの宣言専用のモジュールを作るというのも手です。(筆者はそうしてる)

っと、話が逸れました。
今回は完結編という事で、最後の最後にとっておいた「プレビュー機能の実現」です。
プレビューというのは、画面のプロパティを開くと出てくるやつです。
「プレビューボタン」ではありません。
これが意外と厄介でして、最後に回してしまいましたが、考え方さえ分かれば簡単にできます。(それが難しいんだってば)


どうやるの?

画面のプロパティダイアログありますよね?

これ



真ん中にディスプレイの形をした絵があります。
実はこれ。ただの絵でなく1コのウィンドウなんです。
そしてプレビュー時に飛んでくる引数のあとには、このウィンドウハンドルが一緒に飛んできます。
つまり「このウィンドウを相手にしろ」と。

「あーなるほど!そこに描画するんだね!」

ブブー。
違いまーす。(嫌な人間だ)

別にそれでもいいんですが、それだといろいろ問題が起きてきます。
例え、ただ画像を表示するにもデバイスコンテキストハンドルを取得して、描画して、再描画は・・・。
何より一番の問題が、他のスクリーンセーバー達のプレビューを見ようとして切り替えても終了されないんです

なのでここでは、そのウィンドウの子ウィンドウとしてメインフォームを起動します。
逆サブクラス化とでも言いましょうか。
そうすればプレビューを切り替えたら、親ウィンドウは一旦子ウィンドウを破棄する動作をするのできちんと終了もしますし、何より処理が楽です。


使うAPIの宣言

また増えます。
以下の宣言を加えてください。

'ウィンドウ属性の設定
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _
  ByVal hWnd As Long, _
  ByVal nIndex As Long, _
  ByVal dwNewLong As Long _
) As Long

'ウィンドウ属性の取得
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" ( _
  ByVal hWnd As Long, _
  ByVal
nIndex As Long _
) As Long

'親ウィンドウ変更
Private Declare Function SetParent Lib "user32" ( _
  ByVal hWndChild As Long, _
  ByVal hWndNewParent As Long _
) As Long

'ウィンドウ座標取得
Private Declare Function GetWindowRect Lib "user32" ( _
  ByVal hWnd As Long, _
  lpRect As RECT _
) As Long

'ミューテックスオープン
Private Declare Function OpenMutex Lib "kernel32" Alias "OpenMutexA" ( _
  ByVal dwDesiredAccess As Long, _
  ByVal
bInheritHandle As Long, _
  ByVal lpName As String _
) As Long

'ミューテックス作成
Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" ( _
  ByVal lpMutexAttributes As Long, _
  ByVal
bInitialOwner As Long, _
  ByVal
lpName As String _
) As Long

'ミューテックス開放
Private Declare Function ReleaseMutex Lib "kernel32" ( _
  ByVal hMutex As Long _
) As Long

'ハンドル破棄
Public Declare Function CloseHandle Lib "kernel32" ( _
  ByVal hObject As Long _
) As Long

さて、またまたいっぱいあります。
ではそれぞれの関数の説明です。
ちなみに、ここでは「親ウィンドウ」と書いてあればそれは「ディスプレイの形をしたウィンドウ」の事です。
「子ウィンドウ」とあれば、それは自分の事です。

SetWindowLong」関数。
ウィンドウの属性を設定(書き換え)します。主にサブクラス化と呼ばれる方法の常套手段ですが、今回は子ウィンドウのスタイルを設定するのに使います。
hWnd      対象となるウィンドウハンドルを指定。
nIndex     何を書き換えるのかフラグを指定します。ここでは「GWL_STYLE」フラグを指定します。
dwNewLong  新しい値を指定します。
戻り値は、成功したら前の設定値が返ります。失敗したら0が返ります。

GetWindowLong」関数。
ウィンドウの属性を取得します。今回は親ウィンドウのスタイルを取得するのに使います。
hWnd  対象となるウィンドウハンドルを指定。
nIndex  何を取得するのかフラグを指定。上と同じフラグを指定します。
戻り値は、要求した値が返ります。失敗したら0が返ります。

SetParent」関数。
親ウィンドウを変更します。
hWndChild      子ウィンドウを指定。メインフォームのハンドルです。
hWndNewParent  新しい親ウィンドウを指定。ディスプレイの形のウィンドウです。
戻り値は、成功したら以前の親ウィンドウハンドル。失敗したら0(NULL)が返ります。

GetWindowRect」関数
ウィンドウのサイズをスクリーン座標で取得します。親ウィンドウのサイズを取得するのに使います。
hWnd   対象となるウィンドウハンドルを指定。
lpRect  RECT構造体を指定。ここにデータが格納されます。RECT構造体については後述。
戻り値は、成功すると0以外。失敗すると0が返ります。



ミューテックス?

ここで一旦区切ります。
OpenMutexとか、あまり聞かない名前が出てきました。
ミューテックスについては後で詳しく説明しますが、「メモリ内にフラグを立てる」とでも思ってください。
2重実行禁止で使います。
とりあえずここでは関数の説明だけします。
なんとなく分かって貰えれば良いので、さらっと読んでください。


OpenMutex」関数
ミューテックスを開きます。
dwDesiredAccess  アクセス方法を指定。MUTEX_ALL_ACCESSを指定します。
bInheritHandle    ハンドルを継承可能にするか指定。TRUEで継承可能、FALSEで不可となります。
lpName        オープンするミューテックスの名前を文字列で指定。大・小文字は区別されます。
戻り値は、成功(オープンできたら)ハンドルが返り、失敗したら0(NULL)が返ります。

CreateMutex 」関数
ミューテックスを作成します。
pMutexAttributes   ハンドルを継承するかのLPSECURITY_ATTRIBUTES構造体を指定。使わないのでNULL(ByVal 0&)を指定します。
bInitialOwner      所有権を要求するか指定。TRUEで要求する。FALSEを指定すると要求しません。
lpName         ミューテックスの名前を文字列で指定。この名前でミューテックスが作成されます。NULLも指定できます。
戻り値は、成功すればミューテックスのハンドル、失敗すれば0(NULL)が返ります。

ReleaseMutex」関数
ミューテックスの所有権を解放します。実は今回のサンプルでは使ってません。
hMutex  現在開いているミューテックスのハンドルを指定。
戻り値は、成功すれば0以外、失敗すると0が返ります。
ちなみに所有権を解放するだけなので、ハンドル自体は破棄されません。

CloseHandle」関数
オープンしているハンドルをクローズします。
hOnject  現在開いているハンドルを指定。
戻り値は、成功すると0以外、失敗すると0が返ります。


ミューテックスを使う

さてミューテックスとは、一言で言うと「同期オブジェクト」というものになります。
そのオブジェクトは「1つのスレッドに所有される」ものと「どのスレッドにも所有されない」のものとがあります。
所有されるものを「非シグナル状態」。されないものを「シグナル状態」とも呼びます。

さて今回は「所有権」なんて関係ありません。
オブジェクトが「作られているか」「いないか」を判定するだけだからです。

では、とりあえず2重実行を判定するような関数を作ってみましょう。

Public Function PrevInst() As Long
'フォームが2重起動してるか
Dim strMutex As String
Dim hMutex  As Long

strMutex = "Tarotin@MyScr"

'ミューテックスを開く
hMutex = OpenMutex(MUTEX_ALL_ACCESS, 0, strMutex)
If hMutex Then
'  開けたら起動している
  Call CloseHandle(hMutex)
  hMutex = 0
Else
'  開けなかったら作る
  hMutex = CreateMutex(0, 0, strMutex)
End If
PrevInst = hMutex
End Function

まずミューテックスをオープンしてみます。
んで、もし開けたなら、既にオープンしているという事ですから自分は「二番手」という事になります。
ハンドルが返らず0が返ってきたら、開けなかったという事で自分は「一番手」となります。
あとはそれをIF分岐させ、ミューテックスを作成します。所有権は要求しません(つまりシグナル状態)。
関数自体の戻り値はハンドルとします。
オブジェクトの名前は、他となるべく被らないような名前にしましょう。

これで「2重実行判定関数」は終わりです。
簡単でしょう?


子とか親ウィンドウとは?

親ウィンドウとか子ウィンドウとか言われても、いまいちよく分かりません。
「親」というのは「オーナーウィンドウ」や「トップレベルウィンドウ」とも呼ばれ、タイトルバーがあって「閉じる」とかのボタンがあったりするやつの事です。
「子」は、その親の中にボタンやテキストボックスがあったとします。実はボタンやテキストボックスも「ウィンドウ」の1つなんです。
そういう親の下にあるウィンドウの事を「子ウィンドウ」と呼びます。
子の下に、さらに子ウィンドウを作る(つまり孫)という事もできます。
子ウィンドウは、親ウィンドウに従属し、親が破棄されると同時に子も閉じられます。
親がスクリーン上を移動すれば、子もそれに合わせて移動します。(サイズは変わりません)


例をあげると、


「Form1」というタイトルを持ったウィンドウは、「Command1」ボタンなどの「親ウィンドウ」にあたります。
「Command1」ボタンは当然「Form1」の「子ウィンドウ」となります。

階層で表すと、以下の様になります。

<Form1>
 |
 |
 +-- <Text1>
 |
 +-- <Command1>
 |
 +-- <Option1>
 |
 +-- <Check1>
 |
 +-- <Frame1>
     |
     +-- <List1>

子ウィンドウの設定する

では実際にプレビュー画面に、子ウィンドウとして設定してみましょう。
プレビュー時には、引数に「/P」が飛んでくるので、スタートアッププロージャの「Case "/P"」の下にコードを書きます。

では実際のコードです。

Case "/P", "-P": 'プレビュー
  Preview = 1
'  ウィンドウハンドル取得
  hWnd = Right$(Trim$(Cmd), Len(Cmd) - 2)
'  親ウィンドウのサイズを取得(スクリーン座標)
  Call GetWindowRect(hWnd, rc)
'  メインフォームをロード(表示はしない)
  Load Frm_Main
'  プロパティの設定
  With Frm_Main
    .Left = 0
    .Top = 0
    .Width = (rc.Right - rc.Left) * Screen.TwipsPerPixelX  ' ピクセル単位に変換
    .Height = (rc.Bottom - rc.Top) * Screen.TwipsPerPixelY  ' ピクセル単位に変換
    WndStyle = GetWindowLong(.hWnd, GWL_STYLE) ' スタイルを取得
    Call SetWindowLong(.hWnd, GWL_STYLE, WndStyle Or WS_CHILD) ' スタイルを設定
    Call SetParent(.hWnd, hWnd) ' 親ウィンドウに設定
    .Show
  End With

最初にPreview = 1とやっているのは、「プレビューかそうでないか」を判定させる為のグローバル変数です。
変数や関数の追加については後でまとめて説明します。

まずプレビューウィンドウのハンドルを取得します。ちょっと無理矢理ですが、、、
次に、そのウィンドウハンドルからプレビュー画面のクライアント領域のサイズをスクリーン座標で取得します。
引数にはハンドルとRECT構造体なるものを渡します。
RECT構造体とは、ウィンドウの四辺の位置を格納する為のものです。

宣言は、

Type RECT
 Left As Long
 Top As Long
 Right As Long
 Bottom As Long
End Type

Left : 左辺の位置
Top : 上辺の位置
Right : 右辺の位置
Bottom : 下辺の位置


メインフォームを表示せずロードし、各種プロパティを設定します。
Left、Topを0(ゼロ)にする事で、プレビュー画面の「左端」「一番上」となります。
Width、Heightは、取得したサイズをピクセル単位に変換します。


次にGetWindowLongで、ウィンドウのスタイルを取得します。
スタイルとは、APIビューワの定数を見てもらうと分かると思います。WS_xxxと宣言されているもの全ての事です。

で、次にSetWindowLongで、さっき取得したスタイルと、WS_CHILD(子ウィンドウのフラグ)を組み合わせてメインウィンドウのスタイルを設定します。
さっき何故取得したかというと、この時にWS_CHILDしか指定しないと、ウィンドウのスタイルが破棄され正しく表示されなくなるからです。

次にSetParentでプレビュー画面をメインフォームの「親」、つまりメインフォームをプレビュー画面の「子」として設定します。
最後にShowメソッドで表示させます。


これで「子ウィンドウに設定」は終わりです。


前回との相違点

まず今回やった分のAPIの追加と、それに伴う定数や構造体の追加です。
各宣言は下のサンプルプロジェクトを見てください。

あとグローバル変数「Preview」とミューテックスのハンドル格納用「hMutex」の追加です。
hMutexは、終了時にCloseHandleで破棄させます。


終わり

「スクリーンセーバーを作る」如何でしたでしょうか。
動作の基本だけで終わってしまいましたが、あとはあなたの腕次第!
他人が作ったものより、より一層良いものを作ってみてください。
この「月刊プログラム」が、少しでもお役に立てれば筆者もろ手上げて喜びます。


今回のサンプルプロジェクト
vb05.lzh
(13.9KB)