Visual Basic 6.0 中級講座 |
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 hNewPen = CreatePenIndirect(NewPen) |
このコードではLOGPEN構造体を使っているのであらかじめその宣言をしておいてください。その宣言は次のようになります。(あとで完成版を全部合わせて掲載しているので忙しい方はそちらのほうを参照してもよいでしょう)。
Private Type POINTAPI x As Long y As Long End Type
Private Type LOGPEN |
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 | 0 | (省略) |
破線 | PS_DASH | 1 | ------------ |
点線 | PS_DOT | 2 | ・・・・・・・・・・・・ |
一点破線 | PS_DASHDOT | 3 | -・-・-・-・-・-・ |
二点破線 | PS_DASHDOTDOT | 4 | -・・-・・-・・-・・ |
(空) | PS_NULL | 5 |
スタイルを設定するには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 | 5 | DIBで定義されるパターンブラシ |
DIBパターン | BS_DIBPATTERNPT | 6 | (同上) |
ハッチ | BS_HATCHED | 2 | ハッチを直線で構成する |
中空 | BS_HOLLOW | 1 | 空のブラシ |
中空 | BS_NULL | 1 | (同上) |
パターン | BS_PATTERN | 3 | メモリビットマップで定義されるパターンブラシ |
純色 | BS_SOLID | 0 | 純色のブラシ |
これらのスタイルは私もあまり使ったことがないので詳しい使い方はよく分からないものが多いです。私がよく使う(といってもブラシ自体そんなに使わないのですが)のはハッチブラシと純色のブラシです。
純色ブラシは文字通り1色で塗りつぶします。ハッチブラシ(BS_HATCHED=2)を指定したときはブラシを作るときにハッチの項目を設定できます。設定できる項目は次の表のようになります。
定数 | 値 | 説明 | |
斜め | HS_BDIAGONAL | 3 | 左下から右上への 45 度の直線 |
クロス | HS_CROSS | 4 | 水平、垂直の格子状の直線 |
斜めクロス | HS_DIAGCROSS | 5 | 45 度の格子状の直線 |
斜め | HS_FDIAGONAL | 2 | 左上から右下への 45 度の直線 |
水平 | HS_HORIZONTAL | 0 | 水平の直線 |
垂直 | HS_VERTICAL | 1 | 垂直の直線 |
たとえば、赤い斜めクロスのブラシの作り方は次のようになります。
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関数で作るペンよりもこったペンが作れるようです。