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


『ビットマップビューアを作る(その3)』
2002.May.13

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

会社
前回の続きです。
今回はそんなに難しくありません。


前回までのあらすじ

さて前回はやっとこさ「ビットマップビューア」と名乗れるものが出来上がりました。
今回は「GDIの知識」でも触れた「再描画」というものを実現してみようと思います。
APIの宣言がまた増えてしまいますが、「VBなので・・・」という事で諦めましょう。。。


再描画とは?

その名の通り、「再び描画」します。
再び描画するというのは、ウィンドウを表示させても、他のウィンドウに邪魔され隠れてしまう事があります。
んで再度そのウィンドウを表示させようとした場合、もう一度ウィンドウの内容を更新しなければなりません。
それを「再描画」と言います。


どうやるのか

ここで紹介する方法は、ちと面倒なやり方かもしれません。
今回に限りませんがソースの書き方なんて人それぞれです。
「再描画はこの方法しかない」とは思わないでください。
もっと簡単は方法もありますし、逆に「こんなやり方もあったのか!」なんて思ってくれれば幸いです。
その中で自分にあったプログラムを見つけてください。

・・・では、高尚な前置きここまでにして(高尚か?)さっそく方法です。

まずメモリデバイスコンテキスト(以下メモリDC)を作成し、そこにビットマップを表示させます。
んで再描画命令がきたら、論理DCつまりメモリDCの内容を物理DC(フォームのDC)にビットマップを転送します。
こんだけです。
しかしこれだけといってもなかなか手順が面倒な上、最初の内は理解しがたいかもしれません。
いろいろソースをいじって「結局こういう動作をするんだ」というのを理解してください。


使うAPIの宣言

サンプルは前回までのサンプルと同じものを使います。
持っていない人はここから落とせます
ちなみに前回説明した関数ははぶいています。

宣言と説明です。
' hdc互換のメモリDCを作成
Public Declare Function CreateCompatibleDC Lib "gdi32" ( _
  ByVal hdc As Long _
) As Long

' hdc互換のビットマップを作成
Public Declare Function CreateCompatibleBitmap Lib "gdi32" ( _
  ByVal hdc As Long, _
  ByVal nWidth As Long, _
  ByVal nHeight As Long _
) As Long

' グラフィックオブジェクトを選択
Public Declare Function SelectObject Lib "gdi32" ( _
  ByVal hdc As Long, _
  ByVal hObject As Long _
) As Long

' オブジェクトリソースを解放
Public Declare Function DeleteObject Lib "gdi32" ( _
  ByVal hObject As Long _
) As Long

' メモリDCを解放
Public Declare Function DeleteDC Lib "gdi32" ( _
  ByVal hdc As Long _
) As Long

' 画像転送
Public Declare Function BitBlt Lib "gdi32" ( _
  ByVal hDestDC As Long, _
  ByVal x As Long, _
  ByVal y As Long, _
  ByVal nWidth As Long, _
  ByVal nHeight As Long, _
  ByVal hSrcDC As Long, _
  ByVal xSrc As Long, _
  ByVal ySrc As Long, _
  ByVal dwRop As Long _
) As Long


CreateCompatibleDC

指定されたDCに互換のあるメモリDCを作成します。
メモリDCを作成する場合、何かしら「元」となるデバイスコンテキストが必要となります。

hdc 互換をとるデバイスコンテキスト。NULL(ゼロ)を指定すると画面に互換のあるメモリDCを作成する。

戻り値は、成功したらメモリDCハンドル、失敗したらNULL(ゼロ)が返ります。


CreateCompatibleBitmap

指定されたDCに互換のあるビットマップを作成します。
ここでいうビットマップというのはDDBです。

hdc 互換をとるDC
nWidth、nHeight ビットマップの幅と高さ。このパラメータに0を指定すると1×1のモノクロビットマップを作成します。

戻り値は、成功したらビットマップのハンドル(HBITMAP)、失敗したらNULL(ゼロ)が返ります。


SelectObject

指定されたGDIオブジェクトをデバイスに選択します。古いオブジェクトは新しいものに置き換わります。
作成したGDIオブジェクトはこの関数によって選択させなければ意味がありません。

hdc 対象となるDC
hObject 新しいオブジェクト

戻り値は、成功したら置き換わる前のオブジェクトのハンドル。失敗したらNULL(ゼロ)が返ります。


DeleteObject

指定されたGDIオウブジェクトを削除し、割り当てられていたリソースなども全て解放します。
使い終わったオブジェクトは必ずこの関数にて削除してください。

