Visual Basic 6.0 中級講座
VB6対応

 

Visual Basic 中学校 > VB6 中級講座 >

6.ペンとブラシ

 

前回の最後で「次回はベジェ曲線を使ったスクリーンセーバー・・・どうのこうの」みたいなことを書きましたが、まだベジェ曲線は少し早いと思ったので予定を変更して、今回はペンとブラシを扱います。期待していた方には申し訳ありません。ベジェ曲線を使ったスクリーンセーバーについては次回に延期いたします。(なお、前回の当該記述はすでに訂正してあります)。

この回の要約

・APIでは描画する色を表現するために「ペン」・「ブラシ」という考え方を使う。

・ペンは線の色・太さ・形を表現する。

・ペンはCreatePenIndirect関数等で作る。

・ブラシは塗りつぶしの色・パターンを表現する。

ブラシはCreateBrushIndirect関数等で作る。


この回の使える?サンプル

・画面いっぱいに巨大な × を描画する。→サンプルA

 

 

1.導入

 

デバイスコンテキストを使って直線などを描く方法については前回説明しました。前回の知識を応用すれば たとえば、デスクトップ上に×を描くこともできます。そのコードは次のようになります。

Private Declare Function GetDesktopWindow Lib "user32" () As Long
Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function LineTo Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function MoveToEx Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, lpPoint As Any) As Long

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

Private Sub Command1_Click()

    Dim hDesktopWnd As Long
    Dim hDesktopDC As Long
    Dim rDesktop As RECT
   
    hDesktopWnd = GetDesktopWindow() 'デスクトップのハンドルを取得
    hDesktopDC = GetWindowDC(hDesktopWnd) 'デスクトップのデバイスコンテキストを取得
    Call GetWindowRect(hDesktopWnd, rDesktop) 'デスクトップの大きさを取得
   
    Call LineTo(hDesktopDC, rDesktop.Right, rDesktop.Bottom) '左上から右下に線を引く
    Call MoveToEx(hDesktopDC, rDesktop.Right, 0, 0) 'カレントポジションを右上に変更
    Call LineTo(hDesktopDC, 0, rDesktop.Bottom) '右上から左下に線を引く
   
    Call ReleaseDC(hDesktopWnd, hDesktopDC) 'デバイスコンテキストを開放する。

End Sub

■サンプルA

フォームにコマンドボタンを貼り付けてこのコードをコピーしてください。コマンドボタンを押すと画面いっぱいに×が描かれます。他のアプリケーションが実行中でもそのアプリケーションの上に線を引いてしまうというのがAPIの威力です。

このプログラムには少しばかり前回触れていない部分もあるので順を追って解説しましょう。まず、宣言部のPrivate Declare Function文ですが、これらについてはあと触れます。次に、RECT構造体(ユーザー定義型)を宣言している部分があります。この構造体は長方形の座標を表現するのに使うものです。このプログラムではデスクトップの座標を表現するのに使います。別にVBで普通につかっている、Left, Top, Width, Heightなどのプロパティでもかまわないように思うかもしれませんが、API関数の中にはRECT構造体を要求するものがあるので仕方がないといったところです。

さて、具体的な描画処理に話を移します。具体的な処理はCommand1_Clickプロシージャで行っていますが、要するに線を引くのはLineTo関数ですから他の処理はLineTo関数を呼び出すための前処理のようなものです。

LineTo関数の引数は、デバイスコンテキストへのハンドル、終点のX座標、終点のY座標 の3つです。だから、LineTo関数を呼び出す前にこの3つを取得しておかなければならないのです。1番目のデバイスコンテキストへのハンドルはGetWindowDC関数で取得できますが、この関数を使うにはウィンドウのハンドルが必要ですからこの関数を呼び出すさらに前にウィンドウへのハンドルを取得するGetDeskTopWindow関数を呼び出しているわけです。これでLineTo関数の1つ目の引数「デバイスコンテキストへのハンドル」は用意できました。

2番目、3番目の引数は座標です。今回はデスクトップウィンドウに×を書きたいのでデスクトップウィンドウの大きさを知る必要があります。このためにGetWindowRect関数を使います。この関数は指定したデバイスコンテキストの左上の点の座標と右下の点の座標をRECT構造体に入れてくれます。使い方はコードを見ればすぐわかるでしょう。

