C# と VB.NET の質問掲示板

わんくま同盟

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト


(過去ログ 146 を表示中)
■85444 / )  Re[8]: VB.NETでSusieプラグインを使いたい
□投稿者/ 魔界の仮面弁士 (1434回)-(2017/10/20(Fri) 23:23:11)
2017/10/21(Sat) 03:40:17 編集(投稿者)

No85435 (K-1 さん) に返信
>>もし、PictureBox に表示することが目的なのであれば、
>  とりあえずはPictureBoxに表示し、最終的には
> Picture1.Image.Save(save_file_name)
>  でBMP画像を生成したいのです。

Picture1.Image (VB6) は、ReadOnly で、常に何かしらの画像を返しますが、
PictureBox1.Image (VB.NET) は Writable であり、その初期値は Nothing です。

元が Nothing であるという事は、まずは何かしらの画像をセットしなければ、
> Picture1.Image.Save(save_file_name)
を呼ぶことすらできない、ということです。


そして、それを行うための手段こそが、先の No85434 で回答した
> New Bitmap(w, h) で生成したビットマップを
> Graphics.FromImage して描画し、最後に
> PictureBox1 の BackgroundImage か Image に
> セットする形でも良いかも知れません。
という手順であるわけです。


具体的にはこんな感じです。

Dim bmp As New Bitmap(PictureBox1.Width, PictureBox1.Height)
Using g = Graphics.FromImage(bmp)
  '
  'g に対する描画処理をここに記述
  '
  Dim oldImage = PictureBox1.Image
  PictureBox1.Image = bmp
  If oldImage IsNot Nothing Then
    oldImage.Dispose()
  End If
End Using

またはこんな感じ。

Dim bmp As New Bitmap(PictureBox1.Width, PictureBox1.Height)
Dim hBitmap = bmp.GetHbitmap()
'
'hBitmap に対する描画処理をここに記述
'
DeleteObject(hBitmap);
Dim oldImage = PictureBox1.Image
PictureBox1.Image = bmp
If oldImage IsNot Nothing Then
  oldImage.Dispose()
End If



上記で oldImage.Dispose() しているのは、前に表示されていた古い画像を
処分するためのコードです。もし、一つの Bitmap を複数の場所で
共有利用している場合は、まだ使用中のものを Dispose しないように注意しましょう。
どこからも使用されなくなった時点で、Dispose を呼ぶようにしてください。



> ret = SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, Picture1.Height, LocalMemoryBMP, LocalMemoryInf, 0) 'ビットマップ表示

SetDIBits の第二引数には、HBITMAP を渡す必要があります。
第六引数(上記では LocalMemoryInf)にビットマップのデータを渡すと、
それが第二引数に指定したビットマップのピクセルへと描かれるわけです。


ここでいう HBITMAP とは、ビットマップオブジェクトのハンドルです。
VB6 で言えば、OLE_HANDLE または Long 型相当の値が要求されますし、
VBA7 で言えば、OLE_HANDLE または LongPtr 型相当の値です。
VB.NET で言えば、IntPtr 型の値が必要であることになりますね。

そして VB.NET においては、
> Dim bmp As New Bitmap(PictureBox1.Width, PictureBox1.Height)
で生成した Bitmap オブジェクトの GetHbitmap メソッドを使うことで、
簡単に HBITMAP を取得することができます。


ということで今回の場合、第二引数に渡している『CInt(CObj(Picture1.Image))』の部分が明らかにおかしいです。
ここに渡されている値がどのような内容になっているか、実際に確認してみましたか?

先に述べた通り、Image プロパティの初期値は Nothing のはずです。
PictureBox の Image プロパティに何の画像もセットしていないのであれば、
『CInt(CObj(Picture1.Image))』は『CInt(Nothoing)』と同義であり、
それは要するに、ハンドルとして「0」を指定していたということになります。

もし、Image が Nothing ではなく、何らかの画像が指定されていた場合はどうでしょうか?
その場合、画像を Integer 型に変換することはできませんので、CInt での変換が失敗し、
InvalidCastException の例外となるはずです。
もし現時点で例外になっていないのであれば、Image は恐らく Nothing のままだったのでしょうね。

そして変更すべきビットマップを渡していないのなら、画像が描画されるはずも無いわけで。


ちなみに VB.NET における PictureBox1.Image が返すデータの型は、Image 型(あるいはその継承クラス)ですが、
VB6 においては、Picture1.Image が返す型は StdPicture 型となっています。

StdPicture オブジェクトは、“既定のプロパティ”として Handle プロパティを持っていますので、
VB6 で CLng(Picture1.Image) とした場合は、Picture1.Image.Handle と同義となります。

そして VB6 における Picture1.Image.Handle の値は、HBITMAP (など)に相当します。
ちなみに Handle プロパティのデータ型は OLE_HANDLE 型です。(VB6 では Long と同義)
https://msdn.microsoft.com/en-us/library/aa267505.aspx



>>> Picture1.Refresh() 'ピクチャーボックス更新
>> Refresh しているのは何故ですか?
>  特に意識はありません。元になったVB6のコードで行っていたためです。

では質問を変えましょう。VB.NET の方の Refresh ではなく、移植元である
VB6 当時のコードにおいて、Refresh が記載されている理由を把握できていますか?


VB6 の PictureBox には、AutoRedraw プロパティというものがあります。これは
VB6 のヘルプに「継続表示属性を持つグラフィックス」にするためのものと記されています。