hObject 削除するオブジェクト

戻り値は、成功したら0以外、失敗したら0が返ります。


DeleteDC

指定されたデバイスコンテキストを削除します。
使い終わったDCは必ず削除してください。
ReleaseDCとは違いますので気をつけてください。

hdc 削除するデバイスコンテキスト

戻り値は、成功したら0以外、失敗したら0が返ります。


BitBlt

デバイスにビットブロックを転送(コピー)します。
DDBを操作する代表的な関数の1つです。この関数にて画像の転送を行います。

hDestDC コピー先のDC
x、y コピー先の左上端xとy座標
nWidth、nHeight コピー先の幅と高さ
hSrcDC コピー元のDC
xSrc、ySrc コピー元の左上端xとy座標
dwRop ラスタオペレーション。ここについてはこのページを参照してください。

戻り値は、成功したら0以外、失敗したら0が返ります。


コードは

では実際のコードです。
まず完成コードを見てみましょう。 フォームのAutoRedrawプロパティはFalseに設定してください。


Private Sub Form_Paint()
' メモリDCの内容をフォームに転送
Call BitBlt(Me.hdc, 0, 0, Me.ScaleWidth, Me.ScaleHeight, hMemDC, 0, 0, vbSrcCopy)
End Sub
Public hMemDC As Long
Public hBmp  As Long
Public hOldBmp As Long


Public Sub Main()
Dim BFH  As BITMAPFILEHEADER
Dim BI   As BITMAPINFO
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 vbModal

' 前のビットマップに戻す
Call SelectObject(hMemDC, hOldBmp)
' ビットマップを解放
Call DeleteObject(hBmp)
' メモリDCを解放
Call DeleteDC(hMemDC)

End Sub

さて、ごちゃごちゃと書いてありますね。
まず、フォームの幅や高さを設定している所までは全部同じです。
その次にメモリDCを作成して、そのハンドルをグローバル変数へ代入しています。
ここで勘違いしないでもらいたいのは、デバイスコンテキストを作成しただけなのでビットマップの内容は同じではありません
作成時のメモリDCのビットマップは1×1のモノクロビットマップの情報しかもっていません。
そこで今度はビットマップを作成します。
次に、SelectObject関数にて作成したビットマップをメモリDCに選択します。
この時点でやっとメモリDCに絵が描ける状態になりました。
作成したビットマップはメインフォームのビットマップと互換がとれておりますので、カラー情報なども全てメインフォームと同じです。

んでSetDIBitsToDevice関数で、前回はメインフォームに直接描画していましたが、今回はメモリDCへ描画します。
この時点で転送したDIBはDDBに変換されます。
もちろんメモリDCに描画しただけでは、まだ画面には表示されません。
どうやって表示させるかというと、表の上段に書いてあるコードです。
このコードは今回唯一のフォームイベントです。
Paintイベント内で、BitBlt関数を呼び出しメモリDCの内容をフォームに描画します。
Paintイベントは再描画命令がきた場合にのみ発生するイベントで、Showメソッドを実行しウィンドウが表示された直後に呼び出されます。

Showメソッドの所でvbModalを指定していますが、これはMainプロージャが終わらないようにする為です。
メインフォームが閉じられたら、後始末をします。
まずSelectObjectで、前のオブジェクトに戻します。
その後作成したビットマップを削除し、最後にメモリDCを削除してプログラムを終了します。
(人によっては「どうせ破棄するんだからSelectObjectする必要はない」と言う人もいますが、基本ですのでやるようにしてください)


完成

実行させてみて、ウィンドウを隠したりなんなりさせてみてください。
AutoRedrawをTrueにした時と同じ動作をするはずです。
試しにPaintイベントを何もしないとウィンドウには何も表示されません。

ちなみにウィンドウをモーダル表示しているのでタスクバーには表示されなくなっています。
モードレス表示にし、後始末をUnloadイベント内で処理すればこの問題は解決されます。
今回はあまりあっちゃこちゃにコードを散らしたくなかったので、こんなコードとなりました。


終わり

とりあえず今回はメモリDCの使い方と、ついでに再描画のやり方でした。
メモリDCとはどういうものか?
DDBとはどういうものだ?
そんな事がなんとなくわかってもらえれば嬉しいです。

さて、では今回やったサンプルコードです。
実はちょっとした「おまけコード」を付けました。
たいしたものではないですが、何かの参考になればと思います。

vb09.lzh(8.78KB)