なお、デスクトップのサイズを知るには実はAPI関数を呼び出さなくてもVBのScreenオブジェクトを使えばできます。たとえば、Screen.Widthでデスクトップの横幅が分かります。(ただ、単位がTWIPなので、Pixelに直すにはScreen.Width / Screen.TwipsPerPixelXのようにする必要があります)。今回この方法を使わないのは将来への布石です。だから、通常はScreenオブジェクトを使う方法をおすすめします。(ただし、Screenオブジェクトではデスクトップのサイズは取得できても、他のウィンドウのサイズや座標は取得できません。GetWindowRect関数では両方できます)。

GetWindowRect関数を使った場合、デバイスコンテキスト(この場合はデスクトップの)の横幅を知るにはrDesktop.Right - rDesktop.Left のようにします。rDesktopは自分で宣言している変数なので別の名前でもかまいません。また、今回はデスクトップを対象としているのでLeftとTopは0に決まっています。

思ったより話が長くなりましたがこれでLineTo関数の3つの引数がすべてそろいました。それで、LineTo関数の振る舞いについて見てみます。LineTo関数の振る前について見るということは他のAPI描画関数(GDI関数)について見るということにもなりますから注意してください。

LintTo関数は文字通り線分を引く関数ですが、指定する座標は終点だけで始点は指定できません。このことは次のようにたとえることができます。

紙に向かってペンを持っている人がいてその人は指示があるまでペンを動かさない。あるとき、「座標(100,20)まで線を引け」と指示されたら、その人は 今ペンのある場所 から座標(100,20)までの線分を描くだろう。

たとえで申し訳ありませんが言いたいことは分かってもらえたと思います。こういうふるまいをするのがAPI描画関数(GDI関数)の特徴です。このたとえ話で 「今ペンのある場所」のことをカレントポジションと言います。カレントポジションは多くの描画関数では始点とされます。さらに、座標(100,20)まで、ペンを動かした(線を引いた)後はこの座標(100,20)が新たなカレントポジションになります。

こういったわけで、LineTo関数は同じように使ってもそのときのカレントポジションによって描かれる線分は異なるのです。線などを描かずにカレントポジションだけ変更するにはMoveToEx関数を使います。

 

2.ペン

 

ここからが今回のテーマです。

さて、以上のような方法でデスクトップ上に線分を引きことができるようになったわけですが、いつも黒い線が描かれるというのは不満でしょう。やはり色も指定したいものです。それに線の幅も指定できるといいですね。こういったことはどのように実現できるのでしょうか。

ここで、さきほどのたとえ話の中に出てきた人が「ペン」を持っていたことに注目してください。いつも黒くて細い線が描かれるのは、この人が持っているペンが黒い細いペンだからなのです。この人に赤いペンに持ち替えろと命令すれば、それから後で呼び出すLineTo関数は何も指示しなくても勝手に赤い線を描画するわけです。

要するに「描画される線の色や太さを変えるにはペンを変える。」ということです。

この「ペン」という言葉はたとえ話の中に出てくる言葉ですが、実はそのままプログラム用語になっています。

それでは、ペンを持ち替える方法を説明しましょう。

ペンを持ち替えるにはSelectObject関数を使います。この関数は2つの引数を持ち、1つ目はデバイスコンテキストへのハンドル、2つ目は持ち替えたいペンへのハンドルを指定します。

この説明でお分かりかと思いますがSelectObject関数は単独では意味がなくて、この関数を呼び出す前にペンへのハンドルを取得する関数を呼び出さなければならないのです。

それにしても、Windowsにはどんなペンが用意されていると思いますか?実は用意されていません。ペンは自分で作るものなのです。ペンを作るにはCreatePenIndirect関数などを使います。話が混乱してくるので今度は具体的なコードを記述しながら説明しましょう。

まず、ペンを作ります。赤くて太いペンを作りましょう。この部分のコードは次のようになります。

Dim hNewPen As Long
Dim hOldPen As Long
Dim NewPen As LOGPEN 

NewPen.lopnColor = vbRed
NewPen.lopnWidth.x = 10 

hNewPen = CreatePenIndirect(NewPen)

このコードではLOGPEN構造体を使っているのであらかじめその宣言をしておいてください。その宣言は次のようになります。(あとで完成版を全部合わせて掲載しているので忙しい方はそちらのほうを参照してもよいでしょう)。

Private Type POINTAPI
    x As Long
    y As Long
