月刊ソフト作り!
Make The Software For VisualBasic
『ビットマップビューアを作る(その4)』
2002.May.21
Presented by kouta_y
感想等は掲示板、苦情はメールへ。
前回の続きです。
今回はグラフィックスの技というより、VBの技です。
前回までのあらすじ
前回はメモリDCを使って再描画を実現させました。
折角メモリDCがあるので、いろいろ活用したいところですが、今回はVBのコントロールの方を活用させたいと思います。
そういえばVBのコントロール使ったサンプルってないなぁ・・・。
スクロールバーを使う
スクロールバーというのは、皆さん一度以上は必ずみたことがあるはずです。
IEでいえば、表示できない部分がでてくると、ウィンドウの右端と下にバーが出てきますよね?
それです。
今回は、大きい画像の場合スクロールバーを使用して画像を描画させる位置を移動させてみたいと思います。
こういうやつです。
スクロールバーコントロールの挿入
スクロールバーコントロールは、VisualBasic6.0の標準ランタイムライブラリに入っていますので、特殊な.OCXファイルなどはいりません。
今回もいつも通り、前回のサンプルをそのまま使います。
スクロールバーはどれ?
コントロールバーのところに他のと一緒にあります。
赤丸内の右が、垂直スクロールバー。左が水平スクロールバーと呼ばれています。
ドラッグ&ドロップでフォームに貼り付けてください。
プロパティなどは実行時(つまりソースに直接)に指定しますので、位置や他のプロパティは適当でかまいません。
ただ、オブジェクト名と横幅や縦幅だけはここで指定します。
VScroll1 | |
(オブジェクト名) | VScrl |
Width | 17 |
HScroll1 | |
(オブジェクト名) | HScrl |
Height | 17 |
ちなみに「V」というのは「Verticality」の略で「垂直」。「H」は「Horizontality」の略で「水平」という意味です。
野球好きの人なら「Vスライダー」「Hスライダー」と覚えれば簡単です。
筆者のレイアウトはこんな感じです。
随分アバウトですけど、デザイン時はこれでオッケーです。
フォームの背景色も変えてあります。
コードは?
では早速コーディングに入りましょう。
今回はAPIの追加はありません。(ほっ)
スタートアップルーチンはほとんど同じですが、一部変更があります。
おまけコードは邪魔なのではぶいています。
Public BI As BITMAPINFO Public hMemDC As Long Public hBmp As Long Public hOldBmp As Long |
Public Sub Main() Dim BFH As BITMAPFILEHEADER Dim Bit() As Byte Dim Cmd As String Dim Argv() As String Dim Argc As Long Dim NO As Integer Cmd = Command$() Argc = ArgvArgc(Cmd, Argv) If Argc <= 0 Then Exit Sub NO = FreeFile() Open Argv(0) For Binary As #NO Get #NO, , BFH If BFH.bfType <> BM Then ' ビットマップではない MsgBox "形式エラー" Close Exit Sub End If Get #NO, , BI.bmiHeader With BI.bmiHeader If .biBitCount < 16 Then ' 8Bit以下の場合カラーテーブルを読み込む Get #NO, , BI.bmiColors End If ' ReDim Bit(.biHeight * .biWidth * 4) As Byte ' 余分に確保 ReDim Bit(((.biWidth * .biBitCount + 31) \ 32) * 4 * .biHeight) As Byte ' バイト数を計算 End With Seek #NO, BFH.bfOffBits + 1 ' ピクセルビットのポインタへ移動 Get #NO, , Bit Close Load Form1 'Form1.Width = (BI.bmiHeader.biWidth * Screen.TwipsPerPixelX) + _ (Form1.Width - (Form1.ScaleWidth * Screen.TwipsPerPixelX)) ' Twipに変換 'Form1.Height = (BI.bmiHeader.biHeight * Screen.TwipsPerPixelY) + _ (Form1.Height - (Form1.ScaleHeight * Screen.TwipsPerPixelY)) ' Twipに変換 Form1.Caption = Argv(0) ' メモリDCを作成 hMemDC = CreateCompatibleDC(Form1.hdc) ' ビットマップを作成 hBmp = CreateCompatibleBitmap(Form1.hdc, Form1.ScaleWidth, Form1.ScaleHeight) ' ビットマップを選択 hOldBmp = SelectObject(hMemDC, hBmp) ' DIBを転送 Call SetDIBitsToDevice(hMemDC, 0, 0, BI.bmiHeader.biWidth, BI.bmiHeader.biHeight, _ 0, 0, 0, BI.bmiHeader.biHeight, Bit(0), BI, DIB_RGB_COLORS) Form1.Show End Sub |
さて、前回との変更点は、
BI変数をグローバル変数にした。
フォームのサイズは変更しない。
フォームの表示はモードレスで。
APIの後処理は、フォームのアンロードイベント内で。(後述)
これだけです。
ここで重要なのはBIをグローバル変数としているところです。
後にも説明しますが、これはスクロールバーの値(位置)や、画像の大きさを計算しないといけないためです。
スクロールバーはどうやって使う
結構面倒くさいです。
スクロールバーをスクロールしたら、勝手に画像が動いてくれるわけではありません。
自分で画像の表示位置、スクロールのさせかたなど全部計算してやらなくてはいけません。
しかしVBはまだ簡単な方なのです。
ですが、こういう計算がパパッとできるようになれば、他のことにもかなり応用がきくようになるはずです。
まず、ここでやる簡単な「流れ」で説明します。
分かりやすくするため、垂直スクロールで説明します。
・ 最初に画像の大きさからスクロールの量を決める。
・ フォームのサイズが変更されたら、(画像の高さ-フォームの描画領域の高さ+スクロールバーの幅)でスクローロバーのMAX値を出す。
・ フォームのサイズが変更されたら、スクロールバーの位置とサイズをフォームにあわせて変更する。
・ フォームの高さが画像の高さを超えてたら、スクロールバーを無効にする。
・ ユーザーがスクロールしたら、再描画を行う。
・ 再描画は、スクロールバーの値をBitBlt関数に渡してメモリDCをフォームに転送する。
こんな感じです。
文中にでてくる描画領域とは、タイトルバーやメニューバーなどを除いた領域のことです。
例えば、BitBlt関数とかで、X,Yの引数に0,0を渡しても、タイトルバーに描画されることはありませんよね。
つまりは「描画領域」とは、実際に描画させることが可能なウィンドウの領域で「クライアント領域」とも呼ばれます。
これは大事なことなので覚えておいてください。
では実際のフォームモジュール内のコードを見てみましょう
Private Sub Form_Load() ' 1/10くらい(テキトー) VScrl.LargeChange = BI.bmiHeader.biHeight \ 10 HScrl.LargeChange = BI.bmiHeader.biWidth \ 10 End Sub Private Sub Form_Paint() ' メモリDCの内容をフォームに転送 Call BitBlt(Me.hdc, 0, 0, Me.ScaleWidth, Me.ScaleHeight, _ hMemDC, HScrl.Value, VScrl.Value, vbSrcCopy) End Sub Private Sub Form_Resize() With VScrl ' MAX値を計算 .Max = BI.bmiHeader.biHeight - Me.ScaleHeight + .Width If .Max <= 0 Then .Enabled = 0 ' 領域をオーバーしたら無効にする Else .Enabled = 1 End If ' 位置とサイズを計算 .Top = 0 .Left = Me.ScaleWidth - .Width If Me.ScaleHeight >= HScrl.Height Then .Height = Me.ScaleHeight - HScrl.Height End If End With With HScrl .Max = BI.bmiHeader.biWidth - Me.ScaleWidth + .Height If .Max <= 0 Then .Enabled = 0 Else .Enabled = 1 End If .Left = 0 .Top = Me.ScaleHeight - HScrl.Height If Me.ScaleWidth >= VScrl.Width Then .Width = Me.ScaleWidth - VScrl.Width End If End With End Sub Private Sub Form_Unload(Cancel As Integer) ' 前のビットマップに戻す Call SelectObject(hMemDC, hOldBmp) ' ビットマップを解放 Call DeleteObject(hBmp) ' メモリDCを解放 Call DeleteDC(hMemDC) End Sub Private Sub VScrl_Change() Me.Refresh End Sub Private Sub HScrl_Change() Me.Refresh End Sub |
さて、一番の見所がForm_Resizeイベントでしょうか。いろいろ書いてあります。
まず最初に説明した通り、スクロールバーの移動量を画像の1/10としています。これは適当です。
Loadイベントの次に呼ばれるのが、ここではResizeイベントです。
まずスクロールバーのMAX値を計算しています。式は上で書いた通りです。
なぜ+Widthしているのかは後で説明します。
計算結果がマイナスなら、画像の大きさよりフォームの方が大きいという事ですので、スクロールバーを無効にします。
垂直スクロールバーのTOPは常に0です。
Leftは上手く右端にくるようにします。
次に高さですが、ここでもマイナス値にならないようにIF文でフォームの大きさをみてから計算しています。
HScrlも全く同じ事をしています。
各スクロールバーのChangeイベントは、スクロールバーがスクロールされたら呼び出されます。
このプロージャは再描画命令だけ出して終わります。
では再描画プロージャ(Paintイベント)ですが、前回同様BitBlt関数でメモリDCの内容を転送するだけです。
しかし、今回の見所は後ろから1番目と2番目の引数です。
この引数にはスクロールバーの現在の値を渡します。スクロールバーの値はそのまま画像の位置として計算しているので、そのままValueを渡します。
最後にフォームが閉じられたら、APIで作成したリソースを解放してあげます。
これを忘れると、リソース消費しまくりの最低なアプリケーションとなってしまいます。
完成
ではビットマップファイルをドラッグ&ドロップで渡してみましょう。
こんな感じで表示されました?
スクロールしてみたり、サイズを変更したりといろいろ試してみてください。
今回はこれで終わりです。
ちょっと待ったー!
何かおかしなことに気付きませんか?
はい。
「え?何が?」
と思った人は、ウィンドウの右下の方をみてください。
真っ黒な正方形な部分がありますよね。
「え?もしかして・・・」
はい。その通り。画像の一部です。
なんとこのスクロールバーとんだ欠陥品です。
そうなんです。
通常ならば、あそこには斜め線があって、サイズ変更可の表示がされて、マウス近づけるとマウスアイコンが変わったりするんですが・・・。
このサンプルにそんな現象はおきません。
「ふざけるな!ここまでの苦労はなんだったんだ!」
まぁまぁ。(←あほ)
もちろん筆者はこうなることを知ったうえで今回やりました。
なんてことでしょう。確信犯です。
実は、スクロールバーにはスクロールバーコントロールとウィンドウスクロールバーという2つのものがあるんです。
上で書いたようなスクロールバーはウィンドウスクロールバーで、今回はそれに似せたインチキスクロールバーなのです。
ウィンドウスクロールバーを作るには、作るだけならそれほど難しくありません。
ですが、今回やったような画像の移動など実際に使う場合、VB向きではないんです。
あとあとの「月刊ソフト作り」でもちょっとずつ説明していきますが、これを実現させるにはウィンドウメッセージを受け取れるウィンドウプロージャというものが必要になり、
フォームモジュールは一切使わず、全て標準モジュール内でコードを組まなければなりません。
しかもその方法は、API関数を山ほど使いますので、宣言も増えてめちゃくちゃ面倒くさいです。
フォームモジュールを使わないVBなんて、既にVBではないと思います。
そんな事をするくらいなら、VBではなくC言語など別の言語で作るべきです。(まあ例外もあるでしょうが)
ってなわけで、今回のこのやり方はインチキスクロールバーだということを、十分認識しておいてください。
ちなみにチカチカとスクロールバーが光るのは、OSの仕様らしいです。
光らせないことも可能ですが、ここでは割愛させていただきます。
終わり
さて、今回はコントロールの技となってしまいましたね。
上でインチキ、インチキと連呼しましたが、プログラムというのは「そう見せている」といったインチキ技が多数存在します。
再描画なども、「消えていないように見せている」だけで実際消えています。
それがコンマ数ミリ秒の世界なので、人間には確認することができないだけなんです。
例の如く、今回のサンプルは下から落とせます。
参考にしてください。
vb10.lzh(9.64KB)