Visual Basic 6.0 中級講座
VB6対応

 

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

5.デバイスコンテキスト

 

今回は他のウィンドウにグラフィックを描画する方法を説明します。ちょっとおもしろい使い道もあると思いますので活用してください。

この回の要約

・デバイスコンテキストを使えば、他のウィンドウにグラフィックを描画したりできる。

・デバイスコンテキストのハンドルを取得するにはAPIのGetDC関数等を使う。

・デバイスコンテキストを使えばAPIを使ってグラフィックを描画できる。

 

1.デバイスコンテキスト

 

通常ウィンドウに何か描画しようと思ったら描画メソッドを呼び出しますね。代表的な描画メソッドには直線を描くLineメソッドや円を描くCircleメソッドがあります。しかし、これはVBだから使えるメソッドであって、VB以外のウィンドウには使えません。(VBで作られたウィンドウであっても、現在プログラム中のウィンドウでなければやはり使えません)。

今回は通常VBから制御できないウィンドウに直線などを描いてみようというわけですからLineメソッドやCircleメソッドは使えないわけです。

それでは、何をつかうかというとここでもでてくるのがAPI関数です。まず始めにLineTo関数を使ってみましょう。LineToはVBのLineと一緒で直線(数学的には線分)を描くための関数です。ただし、スピードの点以外ではVBのLineメソッドよりも劣ります。それで使いにくい感じもするでしょう。

説明はこのくらいにしてサンプルを掲載しますのでまずは打ち込んでみてください。

'■API関数の宣言■

Private Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long

Private Declare Function GetDC 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 GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long

'■変数の宣言■

Dim hTargetDC As Long    'デバイスコンテキストへのハンドル

Dim hTargetWin As Long    '対象のウィンドウのハンドル

Private Sub Command1_Click()

Dim Title As String * 20

hTargetWin = WindowFromPoint(0, 0)

hTargetDC = GetDC(hTargetWin)

If hTargetDC = 0 Then

Label1.Caption = "失敗しました。"

Else

Call GetWindowText(hTargetWin, Title, 20)

Label1.Caption = Title

Call LineTo(hTargetDC, 400, 400)

Call ReleaseDC(hTargetWin, hTargetDC)

End If

End Sub

このサンプルを完成させるにはフォームにコマンドボタンを1つとラベルを1つ貼り付けてください。

実行して、コマンドボタンを押すと、座標0,0にあるウィンドウを取得してそのウィンドウに斜めの線を描きます。ただし、ウィンドウによっては描かれないものもある(この理由は後で説明します)のでいろいろなウィンドウを左上に持ってきて試してみてください。デスクトップ画面はわかりやすいのですべてのウィンドウを最小化して試してみるとよいでしょう。また、自分自身を左上に持ってきて実行することもできます。

では、具体的な説明に入りましょう。

このプログラムでは5つのAPI関数を使うので先頭でそれらを宣言しています。この4つの関数は先ほど簡単に説明したLineTo関数と、前回出てきたWindowFromPoint関数、GetWindowText関数、今回初登場のGetDC関数、ReleaseDC関数です。それぞれの説明は使う場面で個別にします。

次の変数の宣言はおいておいてコマンドボタンをクリックしたときの処理に移りましょう。

ボタンがクリックされるとすぐに左上の(座標0,0にある)ウィンドウを取得します。それがhTargetWin = WindowFromPoint(0, 0)です。これで変数hTargetWinに対象のウィンドウのハンドルが格納されることになります。(ハンドルについては前回の解説を参照してください)。WindowFromPoint関数でウィンドウハンドルを取得する手法は前回とまったく同じですね。

次に取得したウィンドウのデバイスコンテキストを取得します。デバイスコンテキストとはデバイスコンテクストともいい、 ウィンドウのグラフィック部分を担当するものどと思っておいてください。各ウィンドウに1つずつデバイスコンテキストがあって、ウィンドウに描画を命令するには必ずこのデバイスコンテキスト を使用しなければなりません。

この仕組みによりプログラマーはデバイスコンテキストの制御さえ覚えればどのようなウィンドウにも描画することができるようになるわけです。今回のテーマもこの「デバイスコンテキスト」です。