End Type

Private Type LOGPEN
    lopnStyle As Long
    lopnWidth As POINTAPI
    lopnColor As Long
End Type

LOGPEN構造体はさらにPOINT構造体を参照するのでこれも一緒に宣言しておく必要があります。

それでは元のコードに戻りましょう。見れば分かるかもしれませんがLOGPEN型の変数NewPenにペンをデザインしていきます。ここでは、色と幅を設定しています。色はvbRedとしていますがRBG関数も使えます。また、幅は10に設定しています。

設定がすんだら、CreatePenIndirect関数を呼び出してペンを作ります。この関数はLOGPEN型の変数を引数にとってペンを作成してくれます。そして作成したペンへのハンドルを返してくれるという按配(あんばい)です。

次にやっと「ペンを持ち替えろ」という命令をします。これは先ほど出てきたSelectObject関数を使って次のようにするだけです。

OldPen = SelectObject(hDesktopDC, NewPen)

SelectObject関数の2つの引数は先ほど説明したようにデバイスコンテキストとペン(へのハンドル)ですから、使い方は簡単です。ところで、新しいペンは作業が終わり次第再びもとのペンに持ち替えるのが礼儀です。(そうしなしと、ペンが持ち替えられているともしらずに他のアプリケーションが描画を命令して具合の悪いことになるそうです。)その用意としてSelectObject関数は新しいペンに持ち替えるのと同時に今まで持っていたペンへのハンドルを返します。ここでこのペンを取得しておかないと後で元に戻せなくなるので忘れずに取得しておきましょう。上の例では変数OldPenにこの元のペンのハンドルを格納しています。

以上でペンの持ち替えは完了です。この後で呼び出すLineToなどの描画関数は赤い太いペンで命令を実行することになります。このペンでデスクトップ上に×を描くコードは次のようになります。

ほとんどは最初にお見せしたコードと同じで、追加されている部分は色を変えてあります。

Private Declare Function GetDesktopWindow Lib "user32" () As Long
Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function LineTo Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function MoveToEx Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, lpPoint As Any) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function CreatePenIndirect Lib "gdi32" (lpLogPen As LOGPEN) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long

Private Type POINTAPI
        x As Long
        y As Long
End Type

Private Type LOGPEN
        lopnStyle As Long
        lopnWidth As POINTAPI
        lopnColor As Long
End Type


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

Private Sub Command1_Click()

    Dim hDesktopWnd As Long
    Dim hDesktopDC As Long
    Dim rDesktop As RECT
   
    Dim hNewPen As Long
    Dim hOldPen As Long
    Dim NewPen As LOGPEN

   
    hDesktopWnd = GetDesktopWindow()            'デスクトップのハンドルを取得
    hDesktopDC = GetWindowDC(hDesktopWnd)       'デスクトップのデバイスコンテキストを取得
    Call GetWindowRect(hDesktopWnd, rDesktop)   'デスクトップの大きさを取得
   
    'ペンの作成
    NewPen.lopnColor = vbRed
    NewPen.lopnWidth.x = 10
    hNewPen = CreatePenIndirect(NewPen)
   
    'ペンを持ち替える
    hOldPen = SelectObject(hDesktopDC, hNewPen)

   
    Call LineTo(hDesktopDC, rDesktop.Right, rDesktop.Bottom)    '左上から右下に線を引く
    Call MoveToEx(hDesktopDC, rDesktop.Right, 0, 0)             'カレントポジションを右上に変更
    Call LineTo(hDesktopDC, 0, rDesktop.Bottom)                 '右上から左下に線を引く
   
    Call ReleaseDC(hDesktopWnd, hDesktopDC)     'デバイスコンテキストを開放する

    '元のペンに戻す
    hNewPen = SelectObject(hDesktopDC, hOldPen)
    Call DeleteObject(hNewPen)                  '不要になったペンを開放する


End Sub

最後の2行には少し注意してください。この部分は「元のペンに戻す」コードと不要になったペンを開放するコードです。このように不要になったペンはDeleteObject関数を使って開放するようにしてください。

ところで、ペンは色と幅のほかに「スタイル」を設定することができます。設定できるスタイルは次の表に掲げておきます。

  定数 イメージ