そしてこの「AutoRedraw プロパティ」の初期値は False であり、
これが False の状態で VB6 で描画処理を行っていく行為は、
VB.NET で CreateGraphics を用いて描画した場合に相当します。

試しに、AutoRedraw が True/False それぞれの状況でテストしてみます。

'--- VB6 ---
Option Explicit
Private Sub Form_Load()
 '継続表示属性の指定
 Picture1.AutoRedraw = True
 Picture2.AutoRedraw = False

 '背景色と背景画像
 Picture1.BackColor = vbYellow
 Set Picture1.Picture = Me.Icon
 Picture2.BackColor = vbYellow
 Set Picture2.Picture = Me.Icon
End Sub
Private Sub Command1_Click()
 Picture1.Line (1000, 10000)-(0, 0), vbRed
 Picture1.Print Timer
 Picture2.Line (1000, 10000)-(0, 0), vbRed
 Picture2.Print Timer
End Sub
Private Sub Command2_Click()
 Picture1.Refresh
 Picture2.Refresh
End Sub

上記を実行すると分かるのですが、Command1 を押すと、
Picture1/Picture2 双方に描画が行われます。
その後 Command2 を押すと、Picture2 側のみ描画内容がクリアされます。

Refresh は「強制再描画」を行うためのメソッドであり、
迂闊に呼び出すと、折角の描画内容が消えてしまいます。


ちなみに VB.NET の場合、再描画を行うために
>>> PictureBox2.Refresh()
>>> PictureBox3.Invalidate()
>>> PictureBox4.Update()
という 3 種類のメソッドがあります。詳しい説明はこちら。
https://dobon.net/vb/dotnet/control/refreshupdateinvalidate.html

CreateGraphics を使うなら、せめて Refresh ではなく、Update を使うのが良いでしょう。
ただし今回の要件では、そもそも CreateGraphics を使うべきでは無いと思います。



改めて、VB6 の時のコードに何故わざわざ Refresh があったかというと、
それが「API によって直接描画されたもの」であったからです。

VB のステートメントによって処理されたものではないために、
VB 側は描画されたことに気づくことができません。
そのため、API による描画処理を行うと、画面上へ直ちに反映されないという事象が生じます。

そこで、Refresh を呼び出して『強制再描画』を指示する必要があるわけです。

ところが Refresh を呼ぶと、先に示したように描画内容は消えてしまいます。
そこで VB6 では、AutoRedraw = True が使われています。上記の Picture1 がそれです。
これにより、そのイメージは「継続表示属性」を持つものとなります。このモードでは、
描画内容は常にメモリー上に蓄えられていますので、ここから自動的に描画内容が復元されます。

※ちなみに AutoRedraw = False の場合は、自動的に再描画はされないので、
 Paint イベントにて、再描画のための処理を用意する必要があります。


さて、VB.NET の場合はというと…こちらには AutoRedraw というプロパティは
ありませんが、それに相当するのが、今回の回答の冒頭に述べた
> PictureBox1.Image = bmp
ということになります。
この bmp が、VB6 でいうところの「継続表示属性を持つグラフィックス」にあたります。


VB.NET では、BackgroundImage が背景画像、Iamge が前景画像を意味します。
これらに画像をセットしておけば、Refresh などによる強制再描画を行っても
その内容が失われることはありません。


ここで注意すべきは、描画は「Image にセットされる Bitmap」に対して行うのであり、
「PictureBox 自体への CreateGraphics」で行うのでは無いということです。
CreateGraphics の方は、継続表示されるものでは無いため、容易に消えてしまいます。


今回提示したサンプルも、PictureBox1.CreateGraphics は使わず、
> Dim bmp As New Bitmap(PictureBox1.Width, PictureBox1.Height)
> Using g = Graphics.FromImage(bmp)
というコードになっていますよね。




>>> Private Sub ddsView(ByRef temp_dds_name As String)
>> 何故 ByRef に?
>  特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。

恐らくは、元のコードに ByRef/ByVal のいずれも書かれていなかったのでしょうね。
省略された場合、VB6 では ByRef 相当の意味となりますから。(VB.NET では、省略時は ByVal です)

ByRef にする必要があるのは、その引数が結果を返すための出力引数である場合だけです。
今回は出力引数にする理由が無いので、VB6 であっても、ByRef にしなければならない積極的な理由は無いはずです。

細かいことを言えば、引数がユーザー定義型または配列であった場合、VB6 においては ByVal を選択できないという
制限があるのですが、VB.NET においてはそれは当てはまりませんし、そもそも今回は配列等ではなく
String 型なのですから、ByVal にしておくのが適切であるということです。


>>> Dim LocalMemoryBMP As Long
>> 何故 Long 型に?
>  特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。

それは本当ですか?
もし本当なら、VB6 当時の宣言を教えて頂けるとありがたいです。

VB.NET にとっての Long は、8バイト(64bit)長の符号付き整数型(Int64 構造体)ですよね。

一方、VB6 で用意されている整数型は
 Byte: 1バイト長の符号無し整数型
 Integer: 2バイト長の符号付き整数型
 Long: 4バイト長の符号付き整数型
ぐらいのはずで、8 バイトな Int64 にコンバートされる理由が分からないのです。

VB6 で 8 バイトなのは、Currency と Date 型ぐらいなので、
どのような型から As Long になったのか興味があります。

これが VBA7 であれば、
 LongLong:8 バイト長の符号付き整数型
 LongPtr:x86環境では 4 バイト、x64環境では 8 バイトの整数型
がありましたが、今回は VB6 ですし…。
返信 編集キー/


管理者用

- Child Tree -