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

わんくま同盟

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

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


■103794 / )  Re[5]: Visual Basicで簡易CADを作成
□投稿者/ 魔界の仮面弁士 (3873回)-(2025/07/30(Wed) 15:46:27)
No103792 (shiro さん) に返信
> 回答有難うございます。VBでの壁画は始めたばかりで分からない点が多く参考になります。
壁画というか、描画かな?

掲示板にコードを投稿する時は、
 投稿モード:通常モード / 図表モード
の選択肢を、「通常モード」から「図表モード」に変更しておいてください。
そうしないと、空白やタブでのインデントが潰れてしまいますので。

もし、「通常モード」のままでインデントを維持したいのであれば、
連続する半角スペース を 全角スペース に置き換えておくことでも代用できます。
全角空白を混入させられない言語の時は都合が悪いですけれどね。


インデントが崩れていないかを確認したい場合は、右下の送信ボタンの近くにある
「プレビュー」にチェックをいれて、投稿前に事前確認することもできます。


> 画像削除方法を勉強してみます。
たとえば、先のサンプルの最後にある
「マウスをなぞった位置にリアルタイムに四角形を描画し、マウスを離すとその位置に四角形を追加し、後で追加された四角形をクリックすると色が変わる」
について。
https://www.umayadia.com/vbsample/VBdotNet-Samples201/Sample275WinFormMouseToDraw.htm#A5

ここに下記のコードを加えると、選択状態の黄色い矩形が取り除かれます。

Private Sub PictureBox1_DoubleClick(sender As Object, e As EventArgs) Handles PictureBox1.DoubleClick
  Dim selectedPolygon As Polygon = polygons.FirstOrDefault(Function(p) p.IsSelected)
  If selectedPolygon IsNot Nothing Then
    '選択済みの矩形があれば削除
    polygons.Remove(selectedPolygon)
    PictureBox1.Invalidate() 'PictureBoxを強制的に再描画する
  End If
End Sub


もしも元のサンプルを拡張して、複数選択できるようにしていた場合はこのようにします。

Private Sub PictureBox1_DoubleClick(sender As Object, e As EventArgs) Handles PictureBox1.DoubleClick
  For n = polygons.Count - 1 To 0 Step -1
    '削除するとインデックス番号がズレるので、後ろの番号から探していく
    If polygons(n).IsSelected Then
      polygons.RemoveAt(n)
    End If
  Next
  PictureBox1.Invalidate() 'PictureBoxを強制的に再描画する
End Sub


上記ではダブルクリックで制御しています。
ダブルクリックで削除する方式では操作しにくいという場合は、別途、削除ボタンを用意し、
それを押したときに IsSelected = True なものを取り除く方式にしても良いでしょう。
あるいは、右クリックやコンテキストメニューなどで指示する手法を採用することもできますね。



> そこで、さらに柱を選択し柱を配置すると、全ての壁が消えてしまいます。
たとえば…

最初は、柱が 0 本、壁が 0 枚の状態です。
この場合、Paint イベントでは「方眼グリッドと、0 本の柱と、0 枚の壁」を描画するようにします。

柱を選択して、マウス操作で柱を建てる場所を決めました(MouseUp/MouseDown で座標を決めていきます)。
これにより、柱が 1 本、壁が 0 枚の状態になりました、
この場合、Paint イベントでは「方眼グリッドと、1 本の柱と、0 枚の壁」を描画するようにします。

同様にして、柱を 4 本建てました;左上、右上、左下、右下と正方形の頂点位置にくるように。
この場合、Paint イベントでは「方眼グリッドと、4 本の柱と、0 枚の壁」を描画します。

さらに、壁を 3 枚描きました。上壁、右壁、下壁を作るイメージです。
この場合、Paint イベントでは「方眼グリッドと、4 本の柱と、3 枚の壁」を描画します。壁が【コ】の字に描かれます。

右クリック等で右側の壁を一枚選択して、その壁だけを削除し、再描画を依頼します。
この場合、Paint イベントでは「方眼グリッドと、4 本の柱と、2 枚の壁」を描画します。壁が【=】の字に描かれます。


すなわち、それぞれのオブジェクト(壁や柱)が、どの座標に描かれているのかを
List(Of ) 等で保持しておく必要があるということです。これは、Visual Basic 中学校サンプルでいう
 Private polygons As New List(Of Polygon)
のことです。現状はそれが無いため、再描画に耐えられるコードになっていません。


> それをToolStripMenuItemで「柱・壁」をselect文で場合分けをしようとしました。
変数 element の宣言が無いようですが、Form1 上に
 Private element As String
があるのでしょうか?


> g = PictureBox1.CreateGraphics()
これは使わないでください。

CreateGraphics は一時的な描画であり、恒久的に描画結果を残すものではありません。
ウィンドウのリサイズなどで容易に失われてしまうものです。
また、Invalidate が呼び出された時点で、CreateGraphics による描画結果は失われることになります。

CreateGraphics の呼び出しは行わないようにして、描画処理を
PictureBox1_Paint イベントの引数 e.Graphics に集約させるべきです。

KOZ さんが紹介してくださった Visual Basic 中学校 のサンプルにおいても、
MouseMove/MouseDown 中に Graphics を操作したりはせず、
Graphics での描画処理は、すべて Paint イベント内だけで行っていましたよね。


とはいえ、ラバーバンドのように一時的な描画に対しては、CreateGrapchis でも
用を足せるのですが、その場合も、Dispose の呼び出しは忘れないようにしてください。

Graphics は、Pen や Brush と同様に「破棄が必要なオブジェクト(IDisposable)」です。

CreateGraphics や Graphics.FromImage によって生成した Graphics オブジェクトは、
使用後に処分せねばならない、ということです。Graphics を自分で生成して使うケースでは
保持させる Graphics 変数を Using ブロックで囲んでおくことが望ましいです。

※ただし、Paint イベントの e.Graphics プロパティで得られる Graphics オブジェクトについては
 自分で生成したインスタンスではないため、処分してはいけません。


もしも描画する内容が多く、すべてを Paint イベント毎回再描画するとコストが高すぎる場合には、
永続的に表示し続けたい画像を Bitmap として用意する方法を併用できます。


たとえば、描画先のキャンバス(たとえば PictureBox1)と同サイズの Bitmap を
Dim bmp As New Bitmap(幅, 高さ) などで作成して
 Using g = Graphics.FromImage(bmp)
  …
 End Using
を使って描画した後、PictureBox1.Image = bmp で割り当てるという手法です。

この時、背景画像(PictureBox1.BackgroundImage)と前景画像(PictureBox1.Image)を併用しても構いません。
割り当てた Bitmap の背景部が、完全透過あるいは半透明だった場合、その下の画像や背景色が
透けて描画されますので、これを CAD のレイヤーのように使うことができます。

レイヤー数を増やしたい場合には、透過背景の Bitmap を複数枚用意しておき、
それが g.DrawImage メソッドで重ね合わせて描画することで対応できます。

なお、Bitmap も破棄が必要なオブジェクト(IDisposable)なため、
使わなくなった時点で bmp.Dispose() を呼ぶ必要があります。
返信 編集キー/


管理者用

- Child Tree -