プログラミング / Active Basic

  • 2017年12月13日:作成 12年間放置されていた予定項目です(^_^;)

2004年、業務でも使用可能なフリーの開発環境を探していて、Win32APIに対応、コンパイル型かつdll不要、使用上の制限もなく、と私の要望をほぼ満たすActive Basicに出会いました。なんとN88-Basicも走りその延長でも書ける*、と言う事もかつてのBasic使いには大変に魅力的でした。

2017.12.13:追記 *旧Basicユーザーの心理的ハードルを下げる、プログラミングのリハビリを行う、統合環境やコンパイルを体験するなど、N88-BasicはWindowsプログラミングの導入契機としては良かったのですが、やはりN88的記述には限界がありAB的記述に移行、現時点ではまったく使わなくなりました。

その後、メモ帳+コンパイラのフルスクラッチC++に目覚め(Linux対応で)そちらに移行しつつありますが、今でも主開発環境としてActive Basicを愛用しています。完成度の高い非常に優れた開発環境であり、当時の選択は正しかったと現在も確信しています、開発公開された山本氏に感謝 m(__)m。

但し、商用環境と異なり便利なテンプレートや補助機能があるわけではなく、参考となる情報も少なく、使いこなすにはそれなりの努力が必要になることも確かです。そこで、全くのWindowsプログラム初心者であった私が、Active Basicで学んだ事をスクリーンセーバーを題材に解説します(と言うか、スクリーンセーバーが主目的でした)。

適当なBitmap画像を準備すれば、すぐにスクリーンセーバーがコンパイル可能、かつての“Basicの勇者”であれば、すぐに理解できると思います。この機会にプログラミングに復帰してください!

なおこのセーバー雛形が最初に書かれたのは前の戌年の2006年で、いま改めて見直すと?と思う部分もあります。しかし、これ以上引っぱってもよい事はない、何が何でも戌年に合わせて公開する事を優先し原則当時のままとしました。幸か不幸か、私自身がほとんど進歩していないので、それで特に問題はなさそうです (^_^;)

この雛形ファイルは配布中で、使用許諾条項を守れる方限定でご利用いただけます。なお、当時は640×480のVGA環境常用であったため、_ アンダーバーによる改行を多用しています。害はありませんが、コメントアウトが若干面倒になります。気になる方はご自身で取っ払って下さい。