実線 PS_SOLID (省略)
破線 PS_DASH ------------
点線 PS_DOT ・・・・・・・・・・・・
一点破線 PS_DASHDOT -・-・-・-・-・-・
二点破線 PS_DASHDOTDOT -・・-・・-・・-・・
(空) PS_NULL  

スタイルを設定するにはLOGPEN型の変数に対して

NewPen.lopnStyle = 2

のようにします。この例ではスタイルを点線にしています。ただし、スタイルが有効なのはペンの幅が1の時だけです。また、このときペンの色はその前景色を意味しています。

次に青い点線のペンを作成するコード例を掲載しますので参考にしてください。

Dim hNewPen As Long
Dim hOldPen As Long
Dim NewPen As LOGPEN
    
'ペンの作成
NewPen.lopnColor = RGB(0, 0, 255)
NewPen.lopnWidth.x = 1
NewPen.lopnStyle = 2
hNewPen = CreatePenIndirect(NewPen)
   
'ペンを持ち替える
hOldPen = SelectObject(hDesktopDC, hNewPen)
   

3.ブラシ

 

今度は「ブラシ」です。ペンは直線や曲線などを描くときに使われますが、ブラシは色を塗るときに使われます。色を塗るにはExtFloodFill関数つかいますが、この他にもEllipse関数やFillRect関数(内部が塗りつぶされた長方形を描画する)などで使われます。

ブラシの使い方はペンとほとんど同じなのでペンの使い方が分かっていれば難しくありません。ここではFillRect関数を使ってブラシの効果を試してみます。

ペンのときとほとんど同じなのでいきなり完成版のコードを見てみましょう。

Private Declare Function GetDesktopWindow Lib "user32" () As Long
Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Private Declare Function FillRect Lib "user32" (ByVal hdc As Long, lpRect As RECT, ByVal hBrush As Long) As Long
Private Declare Function CreateBrushIndirect Lib "gdi32" (lpLogBrush As LOGBRUSH) As Long

Private Type LOGBRUSH
        lbStyle As Long
        lbColor As Long
        lbHatch As Long
End Type

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

Private Sub Command1_Click()
   
    Dim hDesktopWnd As Long
    Dim hDesktopDC As Long
    Dim rDesktop As RECT
   
    Dim rTarget As RECT
    Dim hNewBrush As Long
    Dim hOldBrush As Long
    Dim NewBrush As LOGBRUSH
   
    hDesktopWnd = GetDesktopWindow()            'デスクトップのハンドルを取得
    hDesktopDC = GetWindowDC(hDesktopWnd)       'デスクトップのデバイスコンテキストを取得
    Call GetWindowRect(hDesktopWnd, rDesktop)   'デスクトップの大きさを取得
   
    '描画する長方形の座標を設定
    rTarget.Left = rDesktop.Right \ 4
    rTarget.Top = rDesktop.Bottom \ 4
    rTarget.Right = rDesktop.Right * 3 \ 4
    rTarget.Bottom = rDesktop.Bottom * 3 \ 4
   
    'ブラシの作成
    NewBrush.lbColor = RGB(120, 180, 200)
    NewBrush.lbStyle = 2
    NewBrush.lbHatch = 5
    hNewBrush = CreateBrushIndirect(NewBrush)
   
    'ブラシを持ち替える
    hOldBrush = SelectObject(hDesktopDC, hNewBrush)
   
    Call FillRect(hDesktopDC, rTarget, vbRed)   '長方形を描画
    
    Call ReleaseDC(hDesktopWnd, hDesktopDC)     'デバイスコンテキストを開放する

    '元のブラシに戻す
    hNewBrush = SelectObject(hDesktopDC, hOldBrush)
    Call DeleteObject(hNewBrush)                '不要になったブラシを開放する

End Sub

FillRect関数が描画する長方形の座標をRECT構造体として要求するのでRECT型の変数rTargetに値をセットする部分などが追加されていますがその他の部分はペンのときとほとんど変わらないことが分かるでしょう。

ブラシをデザインするにはLOGBRUSH型の変数を使っています。デザインできる内容は色とスタイルとハッチです。これらについては少し後で解説します。

デザインしたブラシはCreateBrushIndirect関数で作成できます。この関数は作成したブラシのハンドルを返します。この点もペンのときのreatePenIndeirectにそっくりですね。

ブラシの作成が終わったらSelectObjectでブラシを選択します。このときもとのブラシのハンドルを取得しておくのもペンのときと一緒です。そして最後の元に戻してブラシを破棄する部分も一緒ですね。