デバイスは各ウィンドウに1つずつあるので、ウィンドウと同じで番号で管理されています。1番のデバイスコンテキストには直線を引くように命令し、2番のデバイスコンテキストには円を書くよう 命令する・・・と言った具合になるわけです。この番号のことをやはり「ハンドル」と呼ばれます。それで、「デバイスコンテキストを取得する」ということは実は「デバイスコンテキストへのハンドルを取得する」という意味になります。

デバイスコンテキストへのハンドルへのハンドルを取得するにはサンプルの hTargetDC = GetDC(hTargetWin) のように、GetDC関数を使います。この関数はウィンドウハンドルを引数にとり、そのウィンドウの(関連付けられた)デバイスコンテキストのハンドルを返します。

以上でデバイスコンテキストの取得も完了してグラフィック描画の準備が整ったといいたいところですがまだちょっと気をつけることがあります。それは関数が失敗していないかということです。API関数が何かの原因で失敗するとVBごと強制終了してしま うときもあったりして危険です。そこで、API関数が失敗していないかチェックする必要があります。それが次の If hTargetDC = 0 Then の部分です。GetDC関数は失敗すると0を返すのでそれで識別するわけですね。(しかし、この処理は 片手落ちです。この前のWindowFromPointで失敗しているかもしれませんからね。この点は別の機会に私の意見を披瀝するかもしれません)。

関数に失敗したときの処理は簡単なのでとばして成功したときの処理を説明しましょう。この場合、まず、Call GetWindowText(hTargetWin, Title, 20) が実行され取得したウィンドウのタイトルバーの文字がラベルに表示されます。この処理はなくても線を描画することはもちろんできます。まぁおまけの 処理だと思ってください。GetWindowTextの使い方は中級編第2回で解説したGetUserNameに似ています。最初の引数はウィンドウを指定するためのもので対象となるウィンドウのハンドルを入れます。ウィンドウのハンドルはすでに取得してあるので改めて取得する必要はありません。2番目の引数は文字列を格納するためのバッファへのポインタです(バッファへのポインタについてもやはり第2回を参照してください)。3番目の引数は文字数です。今回は20文字までにして置きました。これだと足りないときもあるのですがまぁ今回はこれが目的ではないので20文字まで取得できれば言いかと思いこうしました。

次の2行がいよいよメインとなる処理です。

Call LineTo(hTargetDC, 400, 400) はデバイスコンテキストへ「直線を描画してください」という依頼をしています。(依頼は 基本的には却下されないので実際にはこれはまさしく 直線を描画しろ という命令に他なりません)。3つの引数もそれほど難しくなく、1つ目が対象となるデバイスコンテキストへのハンドル、2つ目がX座標、3つ目がY座標です。これで座標(400,400)まで直線が引かれるわけです。

初めての人が少し妙だと思うところは「どこから」という指定がないところです。普通直線を引くように指定するのだったら「どこからどこまで」のように2つの座標を指定するように思えますがLineToは「どこまで」の部分しかしてできません。どこからを指定するには別のAPI関数(MoveToEx)を使用する必要があります。特に指定しなかった場合は最後に「描画した点から」指定した座標まで描画されるわけです。

たとえば、この後でLineTo(hTargetDC, 500, 300)と命令した場合は座標(400,400)から、座標(500,300)まで直線が引かれます。最初に呼び出すときは座標0,0から描画されます。

最後の Call ReleaseDC(hTargetWin, hTargetDC) はデバイスコンテキストの開放です。 使い終わったデバイコンテキストは必ず開放しましょう。そうしないとどうなるかマイクロソフトは保障していません。多分メモリーリークが発生するのでしょうが・・・?

 

3.クライアント領域

 

ところで今の説明どおりにいろいろ試した方は気づかれたと思いますし、本文中にも書きましたが、ウィンドウによってちゃんと直線が描画されるものとそうでないものがあります。

この原因はGetDC関数にあります。この関数は指定したウィンドウのクライアント領域のデバイスコンテキストを返すものなので非クライアント領域が直線の位置にあると直線がちゃんと描画されたように見えないのです。(語弊がありますが、非クライアント領域にはまったく描画されません)。

クライアント領域とはウィンドウの中でユーザーが作業する領域のことで、「ワードパッド」で言うと下の画像で図示した部分にあたります。

クライアント領域はまた、VBで自由にプログラムできる部分でもあり、それ以外の部分(非クライアント領域)についてはVBプログラマーでもちょっと自由にできません(がんばればできます)。実際にタイトルバーを変形させたり、コントロールボックスの位置を変えたり、丸いフォームを造ったりできる人は相当な腕の持ち主といってよいでしょう。