原因がActive Basicにあるのか私の書き方にあるのか不明ですが、環境によってウイルス判定されてコンパイルが中断されたり、でき上がった実行ファイルが隔離されたりする事がありますが、それは誤検出です。私はまだウイルスが仕込めるレベルまで到達しておりません、ご安心下さい (^^ゞ。

2017.12.13:追記 私はActive Basic 4.03.00からアップしていないため気が付きませんでしたが、4.11.03以降では正しくコンパイルできない事が発覚しました。取敢えずそのままアップいたしますので、4.10.02までの旧版でご利用下さい。なお、書き直された方がいらっしゃいましたら、伝言版までお越しください、お待ちしております (^^ゞ

雛形の基本仕様

スクリーンセーバーは設定された時間PCが操作されなかった場合に、Windowsから呼出される拡張子scrの実行ファイルです。

スクリーンセーバーフロー図
スクリーンセーバーフロー図

Visual Basic等の商用環境ではスクリーンセーバーとして機能する雛形が添付されており、実際に表示されるセーバー画面(のみ?)をコーディングすればでき上がるらしいです。しかし、Active Basic等のフリー環境では、セーバーに要求される機能、フロー図でグレー表示にした部分を自力で実装しなければなりません。

全関数に解説を付けたかったのですが、膨大な量となり挫折しました。一般的事項はヘルプやAB掲示板等をご参考下さい。ここでは、セーバー特有の機能部分と私が自力で解決した(と信じている ^_^;)オリジナル部分を中心に解説します。


コーディング前の下準備

一般作業ですがスクリーンセーバーで必要になるウインドウの種類とその作り方が含まれるので、示してあります。

プロジェクトの作成

まず、適当な名前でプロジェクトを作ります。作業中は適当なタイミングで『すべて保存』して下さい。

  1. 新規作成→プロジェクト→OK
  2. プロジェクト名はセーバーの名前(ファイル名)で、EXE-ノーマルウインドウベースを選択し、次へ
  3. コモンコントロールを使用するをチェック、ソースコードのコメントを生成する、はそのまま(意味不明)→次へ→完了、でメイン部分になるMainWndが自動的に作られる

MainWndの設定修正

  1. MatrialのMainWndを表示(作成時なら自動的に表示される)
  2. スタイルのプルダウンメニューから、ポップアップと枠無しを選択(MainWndは表示されないので関係ないが、自動的に余分なチェックが外れるので横着する)
  3. 可視のチェックをはずす
  4. タイプはデフォルトの通常ウインドウ

なお、この時RADツールで表示されるサイズは意味を持たない

ビットマップファイルの準備

  1. プレビュー画像用のビットマップファイル(PREVIW.BMP 152×112px 256色)1枚、セーバーで表示する画像用のビットマップファイル(DATA1~5.BMP 400×300px 256色)5枚をプロジェクトのフォルダに入れる(上書きでOK)
  2. Materialで1番目のBitmapリソース(IDB_BITMAP1)を挿入し、PREVIW.BMPを指定する
  3. 同様に、2~6番目(IDB_BITMAP2~6)を挿入し、それぞれ順番にDATA1~5を指定する

アイコンファイルの準備

付属のアイコンエディタは16色専用、かつ新規作成ができないので、それ用のダミーのアイコンファイルを準備する必要がある。

  1. 適当な32×32/16色アイコンファイルをプロジェクトのフォルダに入れる
  2. Materialでダミーのアイコンファイルを指定してIconリソースに挿入する
  3. 挿入されたIDI_ICON1(メイン)のダブルクリックでアイコンエディタが起動するので直ちに編集、少なくとも塗りつぶしておく(人様のものを勝手に使わない!)
設定画面
設定画面

設定用ウインドウの作成

  1. Materialでウインドウを挿入する
  2. 新規ウインドウでウインドウ名はOption、詳細は自動、タイプはダイアログテンプレート(モーダル)→作成
  3. MatrialのOptionを表示(作成時には自動的に表示される)
  4. メニューなし、アイコンはIDI_ICON1、スタイルはポップアップ、細線で、タイトルバー、システムメニュー以外のチェックをはずす
  5. 拡張スタイルを設定で最前面にチェック
  6. タイプがモーダルダイアログになっていることを確認
  7. 表示されているIDでラジオボタンとコマンドボタンを各2個、トラックバーを3本作り適当にレイアウトする

2017.12.13:追記 配布雛形はStatic1の下にStatic10を追加して、セーバー名と著作権情報を上下2行に分けて書けるようにしてあります。

プレビュー用ウインドウの作成

  1. Materialでウインドウを挿入する
  2. 新規ウインドウでウインドウ名はPreview、詳細は自動、タイプはダイアログテンプレート(モードレス)→作成
  3. MatrialのPreviewを表示(作成時には自動的に表示される)
  4. メニューなし、アイコン指定なし(Windowsロゴ)、スタイルはチャイルド、枠なしで、チェックはすべてはずす
  5. タイプがモードレスダイアログになっていることを確認

なお、この時RADツールで表示されるサイズは意味を持たない

セーバー本体のウインドウの作成

  1. Materialでセーバー本体のウインドウを挿入する。
  2. 新規ウインドウでウインドウ名はSaver、詳細は自動、タイプはダイアログテンプレート(モーダル)→作成
  3. メニューなし、アイコン指定なし(Windowsロゴ)、スタイルはポップアップ、枠なし、最大化以外のチェックをはずす
  4. 拡張スタイルを設定で最前面にチェック
  5. タイプがモーダルダイアログになっていることを確認

なお、この時RADツールで表示されるサイズは意味を持たない

この段階でもコンパイルできます。しかし実行しても表面上何も起こらず、タスクマネージャ以外で終了できなくなり、と面倒ですからコンパイルは後暫く待って下さい。


コーディング

MainWnd.sbp/本体制御部

MainWndがWindowsから最初に呼び出され、ここで非標準関数やグローバル変数の宣言を行い、その後の流れを制御する最重要部となります。なお名前はWnd(Window)ですが、この雛形ではこれが表示されることはありません。

グローバル変数は少ないほうが良い、無いのが最も良い、と言いながら7個もあります。がしかし、無理をしてグローバル変数を減らしたために、複雑になり汎用性を損なうのであれば、グローバル変数を用いて全体をわかりやすくするのも(今の私にとって ^_^;)賢い選択です(宿題:モジュール間のカシコイ引数受渡しの方法)。

MainWnd.sbp 48行

Const maisu = 5	'■■ 表示画像の枚数 ■■

48行目で表示画像の枚数を設定しています。画像枚数を変更したい場合はここを修正して下さい(なお、maisu=5なら実際には[5]で0~5の6になりますが、画像番号と一致している方がわかりやすい、とその時は思ったので (^^ゞ)。

MainWnd.sbp 81~82行

'*********************************************************************
'* 重複起動の制御                                                    *
'*********************************************************************

CreateMutex(0,0,"saver_sample")
If GetLastError()=183 Then End

スクリーンセーバーは無操作状態が設定時間続いたところで呼び出され起動しますが、起動後の無操作状態で反復して呼び出され重複起動する場合があります。私が確認した範囲ではWindows98では確実にこの現象が発生します。無数に起動したセーバーをキーボード連打ですべて終了させるのは苦痛で、最悪の場合はメモリを食い尽くしシステムを止めてしまいます。そこでスクリーンセーバには重複起動しない仕組みが必要になります。

81~82行で重複起動を制御しています。この位置にベタ書きした部分は確実に実行され、ベタ書き部分は上から下に実行されるので、先頭に記述してより早い段階で確実に停止させるようにしてあります(そこまで律儀に止めなくても問題は無いとは思いますが・・・)。

まず、CreateMutex関数(API:Win32API関数、以下同)でスレッド間の同期を取るためのMutex(同期オブジェクト)を生成します。通例、Mutexの値には実行ファイル名を使用しますが、同値のMutexは複数存在できないため、既に存在する場合は追加生成ではなくエラー値(183)が返されます。

連続して、呼び出し側スレッドが保有する最新のエラー情報を取得するGetLastError関数(API)を実行することで、エラーの有無=Mutexの有無=重複起動のチェックができます。

エラー値が183であれば、If命令語(AB:Active Basic関数、以下同)でEnd命令語(AB)に分岐、終了します。なお、原則としてプログラムは実行する事が無くなれば自動的に終了しますが、End命令語で明示的に終了させるのが正しい作法です。

スクリーンセーバーにはプレビュー画面(小窓)、設定画面、セーバー本体の3通りの起動パターンがありますが、Windowsから複数の起動パターンで同時に呼び出されることはありません(横着仕様、今となっては困った問題 ^_^;)。従って、重複起動を認める例外処理は不要、先頭で単純にはねて問題ありません。

MainWnd.sbp 88~182行

'*********************************************************************
'* コマンドラインオプションの取得と分岐                              *
'*********************************************************************

Sub MainWnd_Create(ByRef CreateStruct As CREATESTRUCT)

     Dim c_option As BytePtr'オプションを取得するための作業用変数
     Dim hPDC As DWord'プレビュー用親画面のデバイスコンテキスト
     Dim counter As Long'単なるカウンター

     'コマンドラインオプションの取得 *********************************

     c_option = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH)

     counter =1
     Do
          lstrcpy(c_option, Left$(Right$(GetCommandLine(),counter),1))
          If IsCharAlpha(GetByte(c_option)) Then
               Exit Do
          Else
               counter = counter+1
          End If
     Loop

     'コマンドラインオプションによる分岐 *****************************

     lstrcpy(c_option, CharLower(c_option))'比較前に小文字に変換する

     Select Case c_option <> "dummy"'こう言う書き方が必要?
          Case c_option="s"
               'c_optionを解放(以下同じ)
               HeapFree(GetProcessHeap(), 0, c_option)
               DialogBox(hMainWnd, "Saver")
          Case c_option="c" or c_option="r"
               HeapFree(GetProcessHeap(), 0, c_option)
               DialogBox(hMainWnd, "Option"): End'制御が帰ったら終了
          Case c_option = "p"
               lstrcpy(c_option, Right$(GetCommandLine(),counter-2))
               'Val関数は文字列演算子を引数に取れないため
               hPDC=Val(c_option)
               HeapFree(GetProcessHeap(), 0, c_option)
               CreateDialog(hPDC, "Preview")
          Case Else
               End
     End Select

End Sub

88~182行でコマンドラインオプションの取得と分岐(セーバー本体、設定、プレビュー小窓)を行っています。

GetCommandLine関数(API)でコマンドライン文字列を取得し、その中からコマンドラインオプションを切出す必要がありますが、戻り値の処理には注意が必要です。

Cルートにsaver_sample.scrを置いた場合
9598XP2000
右クリック構成"C:\saver_sample.scr"C:\saver_sample.scr*
右クリックテスト"C:\saver_sample.scr" /S
ダブルクリック起動"C:\saver_sample.scr" /S
プロパティ(小窓)C:\SAVER~1.SCR /p 12345
プロパティ/設定C:\SAVER~1.SCR /cC:\SAVER~1.SCR /c:12345
プロパティ/プレビューC:\SAVER~1.SCR /s
セーバー呼び出しC:\SAVER~1.SCR /s

*パスにスペースや非アルファベットを含む場合は2000も""が付く

画面のプロパティから呼び出される場合は8・3形式(省略形式)のパスで、人間が直接ファイル操作した場合はフルパスで呼び出されます。Windowsのバージョンでも呼び出され方が異なっており、パス中のスペースや非アルファベットの有無でも異なることがあると言う念の入れ様です(~_~;)。

これは氷山の一角、GetCommandLine関数に限った問題ではなくWindowsは仕様不整合の塊ですから、全バージョンで動作させたいならこのような不整合も考慮する必要があります。

本来なら省略形式かフルパスか、""(ダブルクォーテーション)の有無、追加のオプション(この場合はデバイスコンテキスト、例示は12345)の有無など考慮しなければなりませんが、幸いにもスクリーンセーバーの場合は単純に処理できます。

取得したコマンドライン文字列の最後のアルファベット1文字がコマンドライン引数になります。Sまたはsならセーバー本体の起動、cなら設定画面の起動、pならプレビュー画面の小窓の要求です。右クリック設定の場合はオプションがありませんが、拡張子のrで判定可能です。

文字列の左端から指定したバイト数の文字列を取得するLeft$関数(AB)と文字列の右端から指定したバイト数の文字列を取得するRight$関数(AB)を組み合わせて任意の1文字を切り出しています。

Byte型やBytePtr型でもString型の処理である代入文、c_option = Left$(Right$(GetCommandLine(), counter), 1)のような記述は認められています。しかし、記述の一貫性を高め余分な事を覚えなくて済む様に、指定した文字列を指定した文字列バッファにコピーするlstrcpy関数(API)を使用しています。

GetByte関数(AB)でポインタ(c_option)が示すByte型データを取得し、IsCharAlpha関数(API)でそのByte型の文字がアルファベットかどうかを判定しています(が、最初からByte型を使えば楽チンな気がする ^_^;)。

counterに1を加算しながらDo~Loop制御文(AB)で後方から1文字ずつチェックし、アルファベットが見つかったらExit Doでループを抜けて、コマンドラインオプションによる分岐に進みます。

コマンドラインオプションにはSとsがあり、拡張子もscrではなくSCRの可能性が ありますから、事前にCharLower関数(API)で小文字に変換して判定の手間を減らします。

条件分岐には指定条件によって複数の命令ブロックのいずれかを実行するSelect Case命令語(AB)を使っていますが、ナゼか変数のみ記述すると正しく動作しない(@_@)?ので、私は変数ではなくc_option <> "dummy"のような害の無い式を記述してあります。

Select Case文で、設定画面とセーバー本体にはモーダルダイアログボックスを作成するDialogBox関数(AB)で、プレビュー(小窓)にはモードレスダイアログボックスを作成表示するCreateDialog関数(AB)で分岐しています。

モーダルダイアログボックスは親ウィンドウを無効にし、独自にメッセージループを開始、ダイアログボックス内のEndDialogで終了(破棄)されるまで制御を返しません。つまり、モーダルダイアログボックスを閉じない限り、親ウィンドウを含む別のダイアログを操作することは出来ません。これに対し親ウインドウとメッセージループを共有するモードレスダイアログボックスは、ダイアログボックスを表示したままで別のダイアログを操作可能です。

プレビュー(小窓)は表示した状態で、セーバーのプロパティを操作するのでモードレスダイアログを、セーバーの設定画面を表示した状態でプロパティを操作されると都合が悪いのでモーダルダイアログを作成します。セーバー本体はどちらでも構わないのですが、なんとなくモーダルです(^_^;)。

HeapAllocで確保した領域が不要になったら解放するのが鉄則なので、分岐したら用済みのc_optionをHeapFree関数(API)で解放します。個々の領域は微々たるサイズですが、蓄積すればいずれシステムにダメージを与えます

特にWindows9x系はヒープ領域が小さくシビアに反応するので、起動後時間がたつと障害が発生しその時間が9x系で短い場合、領域の解放忘れを疑う必要があります。また、反復して実行される位置で確保してしまうと、HeapFreeにたどり着く前にヒープ領域を使い切って止まってしまう超お間抜けな事態も起こり得ます(経験者は語る(^^ゞ)。

プロパティ(小窓)の表示、コマンドラインオプションのp以降の数字は小窓の親ウインドウのデバイスコンテキストです。既に求めてあるpの位置、counterの値からこれを取得しますが、文字列のままでは使えないので、Val関数(AB)で数値に変換しています。

Val関数が文字列演算子を引数に取れないためc_optionを使いまわしていますが、あまり格好の良い方法ではありません。ByVal指定して、hPDC = Val(StrPtr(ByVal Right$(GetCommandLine(), counter-2))で動作した(ような)記憶があるのですが、確認したらエラーになりました(@_@)?

なお、プロパティ/設定のcに付いている数字は正体不明ですが、少なくとも今回の雛形では気にする必要はありません(だんだん説明が雑になる ^_^;)。

Option.sbp/設定画面

右クリック→構成(設定)やプロパティ→設定で表示される、設定画面のコーディングを行います。トラックバーなどコントロールの使い方が中心です。

Windows標準添付のスクリーンセーバーの場合は、設定画面を起動したことでプレビューが停止してブランクになってしまったプレビュー小窓を“設定画面で隠す”姑息な仕様になっています。しかし、この仕様上の不備を隠す義理が私にはなく、かつ面倒なのでこの雛型の設定画面は常に正々堂々中央表示です。

TrackBar1(display)が画像の表示時間、TrackBar2(interval)が画像の間隔で、雛形では秒数になります。0秒~59秒が設定可能ですが、表示時間を0秒にすると画面が激しく明滅するので、最小値を1にしています。不用意なピカチュウ状態*を作らない配慮も必要です(^_^;)。“セーバー本体のコーディング”まで進んだら、この当たりを思い出してください。

2017.12.13:追記 *訪問者にピカチュウ事件を知らない世代が既にいるかも・・・。

Option.sbp 162~184行

'*********************************************************************
'* ファイルパスの取得                                                *
'*********************************************************************

Sub path()

     Dim counter As Long

     path = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH)

     'ファイル名を含むパスを取得
     GetModuleFileName(GetModuleHandle(0), path, MAX_PATH)

     '最後の\の位置を探す
     counter =1
     Do
          If Left$(Right$(path, counter),1)="\" Then
               Exit Do
          Else
               counter = counter+1
          End If
     Loop

     '最後の\以降=ファイル名を切り捨てる
     lstrcpy(path, Left$(path,lstrlen(path)-counter+1))

End Sub

Saver固有の問題ではありませんが、INIファイルを使う(レジストリを使わない)私の場合は、実行ファイルの場所=ファイルパスが必須になります。162~184行で取得していますが、地味~に苦労しました。

GetModuleFileName(API)は "" ダブルコートを取り除いてくれるのは親切なのですが、ファイル名を含むパスを返してきますので、ファイル名を削る必要があります。名前を付ける本人ですからファイル名(あるいは文字数)決め打ちで削る事も可能ですが、汎用性を持たせるために最後の\を探して切り分けています。

2017.12.13:追記 当時は名案だと思ったのですが、これでは日本語環境専用になってしまう気がします。OSが自動的に\(円マーク)を/(スラッシュ)に読み替えてくれる、と言う事はあるのでしょうか?

2018.01.09:追記 雛形のiniファイル名はsaver_sampleですが、このままだと複数セーバーを作成同居させた場合にバッティングが起こってしまいます。複数作成予定の場合は、Option.sbpの116~156行 ini_load()/ini_save() のsaver_sample(計8ヶ所)をセーバー名を反映した固有名に変更して下さい。

Preview.sbp/プレビュー画面

セーバー設定画面の小窓
セーバー設定画面の小窓

セーバーのインストール画面の小窓に表示される、プレビューを表示します。コーディング自体、特に目新しいものはありません。

セーバー本体の表示サイズを調整しこの小窓を出力先に指定すれば、動きのあるプレビューになるはずですが、うまくいきませんでした。何かないと寂しかったので取敢えず静止画を表示していますが、これが動かなくても困る事はありませんので、当面これ以上頑張る予定はありません。。

2017.12.13:追記 あれから12年が経過致しましたが、予定通り頑張っておらず進捗もありません (^^ゞ。

設定画面の章でも書きましたが、セーバー呼出しの仕様不備の為、設定画面が起動するとプレビューはブランクになります。これに対応するには大幅な見直しが必要になり、標準セーバーも対応していないので、このままとします(設定画面で隠す、これが最も簡単で手っ取り早いです、つまり、MSは正しいと ^_^;)。

プレビュー小窓のサイズを152x112px固定としており、Windows 9x~XPではそうなっていますが、以降のOSでは確認していません。問題がでたら、その時考えます。

Saver.sbp/セーバー本体

ここが実際にセーバーとして表示される部分で、雛形では単純な縦フェード効果のみとなっています。もっと凝った効果が欲しい場合は208~276行を書き直す、または130~137行のSelect Case文で効果のパターンを追加して下さい。

枚数を増やしたい場合は、MainWnd.sbp 48行の maisu = 5 を修正し、その枚数に合わせSaver.sbp 103~126行のコメントアウト(/* */)を消す、そして増やしたBitmapリソースをMaterialでIDB_BITMAP[6~]に追加して下さい。10枚まではこれでOKです。

Saver.sbp 23行

' ここから下は、イベントプロシージャを記述するための領域になります。

timeBeginPeriod(1)'タイマーの最小解像度を1ミリ秒に設定

動きのあるプログラムでは速度調整が必要になり、常套手段はループ内にSleep(API)を置いてウェイトをかける方法で、小刻みに止めることでCPUを独占させない効果もあります。

しかし、Sleep(1)でも(2)でも(10)でも速度/ウェイトが変化する様子はなく、(0)にすると爆速かつ異常なCPU利用率となり・・・結果、[速い]と[遅い]ではなく、Sleep(10)と(20)などとして[遅い]と[もっと遅い]を設定して誤魔化すことになります。これはActive Basic固有の問題ではなく、Windowsのタイマー最小解像度のデフォルトが1ミリ秒よりはるかに長い為(15ミリ秒とも言われる)で、より高解像度にセットし直すことで解決できます。商用環境でこの問題が起こらないのは、標準テンプレートに、最初からこの機能が組み込まれているのだと思います。

雛形では23行のtimeBeginPeriod(API)でタイマー最小解像度を1ミリ秒に設定しています。雰囲気ではセットしたプログラムの終了時にデフォルトに戻るようですが、作法と言う事で、282~298行の終了判定/終了時にtimeEndPeriod(API)で戻しています。

2017.12.13:追記 このバグ取りが上手くいかず、気晴らしにMediaPlayerでMIDIを鳴らしたら、突如プログラムが爆速に・・・MIDI再生に備えMediaPlayerが解像度を変更したからなのですが、一瞬「プログラムもBGMでノリノリになる!」と信じかけました、懐かしいです (^^ゞ

Saver.sbp 68~126行

     '画像の読み込み******************************

/*	For x = 1 To maisu Step 1
    memdc[x]=CreateCompatibleDC(hdc)
    hBmp[x]=LoadImage(GetModuleHandle(0), IDB_BITMAP[連番/x + 1], _
         IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR)
    SelectObject(memdc[x], hBmp[x])
	Next	画像読込を変数maisuを使って必要分読み込むように作りたかったが
			IDB_BITMAP[連番/x + 1]の部分が処理できず挫折中	*/

    memdc[1]=CreateCompatibleDC(hdc)	'1枚目
    hBmp[1]=LoadImage(GetModuleHandle(0), IDB_BITMAP2, IMAGE_BITMAP, _
         0, 0, LR_DEFAULTCOLOR)
    SelectObject(memdc[1], hBmp[1])

                 /// 画像3~4は省略 ///

    memdc[5]=CreateCompatibleDC(hdc)	'5枚目
    hBmp[5]=LoadImage(GetModuleHandle(0), IDB_BITMAP6, IMAGE_BITMAP, _
         0, 0, LR_DEFAULTCOLOR)
    SelectObject(memdc[5], hBmp[5])

/*  memdc[6]=CreateCompatibleDC(hdc)	'6枚目
    hBmp[6]=LoadImage(GetModuleHandle(0), IDB_BITMAP7, IMAGE_BITMAP, _
         0, 0, LR_DEFAULTCOLOR)
    SelectObject(memdc[6], hBmp[6])

                 /// 画像7~9は省略 ///

    memdc[10]=CreateCompatibleDC(hdc)	'10枚目
    hBmp[10]=LoadImage(GetModuleHandle(0), IDB_BITMAP11, IMAGE_BITMAP, _
         0, 0, LR_DEFAULTCOLOR)
    SelectObject(memdc[10], hBmp[10])    */

Saver.sbp 68~126行で画像の読込(メモリ上への展開)を行っていますが、事もあろうにMASUGU記述となってしまいました (^^ゞ。

本当は変数(maisu)を使ったForループで必要枚数を自動的に読込むように作りたかったのですが、IDB_BITMAPの連続処理に失敗し挫折、そのまま放置されています。該当部分はコメントアウトして残してありますので、何方か書直しできましたら伝言板までお越しください、お待ちしております m(__)m。

なお、プログラミング初心者でも枚数が簡単に増やせるように、10枚分まで準備し、6~10枚部分をコメントアウトしています。10枚まではコメント削除で、それ以上は該当部分の追加で対応して下さい。

Saver.sbp 282~298行

'*********************************************************************
'* 終了判定                                                          *
'*********************************************************************

Sub owari()

     Dim Cursor_1 As POINTAPI'マウスカーソルポジションを格納
     Dim Cursor_2 As POINTAPI

     GetCursorPos(Cursor_1)'マウスカーソルポジションを取得
     Sleep(weight)
     GetCursorPos(Cursor_2)'マウスカーソルポジションを再度取得

     'マウスカーソルが移動していれば終了
     If Abs(Cursor_1.x-Cursor_2.x)>1 or Abs(Cursor_1.y-Cursor_2.y)>1 Then _
        timeEndPeriod(1) :End

     '待機中のイベントキューがあれば終了
     If GetInputState()<>0 Then timeEndPeriod(1) :End

End Sub

スクリーンセーバーは作業再開時に終了しなければなりませんが、雛形では282~298行の終了判定でマウス/キーボード操作をキューに終了させています。

マウスのボタンとキーボードの操作はお約束のGetInputState(API)で検出していますが、マウスの移動判定はたぶん他所にはない横着の結晶 (^_^;)完全オリジナルです。通常は、起動時の座標を変数に保持し周期的に現座標とそれを比較して移動の有無を検出しますが、変数の引継も面倒だがグローバル変数もこれ以上増やしたくない、と言う事で移動したか?ではなく移動中か?を検出しています。

GetCursorPos(API)をSleep(Weight)を挟んで2回実行することで、その間に移動したか≒移動中か?を検出しています。それでいいのかと思われるかもしれませんが問題なく機能しており、移動量 >1 としているため振動によるマウス移動などで意図せず終了してしまう事もありませんし、この値を大きくすることでより感度を下げる(ゆっくりした動きを無視する)ことも可能です。たとえ出発点が横着であったとしても、結果的にこれは名案だと思っています(自画自賛 ^_^;)。

また、描画ループから呼出され続けるこの位置にSleep/ウェイトが置かれていることも重要です。より呼出し頻度の低い場所に置いてしまうとSleep(500)のような大きな値でないとウェイトとして機能せず、その間応答しないため、終了時の感触が悪くなります。その点、最も頻度の高い終了判定内であればSleep(1)や(2)でも十分なウェイトとなり、終了時のもたつきも発生しません。

2017.12.19:追記 296行 If GetInputState()<>0 Then timeEndPeriod(1) : End をコメントアウトする事で、キーボードに反応しなくなりますから、セーバーが起動した状態でシステムモニタを表示してメモリリークやCPU使用率をチェックする事が可能になります。多数のループを回し続ける事の多いスクリーンセーバーでは、メモリの解放忘れ等あると、使用可能メモリが急速に減少していく(メモリリーク)ので簡単に判断できます。


コンパイル

コーディングが完了したら、リリースコンパイルします。でき上がったsaver_sample.exeをセーバー名(2バイト可).scrにリネームしてできあがりです、お疲れ様でした (^_^)/

・・・と、何時も上手く行けば良いのですがエラーになる事もあります、むしろバグなしの方が珍しく、現実はプログラミング=バグ取りだと思っていマチガイありません。コンパイルエラーが発生した場合は指示に従ってバグ取りを行えばよいので簡単ですが、コンパイルそのものは完了するが正常に動作しない状態が厄介です。

例えば、この雛形をActive Basic 4.11以降でコンパイルするとエラーはでませんが、セーバーは起動しません。使用バージョンによって全く起動しない場合、内部的には起動しているが表示されない場合などあって、何をどうすれば良いのかさっぱり (@_@) です。まぁ、4.0コンパイルの実行ファイルに致命的瑕疵があるなら話は別ですが、まったく実害はないので、幸いこの問題で自分は困っていません (^_^;)。

雛形が動作することはシツコク確認済みです。ソースはそのままで画像入替のみ行い、4.10以前でコンパイルして動作しない場合は、必要枚数のBMPが挿入(準備)されている事を確認してください。画像が不足している場合コンパイルはできますが、エラーで動作しません。


項目の目次に戻る サイトのトップに戻る