どうです?ペンの使い方さえ分かっていればブラシは難しくないというのがお分かりいただけましたか?

それでは、ブラシを作るときに設定できる項目について説明しましょう。まず、色ですがこれはペンのときと同じです。

次にスタイルですが設定でいるスタイルは下の表のようになります。

定数 説明
DIBパターン BS_DIBPATTERN DIBで定義されるパターンブラシ
DIBパターン BS_DIBPATTERNPT  (同上)
ハッチ BS_HATCHED ハッチを直線で構成する
中空 BS_HOLLOW 空のブラシ
中空 BS_NULL (同上)
パターン BS_PATTERN メモリビットマップで定義されるパターンブラシ
純色 BS_SOLID 純色のブラシ

これらのスタイルは私もあまり使ったことがないので詳しい使い方はよく分からないものが多いです。私がよく使う(といってもブラシ自体そんなに使わないのですが)のはハッチブラシと純色のブラシです。

純色ブラシは文字通り1色で塗りつぶします。ハッチブラシ(BS_HATCHED=2)を指定したときはブラシを作るときにハッチの項目を設定できます。設定できる項目は次の表のようになります。

  定数 説明
斜め HS_BDIAGONAL 左下から右上への 45 度の直線
クロス HS_CROSS 水平、垂直の格子状の直線
斜めクロス HS_DIAGCROSS 45 度の格子状の直線
斜め HS_FDIAGONAL 左上から右下への 45 度の直線
水平 HS_HORIZONTAL  水平の直線
垂直 HS_VERTICAL 垂直の直線

たとえば、赤い斜めクロスのブラシの作り方は次のようになります。

NewBrush.lbColor = vbRed
NewBrush.lbStyle = 2
NewBrush.lbHatch = 5
hNewBrush = CreateBrushIndirect(NewBrush)
    

紫の純色ブラシの作り方は次のようになります。

NewBrush.lbColor = RGB(220, 0, 220)
NewBrush.lbStyle = 0
hNewBrush = CreateBrushIndirect(NewBrush)
    

4.背景色

 

さて、実際にやってみた方は分かると思いますが「色」の項目は前景色を意味しているに過ぎないのでハッチブラシを使っているときは背景色を設定できずにストレスがたまります。また、ペンでも点線や破線を使っているときに黒地に赤い点線などといった設定はできず、ただ前景色だけしか設定できないのでやはりストレスがたまります。

要するに背景の色はどのように設定するのかということです。

簡単言うと背景色を設定するにはSetBKColor関数を使わなければなりません。この関数の宣言は次のようになります。

Private Declare Function SetBkColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long) As Long

この関数は使い方が簡単なので困ることはないでしょう。第1引数はデバイスコンテキストへのハンドル、第2引数は設定したい色です。この関数を使って背景色を設定するには次のようにします。

OldColor = SetBkColor(hDesktopDC, vbRed)

この関数は例によってもとの色を返すので取得しておくとよいでしょう。(色を表す変数の型はLongにするとよいと思います)。

この関数をFillRectやLineToを呼び出す前に実行しておけば、背景を望みどおりの色にすることができます。

 

5.VBのフォーム上での描画API関数

 

以上ペンやブラシの作り方を説明してきたわけですが、1つ1つは簡単でも望みどおりの図形を描くのは大変そうだと思ったでしょう。色を変えるたびにペンを作成して持ち替えて、前のペンを取得して・・・・。

これらはまぁしょうがないと思って我慢していただくほかはないのですが、VBのフォーム上でこれらAPIを使うとなると話は違います。VBのフォームはAPIを呼び出さなくてもペンやブラシが作れるのでもっと楽です。それにはフォームのDrawWidth,DrawStyle,FillColor,FillStyleプロパティを使うのです。これらプロパティはこの順で、ペンの幅、ペンのスタイル、ペンまたはブラシの色、ブラシのハッチを表しているのでAPIを呼び出すことなく簡単に項目を設定することができます。

 

6.最後に

 

これでAPI関数を使っていろいろと描画できるようになったと思います。いよいよ次回はこれらの知識を動員してベジェ曲線を使ったスクリーンセーバーを作ろうと思います。それでは。

いい忘れましたが、ExtCreatePen関数を使えば、CreatePenIndirect関数で作るペンよりもこったペンが作れるようです。