しかし、その非クライアント領域であってもデバイスコンテキストのハンドルを取得することは簡単にできます。やりかたはクライアント領域のデバイスコンテキストを取得したときとまったく同じでただ、GetDC関数ではなくGetWindowDC関数を使うだけです。使い方はGetDCとまったく同じですからさきほどのGetDCを変えて試してみてください。今度はクライアント領域にも描画されるでしょう。

なお、GetWindowDCの宣言は次のようになります。

Public Declare Function GetWindowDC Lib "user32" Alias "GetWindowDC" (ByVal hwnd As Long) As Long

 

4.プログラムの構造

 

今紹介したプログラムはデバイスコンテキストを使って描画する代表的な手順を踏んでいます。この手順を整理してみると次のようになっていることがわかります。

@対象となるウィンドウのハンドルを取得する。

Aウィンドウハンドルを使ってデバイスコンテキストへのハンドルを取得する。

Bデバイスコンテキストへのハンドルを使って描画命令を出す。

手順はこの3段階から成り立っていますがどの段階もAPIの知識がないとてこずってしまいます。@のウィンドウハンドルを取得する方法は前回説明しました。いろいろな方法があります。今回の例ではWindowFromPoint関数を使いましたね。

Aのデバイスコンテキストを取得する方法はほぼ1つしかないといってもいい状況ですからなれればかんたんです。これにはGetDC関数を使います。ただし、この後でその他の方法についても若干触れますので注意してください。

Bの描画命令を実行するにはやはりAPI関数を使用しなければなりません。先ほどの例ではLineTo関数を使って直線を描きました。他にどんな関数があるかかんたんにまとめてみますと次のようになります。

また、下の表には同じ機能を持つVBの関数も記載されています。ただし、該当するVBの関数がないからといってVB標準の機能だけで実現不可能というわけではありません。

API関数 VB関数 説明
Arc Circle 弧を描きます。楕円の弧でも描けます。
Chord なし 弓形を描画します。弓形とは楕円弧とその両端を結ぶ線分を組み合わせた図形です。
Ellipse Circle 楕円形を描画します。
FillRect Line 長方形を描画します。内部は塗りつぶされます。
Pie Circle 扇形を描画します。
PolyBezier なし ベジェ曲線を描きます。ベジェ曲線については次回取り上げます。
Polygon なし 多角形を描画します。
PolyLine なし 連続した線分(折れ線)を描画します。
PolyPolygon なし 複数の多角形を一度に描画します。
PolyPolyLine なし 連続した線分(折れ線)を一度に複数個描画します。
RectAngle Line 長方形を描画します。内部は塗りつぶされません。
RoundRect なし 角の丸い長方形を描画します。

これだけの情報があれば、後は各API関数の引数や宣言が分かればいろいろと描画できるわけですね。(引数の詳細についてはMSDNを参照してください。)

 

5.いろいろな描画

 

では、上の表に掲げたEllipse関数をプログラムに実装してみましょう。それほど難しくありませんよ。

上の表を見れば分かるようにわざわざEllipse関数を使わなくてもVBのCircleを使えば楕円形を描画できます。そこでこれから書く説明はあくまで「描画系のAPIを使用する例」です。本当に楕円形だけ描ければいい場合はCircleで軽く済ませてしまった方が良いです。それなら、VB標準の機能にはない描画系のAPI関数の例で説明するという手もあると思われるかもしれませんが、他の関数は多少複雑なので説明には適さないとしてここでは割愛したしだいです。

さて、プログラムでは専用のクラス Artist を作ります。別にクラスにしなくてもEllipse関数は使えます。これはあくまで一例です。

クラスの作り方を知らない方はプログラムする前に以下の手順を踏んでください。

@新しいプロジェクトを開きます。

Aメニューの[プロジェクト]から、「クラスモジュールの追加」をクリックします。

B出てきた一覧の中から「クラスモジュール」を選択して「開く」を押します。

以上で新しい空のクラスが作成されました。作成されたクラスはプロジェクトエクスプローラに表示されているはずですので確認してください。

次に作成したクラスに Artist という名前をつけます。

Cプロジェクトエクスプローラで作成したクラスを選択してください。

Dプロパティウィンドウの(オブジェクト名)の欄に Artist と入力してください。

これでArtistクラスの準備はよいのですがこのArtistクラスをフォームから呼び出せるようにするにはさらに次の作業が必要です。

Eフォームの宣言部に Dim Artist As Artist と記述する。

FForm_Loadプロシージャに Set Artist = New Artist と記述する。

これで下準備完了です。

具体的なコーディングに入る前にEllipse関数にもう少し詳しい説明をします。

Ellipse関数は5個もの引数をとります。それぞれの引数の役割は次のとおりです。

一つ目の引数はデバイスコンテキストへのハンドルです。あとの4つの引数は順に図のX1,Y1,X2,Y2を表しています。この図からわかるようにEllipse関数は楕円形がぴったりはまるような長方形を指定することによって楕円形をも指定するようになっています。だから、数学が苦手な人でも難しい計算もいらずにただ長方形さえ指定できれば楕円がかけるわけです。

それでは早速Artistクラスに、Ellipse関数(コード)を実装してみましょう。

まず、Ellipse関数の宣言を記述してください。それは以下のようになります。

Private Declare Function Ellipse Lib "gdi32" Alias "Ellipse" (ByVal hdc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long

宣言ができたらこの関数を呼び出すための専用の関数 DrawEllipse を作ります。これは危険なAPI関数の呼び出しはずべてクラスで行うことによって、API関数の一元管理を実現するためです。あと、クラスに記述してしまえば他のプログラムでEllipse関数を使いたくなったときにはそのクラスを挿入するだけで言いというクラス一般のメリットも重要です。さて、DrawEllipseはさしずめ次のようになります。

Public Function DrawEllipse (hDC As Long, Left As Long, Top As Long, Right As Long, Bottom As Long) As Long

Dim Ret As Long        'Ellipse関数の戻り値を格納します。

Ret = Ellipse(hDC, Left, Top, Right, Bottom)

DrawEllipse = Ret

End Function

引数の名前はX1、Y1のような名前ではなくLeftやTopのようにVBでもおなじみの分かりやすい名前に変えました。自分で関数を作っているのですからできるだけ分かりやすくするように努めるべきでしょう。関数の中身はただEllipse関数を呼び出しているだけなのでどうということもありません。注意事項としてはこの関数を外部からも呼び出せるようにPrivate Sub ではなくPublic Sub で宣言することくらいです。

次に、この関数をフォームから呼び出します。今回はデバイスコンテキストへのハンドルはフォームのhDCプロパティを使って取得することにしますが、上のほうでやったようにWindowFromPoint関数などを使えば自分以外のWindowのデバイスコンテキストも取得できるので変なところに楕円を書くことができます。

注意:API関数は座標単位にピクセルを使うのでプログラムを実行する前にフォームのScaleModeプロパティを 3 − ピクセル にしておいてください。

関数を呼び出すのはとても簡単で次のようにするだけです。

Artist.DrawEllipse Me.hDC, 10, 10, 200, 100

この調子でArtisrクラスに他のAPI関数も装備していってみてください。

このプロジェクトの全コードを下に示しておきます。

●Artistクラスの全コード

Private Declare Function Ellipse Lib "gdi32" (ByVal hDC As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long

Public Function DrawEllipse(hDC As Long, Left As Long, Top As Long, Right As Long, Bottom As Long) As Long

    Dim Ret As Long 'Ellipse関数の戻り値を格納します。

    Ret = Ellipse(hDC, Left, Top, Right, Bottom)

    DrawEllipse = Ret

End Function

●フォームの全コード

フォームにはコマンドボタンを1つ配置してください。

Dim Artist As Artist

Private Sub Command1_Click()

    Artist.DrawEllipse Me.hDC, 10, 10, 200, 100

End Sub

Private Sub Form_Load()

    Set Artist = New Artist

End Sub


6.最後に

 

本当はもっといろいろな機能をクラスに実装しようと思ったのですが、1つ1つのAPI関数の使い方を説明している余裕がなくなってEllipseだけになってしまいました。ですから、本当に皆さんの力で是非Artistクラスを完成させてください。

次次回はPolyBezier関数を大きく取り上げる予定です。できればPolyBezierを使ったスクリーンセーバまで説明しようと思っているのですが(どうなるかわかりません)。

それでは失礼します。