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

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

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

Visual Basicで簡易CADを作成

[トピック内 51 記事 (1 - 20 表示)]  << 0 | 1 | 2 >>

■103783 / inTopicNo.1)  Visual Basicで簡易CADを作成
  
□投稿者/ shiro (1回)-(2025/07/25(Fri) 20:50:26)

分類:[VB.NET/VB2005 以降] 

Visual Basicで簡易CADを作成したいため、下記のようにグリッド座標を作成しました。
18*18個のマス目が出来ます。点線は半分ズレた柱を配置するためです。

最初に「柱」を選択し、柱を配置したい線の交点にマウスを近づけると、一番近い交点を認識し、そこに柱の■を配置したいです。
柱の数だけ連続して処理したいです。

その後、「壁」を選択し、柱■の2点間をラバーバンド機能でマウスで長方形で配置したいです。

グリッド座標を作成するまでは出来たのですが、その後の処理をするコードで失敗していました。
どのようなコードで記述すれば上手く行くか、アドバイス頂けると有難いです。


                記

Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint

Dim i As Integer
Dim j As Integer

Dim pen As New Pen(Color.Black, 1)

For i = 0 To 36
If (i Mod 2 = 1) Then
pen.DashStyle = Drawing2D.DashStyle.Dot
e.Graphics.DrawLine(pen, 100, 100 + 10 * i, 460, 100 + 10 * i)
Else
pen.DashStyle = Drawing2D.DashStyle.Solid
e.Graphics.DrawLine(pen, 100, 100 + 10 * i, 460, 100 + 10 * i)
End If
Next

For j = 0 To 36
If (j Mod 2 = 1) Then
pen.DashStyle = Drawing2D.DashStyle.Dot
e.Graphics.DrawLine(pen, 100 + 10 * j, 100, 100 + 10 * j, 460)
Else
pen.DashStyle = Drawing2D.DashStyle.Solid
e.Graphics.DrawLine(pen, 100 + 10 * j, 100, 100 + 10 * j, 460)
End If
Next

End Sub

引用返信 編集キー/
■103784 / inTopicNo.2)  Re[1]: Visual Basicで簡易CADを作成
□投稿者/ KOZ (489回)-(2025/07/27(Sun) 14:35:25)
No103783 (shiro さん) に返信
> 最初に「柱」を選択し、柱を配置したい線の交点にマウスを近づけると、一番近い交点を認識し、そこに柱の■を配置したいです。
> 柱の数だけ連続して処理したいです。
> その後、「壁」を選択し、柱■の2点間をラバーバンド機能でマウスで長方形で配置したいです。

ここが参考になるかも。
https://www.umayadia.com/vbsample/VBdotNet-Samples201/Sample275WinFormMouseToDraw.htm
引用返信 編集キー/
■103785 / inTopicNo.3)  Re[2]: Visual Basicで簡易CADを作成
□投稿者/ shiro (2回)-(2025/07/28(Mon) 20:17:16)

> ここが参考になるかも。
> https://www.umayadia.com/vbsample/VBdotNet-Samples201/Sample275WinFormMouseToDraw.htm

非常に参考になりました。
MouseDown、MouseMoveで、ラバーバンドも上手く描けました。有難うございます。

簡易CADですので、MouseDownで描いた柱等が誤った場合が想定されました。
情報提供頂いたHPには消去方法がありませんでした。

右クリック等で、MouseDownで描いた図形を削除するVBの方法をご存じでしたらご教授いただければ幸いです。
引用返信 編集キー/
■103786 / inTopicNo.4)  Re[3]: Visual Basicで簡易CADを作成
□投稿者/ kiku (483回)-(2025/07/29(Tue) 08:49:21)
No103785 (shiro さん) に返信
>>ここが参考になるかも。
>>https://www.umayadia.com/vbsample/VBdotNet-Samples201/Sample275WinFormMouseToDraw.htm
>
> 右クリック等で、MouseDownで描いた図形を削除するVBの方法をご存じでしたらご教授いただければ幸いです。

上記リンクの「四角形をクリックすると色が変わる」がそのまま参考になると思います。

1.MouseDown発生
2.右クリックを判定
3.右クリックした場所が四角形内かどうか判定し、オブジェクトを特定
4.polygonsから特定されたオブジェクトを削除
5.PictureBoxを強制的に再描画

引用返信 編集キー/
■103791 / inTopicNo.5)  Re[3]: Visual Basicで簡易CADを作成
□投稿者/ 魔界の仮面弁士 (3871回)-(2025/07/29(Tue) 14:06:15)
No103783 (shiro さん) に返信
> Dim i As Integer
> For i = 0 To 36
このような書き方をするのは、VB.NET 2002 までです。

VB.NET 2003 以降のバージョンにおいては、For ループのカウンタ変数は事前宣言せずに
 For i As Integer = 0 To 36
のように「For ループ内でのみ使える局所変数」とすることが強く推奨されます。

さらに VB2008 以降では、初期値指定のある変数宣言時には型宣言を省略できるようになったため
 For i = 0 To 36
のように、As Integer 部分も省略する記法が一般的になりました。結果的に、現行バージョンでは
For ループのカウンタ変数は、明示的な変数宣言無しで使っているように見える書き方になっています。


> Dim pen As New Pen(Color.Black, 1)
Pen、Brush、Font などは IDisposable なオブジェクトであるため、
自身で New したものは、使用しなくなった時点で Dispose の呼び出しが必須となります。
VB2005 以降をお使いであれば Using ブロックで囲むのが良いでしょう。

すなわち、「Dim pen As New Pen(Color.Black, 1)」ではなく
 Using pen As New Pen(Color.Black, 1)
  :
 End Using
の構文にします。

ただし Dispose するのは「自身で生成したオブジェクト」に限られます。
Pens.Black や Brushed.Red などは自身で作成したものではなく、
共有オブジェクトなので、勝手に Dispose してはいけません。


Imports System.Drawing.Drawing2D
Public Class Form1
  'マス目の位置
  Private Const marginSize As Integer = 100 '左上の余白サイズ
  Private Const gridCellSize As Integer = 10 '小マスの大きさ
  Private Const gridCount As Integer = 36  '縦横の実線マスの数

  Private ReadOnly edgePos As Integer = marginSize + gridCellSize * gridCount

  Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
    Using gridPen As New Pen(Color.Black)
      '方眼を描く
      For i = 0 To gridCount
        gridPen.DashStyle = If(i Mod 2 = 0, DashStyle.Solid, DashStyle.Dot)
        Dim linePos = marginSize + gridCellSize * i
        e.Graphics.DrawLine(gridPen, marginSize, linePos, edgePos, linePos)
        e.Graphics.DrawLine(gridPen, linePos, marginSize, linePos, edgePos)
      Next
    End Using
  End Sub
End Class



No103785 (shiro さん) に返信
>>ここが参考になるかも。
>>https://www.umayadia.com/vbsample/VBdotNet-Samples201/Sample275WinFormMouseToDraw.htm
> 非常に参考になりました。
> MouseDown、MouseMoveで、ラバーバンドも上手く描けました。有難うございます。

MouseDown / MouseMove イベントは、何をどこに描くのかという「座標情報」を定めています。
座標情報が確定したところで Invaliedate メソッドを呼び出して、描画依頼を飛ばします。

Paint イベントでは、事前に定められた座標情報に従って、柱や壁やラバーバンドを描く処理だけを記述します。


> 情報提供頂いたHPには消去方法がありませんでした。
> 右クリック等で、MouseDownで描いた図形を削除するVBの方法をご存じでしたらご教授いただければ幸いです。

最後に、「クリックした図形の色を変える」サンプルがありましたよね。

あのサンプルでは Polygon クラスを自作して、
 Private polygons As New List(Of Polygon)()
で管理されていました。

今回の場合、Polygon ではなく Pillar クラスや Wall クラスを自作しているかと思いますが、
考え方としては同じです。

選択された図形を管理できるようになっているのですが、その List(Of ) コレクションから
不要な図形を RemoveAt (あるいは Remove) すれば、その図形を削除できることになります。
図形情報を Add / RemoveAt した後は、Invaliedate を呼び直すことで再描画されます。


右クリックの判定を行いたいのであれば、MouseDown イベントの段階で
 If e.Button.HasFlag(MouseButtons.Right) Then
で判定すれば良いでしょう。ちなみに、Control / Alt / Shift 判定も加えるなら
 If ModifierKeys.HasFlag(Keys.Control) Then
 If ModifierKeys.HasFlag(Keys.Alt) Then
 If ModifierKeys.HasFlag(Keys.Shift) Then
を使うことができます。
引用返信 編集キー/
■103792 / inTopicNo.6)  Re[4]: Visual Basicで簡易CADを作成
□投稿者/ shiro (3回)-(2025/07/30(Wed) 12:22:13)
kiku 様

魔界の仮面弁士 様


回答有難うございます。VBでの壁画は始めたばかりで分からない点が多く参考になります。
画像削除方法を勉強してみます。

削除の前に問題が見つかり、自己解決出来なかったので投稿させて頂きます。
柱と壁を別々に動かすことは成功し、それをToolStripMenuItemで「柱・壁」をselect文で場合分けをしようとしました。
下記がその部分です。SnapToGridはFunctionで定めました。

初期設定で柱が設定されており、任意のグリッド座標に柱の四角形が配置されます。
その後、壁を選択し、ラバーバンド機能で柱間に壁を配置できました。
そこで、さらに柱を選択し柱を配置すると、全ての壁が消えてしまいます。

壁が消えないようにするにはどうすれば良いでしょうか。

最終的に、間違って配置した柱や壁も削除機能で削除し、最後に確定した配置から、
座標値を取得し、計算に進みたいと思っています。


           記

Public Class Form1

Private snappedRectangles As New List(Of Rectangle)


Private Sub Form1_Load(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles MyBase.Load

element = "col"
柱ToolStripMenuItem.Checked = True

'描画・オフ
drawFlag = False

End Sub


'================================================================================
'PictureBoxのMouseDownイベント
'--------------------------------------------------------------------------------
Private Sub PictureBox1_MouseDown(
ByVal sender As System.Object,
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles PictureBox1.MouseDown

'描画
Select Case element

'柱
Case "col"

Dim snapPoint As Point = SnapToGrid(e.Location)

' 四角形の中心を交点に合わせる
Dim rect As New Rectangle(
snapPoint.X - rectangleSize \ 2,
snapPoint.Y - rectangleSize \ 2,
rectangleSize,
rectangleSize
)

snappedRectangles.Add(rect)
PictureBox1.Invalidate() ' 再描画

'描画フラグ・オン
drawFlag = True

'壁
Case "wall"

Dim g As Graphics
Dim wallPen As Pen

Dim snapPoint2 As Point = SnapToGrid(e.Location)

Dim rect2 As New Point(
snapPoint2.X - rectangleSize \ 2,
snapPoint2.Y - rectangleSize \ 2
)

'1回目のクリック
If drawFlag = False Then

'開始位置を取得
ptStart.X = snapPoint2.X
ptStart.Y = snapPoint2.Y

'描画フラグ・オン
drawFlag = True

'終了位置の初期化
ptEnd.X = -1
ptEnd.Y = -1

'2回目のクリック
Else

'ラバーバンドを消す
If ptEnd.X <> -1 Then
Call DrawRubberLine(ptStart, ptEnd)

End If

' 終了位置を取得
ptEnd.X = snapPoint2.X
ptEnd.Y = snapPoint2.Y


g = PictureBox1.CreateGraphics()

wallPen = New Pen(Color.Blue)
wallPen.Width = 5

'描画

g.DrawLine(wallPen, ptStart, ptEnd)

'描画フラグ・オフ
drawFlag = False

End If
End Select
End Sub

'--------------------------------------------------------------------------------
'PictureBoxのMouseMoveイベント
'--------------------------------------------------------------------------------
Private Sub PictureBox1_MouseMove(
ByVal sender As Object,
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles PictureBox1.MouseMove

'描画
Select Case element

Case "wall"

Dim snapPoint2 As Point = SnapToGrid(e.Location)

Dim rect2 As New Point(
snapPoint2.X - rectangleSize \ 2,
snapPoint2.Y - rectangleSize \ 2
)

'描画フラグ・オフのとき
If drawFlag = False Then
Exit Sub
End If

'ラバーバンドを消す
If ptEnd.X <> -1 Then
Call DrawRubberLine(ptStart, ptEnd)

End If

' 終了位置を取得
ptEnd.X = snapPoint2.X
ptEnd.Y = snapPoint2.Y

Call DrawRubberLine(ptStart, ptEnd)

End Select

End Sub


Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint

For Each rect In snappedRectangles
g.FillRectangle(Brushes.Red, rect)
g.DrawRectangle(Pens.Black, rect)
Next
End Sub

'--------------------------------------------------------------------------------
'ラバーバンド(直線)を描画
'--------------------------------------------------------------------------------
Private Sub DrawRubberLine(ByVal p1 As Point, ByVal p2 As Point)

'スクリーン座標に変換
p1 = PictureBox1.PointToScreen(p1)
p2 = PictureBox1.PointToScreen(p2)

'ラバーバンドを描画
'ControlPaint.DrawReversibleLine(p1, p2, Color.White)

ControlPaint.DrawReversibleLine(p1, p2, Color.Red)
End Sub

Private Sub 柱ToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles 柱ToolStripMenuItem.Click

element = "col"

'メニューの状態
柱ToolStripMenuItem.Checked = True
壁ToolStripMenuItem.Checked = False

'描画・オフ
drawFlag = False

End Sub

Private Sub 壁ToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles 壁ToolStripMenuItem.Click

element = "wall"

'メニューの状態
柱ToolStripMenuItem.Checked = False
壁ToolStripMenuItem.Checked = True

'描画・オフ
drawFlag = False

End Sub
End Class
引用返信 編集キー/
■103794 / inTopicNo.7)  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() を呼ぶ必要があります。
引用返信 編集キー/
■103795 / inTopicNo.8)  Re[6]: Visual Basicで簡易CADを作成
□投稿者/ shiro (4回)-(2025/07/31(Thu) 07:34:24)
魔界の仮面弁士 様

ダブルクリックでの画像消去コード有難うございました。
非常に参考になりました。

Form1 上に
 Private element As String
は設定していました。

参考のListは「polygon」となっています。
これですと、グリッドの交点を掴むと、スパン幅(1m)に壁が配置されてしまいます。

私のwallはグリッドの2点の交点を結んで、線分の幅で厚さを表そうとしています。
これですと実際のCADに近くなり使いやすいです。
その場合、Pointになるのではと思い、下記のとおり簡単なListを作ってみました。

しかし、今度は次の壁を描こうとすると前のが消えてしまいます。

Paintで「For Each」で描かないとダメなのでしょうか。
その場合のコード記述が分かりませんでした。


       記

   Private startPoint As Point
    Private endPoint As Point
    Private isDragging As Boolean = False

     Private pointList As New List(Of Point)



    Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
        startPoint = SnapToGrid(e.Location)
        isDragging = True
    End Sub

    Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
        If isDragging Then
            endPoint = SnapToGrid(e.Location)
            PictureBox1.Invalidate()
        End If
    End Sub

    Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
        isDragging = False
         endPoint = SnapToGrid(e.Location)
        PictureBox1.Invalidate()
    End Sub

    Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
        Dim pen As New Pen(Color.Black, 1)
        ' グリッド描画
        For i = 0 To PictureBox1.Width Step 10
            e.Graphics.DrawLine(pen, i, 0, i, PictureBox1.Height)
        Next
        For j = 0 To PictureBox1.Height Step 10
            e.Graphics.DrawLine(pen, 0, j, PictureBox1.Width, j)
        Next

        ' ラバーバンド線描画
        ' If isDragging Then
        Dim rubberPen As New Pen(Color.Blue, 5)
        rubberPen.DashStyle = Drawing2D.DashStyle.Solid

        e.Graphics.DrawLine(rubberPen, startPoint, endPoint)
        'End If
    End Sub


引用返信 編集キー/
■103796 / inTopicNo.9)  Re[7]: Visual Basicで簡易CADを作成
□投稿者/ 魔界の仮面弁士 (3874回)-(2025/07/31(Thu) 10:48:38)
No103795 (shiro さん) に返信
> ダブルクリックでの画像消去コード有難うございました。

先の例では、polygons.RemoveAt によって、選択中の四角形を削除したわけですよね。
後はその応用です。

たとえば、polygons(i) と polygons(j) のインデックスを入れ替えれば、
重なり合った図形の上下関係を入れ替える(手前に表示、奥に移動)実装を作れます。

polygons(n).Rect の座標を差し替えるコードを書けば、
選択中の四角形をドラッグ移動で別の場所に再配置することもできるでしょう。


> 参考のListは「polygon」となっています。
> これですと、グリッドの交点を掴むと、スパン幅(1m)に壁が配置されてしまいます。

グリッドには実線と破線がありますが、スナップ先はどちらの交点でも良いのですかね。

グリッドの実線最外周部にオブジェクトを配置すると、柱や壁の厚みが
方眼の外にはみ出る形になりそうですが…再外周部には柱を建てない方が良いのかな。


> 参考のListは「polygon」となっています。
Private polygons As New List(Of Polygon)
のことですね。VB は大文字小文字を区別しませんが、意識して使い分けましょう。

クラス名は通常、先頭を大文字で表記します。
Private 変数やローカル変数は先頭小文字にすることが多いです。


さて。参考コードでは描こうとしているのが「四角形」であることから、座標情報の保持のために
『Recangle 構造体をメンバーに持つ Polygon クラス』というオブジェクトを作っておられます。

まぁ、Polygon は本来「多角形」を表す単語ですし、
Recangle も四角形ではなく「矩形(長方形)」を表すものですから、
役割と名前に軽微なズレがあるのですが……それはさておき。


> しかし、今度は次の壁を描こうとすると前のが消えてしまいます。
「次の壁だけ」を描こうとするからいけないのです。

先の回答で、私は
>> Paint イベントでは「方眼グリッドと、4 本の柱と、3 枚の壁」を描画します
と書いています。配置しようとしている壁だけを描くのではありません。

各回の Paint イベントでは、
『今から配置しようとしているオブジェクトだけ』を描画するのではなく
『今までに配置してきたオブジェクトすべて』を毎回再描画することになります。

そもそも Invalidate メソッドは、これまでの描画結果をまっさらに無効化して、
最初から描画しなおすことを求めるメソッドです。当然、以前の描画内容は毎回クリアされます。

もしもそうした全描画が手間なのであれば、前回の回答のように、
>> 永続的に表示し続けたい画像を Bitmap として用意する方法
を併用することで、差分だけを描画する仕組みにすることもできます。


> 私のwallはグリッドの2点の交点を結んで、線分の幅で厚さを表そうとしています。
> これですと実際のCADに近くなり使いやすいです。
> その場合、Pointになるのではと思い、下記のとおり簡単なListを作ってみました。
いやいや。単に宣言しただけで、一切使われていませんよね。その List 変数…。


御存知の通り、Point が表す情報は「点」だけです。

そして List(Of Point) で表せる情報とは、「0個以上の点」の集合です。
保持される点は 2 個かもしれないし 3 個かも知れません。

しかし Wall を管理するなら、「点A、点B、厚み」の 3 情報が必要ではありませんか?
厚みが固定なら 2 情報ですかね。「左上点、右下点、壁の回転角」などの持ち方もできますが。

もしも壁が 3 箇所に配置されていたら、三枚分の座標情報が必要になります。

柱を配置している座標と、壁を配置する座標はそれぞれ別物なのですから
 Private pointList As New List(Of Point)
という変数だけでは、管理情報が曖昧になってしまいます。

そこで、それぞれの位置関係を分かりやすくするためにも、
Point の集合で管理するのではなく、Visual Basic 中学校のサンプルの考え方に倣って
壁を管理するための「Wall クラス」
柱を管理するための「Pillar クラス」
など、配置オブジェクトごとの管理クラスを用意することをおすすめします。


Wall(壁) クラスにせよ、Pillar(柱) クラスにせよ、
Dot(点) クラスにせよ、Line(線分) クラスにせよ、
Polygon(多角形) クラスにせよ、Ellipse(楕円) クラスにせよ、
それぞれに「自身の位置やサイズを表すためのプロパティ」を持たせます。

そして、柱や壁といったそれぞれのオブジェクトを List(Of ) コレクションなどで管理します。

個々のオブジェクトには、元サンプルのように IsSelected プロパティを設けても良いでしょうし、
BackColor プロパティや SelectionColor プロパティなどを追加することもできるでしょう。


もちろん、描画対象のオブジェクトによって、保持するべき情報は異なります。
壁なら「点A、点B、厚み」の 3 情報で管理されますが、
柱なら、「中心点、柱の太さ」の 2 情報ですかね。
円柱とか角柱といった追加情報を与えることもできそう。

それぞれを List(Of Wall) や List(Of Pillar) といった個別のコレクションで管理することもできますが、
すべての描画オブジェクトを束ねられるよう、Interface あるいは MustInherit を用いて
ポリモーフィズムな実装にするという選択肢もあります。


普段使う Form1 には、Label や TextBox や Button や PictureBox といった、
様々なオブジェクトを配置できますよね。これらのコントロールは、すべて Control クラスを
継承したクラスとして実装されており、Me.Controls のコレクションに束ねられています。



それと同様、この CAD アプリでも描画オブジェクトをポリモーフィズムな実装にしておけば、
Wall や Pillar などを、単一のコレクション上に束ねることができます。

ポリモーフィズムの例として…たとえば、全ての描画オブジェクトの継承元となる
 Public MustInherit Class DrawingObjectBase
  Public Property IsSelected As Boolean
  Public MustOverride Sub Draw(g As Graphics)
 End Class
といったベースクラスを用意してみます。

Form1 上のコレクションも、As New List(Of Polygon) ではなく
 Private drawItems As New List(Of DrawingObjectBase)()
のように、ベースクラスのコレクションに変更します。

Polygon や Wall や Pillar は、このDrawingObjectBase を継承させます。
たとえば Polygon であればこう。

 Public Class Polygon
  Inherits DrawingObjectBase

  Public Property Rect As Rectangle
  Public Overrides Sub Draw(g As Graphics)
   g.FillRectangle(If(IsSelected, Brushes.Yellow, Brushes.Cyan), Rect)
   g.DrawRectangle(Pens.Blue, Rect)
  End Sub
 End Class

このようにしておくと、List(Of )に追加したアイテムのインスタンスが
「壁」であれ「柱」であれ「矩形」であれ、PictureBox1 の Paint イベントの描画処理は

 For Each drawItem In drawItems
  drawItem.Draw(e.Graphics)
 Next

だけで済みます。コレクションにAdd したオブジェクトが柱であれ壁であれ矩形であれ、
PictureBox1_Paint 側ではそれらを区別せず、単に Draw メソッドを呼ぶだけで済むので、
配置オブジェクトの種類を増やすことも容易になります。



> Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
> Dim pen As New Pen(Color.Black, 1)
> Dim rubberPen As New Pen(Color.Blue, 5)
Pen は IDisposable なオブジェクトです。
繰り返しになりますが、自身で生成するのであれば、Using ステートメントで囲む癖をつけましょう。

とはいえ、これらの Pen は何度も使い続ける物ですから、描画のたびにその都度生成しなおすのではなく、
Form の起動時に一度だけ生成し、それを Private 変数で保持して使いまわすという方法でも構いません。
Visual Basic 中学校のサンプルで言うところの「Private selectPen As Pen」のように。

※本来は、フォーム終了時に selectPen も Dispose するコードを書くのが望ましいですが、
 Form1 終了時にはアプリ自体が終了して、自動的に解放されるため、先のサンプルでは省略されています。
引用返信 編集キー/
■103797 / inTopicNo.10)  Re[8]: Visual Basicで簡易CADを作成
□投稿者/ shiro (5回)-(2025/08/01(Fri) 06:15:16)
魔界の仮面弁士 様


ご助言有難うございます。

「ポリモーフィズムな実装」など、画像処理を始めたばかりで難解で、
まずはListで処理をしようと思います。

下記のとおりListで試したところ、Invalidate メソッドで壁も柱も
残ったのですが、壁を配置するとき、次の壁を書くと、直前に書いた壁が消えます。
結局、最後に書いた壁だけが残ります。

全て配置した壁を残る場合はどうすれば良いか上手くいきません。

     記

Public Class Form1

    Private pointList As New List(Of Point)
    Private element As String
    Private startPoint As Point
    Private endPoint As Point
    Private isDragging As Boolean = False

   Private Sub PictureBox1_MouseDown(
          ByVal sender As System.Object,
          ByVal e As System.Windows.Forms.MouseEventArgs) _
          Handles PictureBox1.MouseDown

        Select Case element

                Case "wall"

                    startPoint = SnapToGrid(e.Location)

                     isDragging = True

        End Select

    Private Sub PictureBox1_MouseMove(
          ByVal sender As Object,
          ByVal e As System.Windows.Forms.MouseEventArgs) _
          Handles PictureBox1.MouseMove

        Select Case element

            Case "wall"
 
                If isDragging Then
                    endPoint = SnapToGrid(e.Location)
                    PictureBox1.Invalidate()
                End If

        End Select

    End Sub


    Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp

        isDragging = False
 
        pointList.Clear()
        pointList.Add(startPoint)
        pointList.Add(endPoint)

        PictureBox1.Invalidate()

    End Sub

    Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint

        Dim pen7 As New Pen(Color.Blue, 5)

        If isDragging Then
            e.Graphics.DrawLine(Pens.Blue, startPoint, endPoint)
        ElseIf pointList.Count = 2 Then
            e.Graphics.DrawLine(Pen7, pointList(0), pointList(1))
        End If

    End Sub


引用返信 編集キー/
■103798 / inTopicNo.11)  Re[9]: Visual Basicで簡易CADを作成
□投稿者/ kiku (484回)-(2025/08/01(Fri) 08:47:54)
No103797 (shiro さん) に返信
> Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
>
> isDragging = False
>
> pointList.Clear()
> pointList.Add(startPoint)
> pointList.Add(endPoint)
>
> PictureBox1.Invalidate()
>
> End Sub
pointListを毎回クリアしているので、
保持している線は1本のみで、
過去の線は保持していないのではないでしょうか?

> Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
>
> Dim pen7 As New Pen(Color.Blue, 5)
>
> If isDragging Then
> e.Graphics.DrawLine(Pens.Blue, startPoint, endPoint)
> ElseIf pointList.Count = 2 Then
> e.Graphics.DrawLine(Pen7, pointList(0), pointList(1))
> End If
>
> End Sub

ドラック中は、ドラック中の線のみを描画し、
ドラックしていないときは、保持した1本のみを描画しています。
やはり、過去の線を描画するようにはなっていないようです。

引用返信 編集キー/
■103800 / inTopicNo.12)  Re[9]: Visual Basicで簡易CADを作成
□投稿者/ 魔界の仮面弁士 (3876回)-(2025/08/01(Fri) 10:46:05)
2025/08/01(Fri) 14:32:02 編集(投稿者)

No103797 (shiro さん) に返信
> Dim pen7 As New Pen(Color.Blue, 5)
うーん。頑なに Dispose をサボってくれますねぇ…。
もう指摘するのは諦めました。


> 下記のとおりListで試したところ、Invalidate メソッドで壁も柱も

必要な手順を整理してみましょう。


A:PictureBox1 の MouseDown/MouseMove/MouseUp イベント群の仕事は主に 3 つ
 A1:配置しようとしている図形の位置情報(ドラッグ時などに描きたいラバーバンドの座標など)を更新すること
 A2:MouseUp などで配置が完了した際に、その図形(壁や柱)の情報を List(Of ) に追加していくこと
 A3:これらの位置情報を更新した場合、PictureBox1.Invalidate() を呼びだすこと

B:PictureBox1 の Paint イベントの仕事は主に 3 つ
 B1:方眼グリッドを描画すること
 B2:List(Of ) に保持させておいた図形群を、すべて描画していくこと
 B3:配置中図形(ドラッグ移動などによるラバーバンドなど)の状況を描画すること


A2・B2 で読み書きしている List コレクションに保持されている図形群とは、
「今までに配置してきた、『すべての』壁と柱の情報」のことを指します。
これは VB 中学校のサンプルで言うところの「Private polygons As New List(Of Polygon)」に当たります。

A1・B3 で読み書きしている配置中図形の情報とは、
「現在配置しようとしている、『いずれか 1 つ』のオブジェクト情報(壁 or 柱)」を指します。
VB 中学校のサンプルで言う「mouseDownPosition」「mouseDragPosition」などのことです。


ここまではよろしいですか?

※B1 は Paint イベントで毎回再描画する代わりに、方眼画像を描いた Bitmap を
 あらかじめ PictureBox1.BackgroundImage あるいは PictureBox1.Image に割り当てておく方が楽だと思います。


> 次の壁を書くと、直前に書いた壁が消えます。
「今の壁を描く時、それまで描いた壁を描いていないから」です。


まず、shiro さんが用意された pointList というのは、
「今までに配置してきた『すべての』壁の情報」ではなく
「現在配置しようとしている、『いずれか 1つ』の壁情報」に過ぎません。


VB中学校の「Private polygons As New List(Of Polygon)」は、配置済みのすべての矩形情報を保持していますが
shiro さんの「Private pointList As New List(Of Point)」が保持するのは、現在配置しようとしている element = "wall" の情報だけなのです。


もしも pointList を「配置済みのすべての壁情報」に変更したいのであれば、現在の MouseUp イベント内の
 pointList.Clear()
という行をコメントアウトしてください。そうすれば、過去の壁情報がすべて累積保持されます。

その場合、壁を 3 つ描いたとしたら、
 pointList(0) は、壁1の開始点
 pointList(1) は、壁1の終了点
 pointList(2) は、壁2の開始点
 pointList(3) は、壁2の終了点
 pointList(4) は、壁3の開始点
 pointList(5) は、壁3の終了点
のように管理されることになります。

すなわち、Paint イベントでは
 If isDragging Then
  e.Graphics.DrawLine(Pens.Blue, startPoint, endPoint)
 ElseIf pointList.Count = 2 Then
  e.Graphics.DrawLine(Pen7, pointList(0), pointList(1))
 End If
ではなく、
 For n = 0 To pointList.Count - 1 Step 2
  e.Graphics.DrawLine(Pen7, pointList(n + 0), pointList(n + 1))
 Next
 If isDragging Then
  e.Graphics.DrawLine(Pens.Blue, startPoint, endPoint)
 End If
にすれば良い、ということになります。
これにより、全ての壁が描かれるようになることでしょう。


正しく動作することを確認できたら、コメントアウトしていた pointList.Clear() していた行を復活させてみてください。
「最新の壁だけが描かれ、過去の壁は消えてしまう」状態に戻ってしまうことを体験できると思います。


そして先に述べた通り、pointList As List(Of Point) が保持しているのは
あくまで座標情報の集合にすぎません。

このままでは、蓄えていった座標が「壁」なのか「柱」なのかを区別できません。

壁の位置情報と、柱の位置情報を、それぞれ別々の List(Of Point) 変数で分けて管理することもできますが、
そのように個別の変数で管理していってしまうと、今後の機能拡張――たとえば、
それぞれの「壁の厚さ」や「柱の太さ」や「色」などをも指定・変更できるようにするとか、
新たに壁や柱以外のオブジェクト(扉など)を加えるなど――を実装する際、
管理が煩雑になり過ぎてしまいますし、デバッグも大変になってしまいます。


そのため、参考にしているサイト(Visual Basic 中学校)の例に倣って、
描画対象のオブジェクトごとに、管理用のクラス(Wall や Pillar など)を
用意されることを強くおすすめします。
その方が今後の拡張性が高くなり、メンテナンスも容易になりますよ。


そこからさらに、ポリモーフィズムな設計(基底クラスを Inherits するなど)を採用することで、
さらに管理が容易になるかと思いますが…そこまで組むにはオブジェクト指向設計の知識が
要求されますので、それは今後、経験をつんでからの課題ということで。



No103798 (kiku さん) に返信
> ドラック中は、ドラック中の線のみを描画し、
> ドラックしていないときは、保持した1本のみを描画しています。

drag (引きずる) にしても
drug (薬物) にしても、一般的には
「ドラック」ではなく
「ドラッグ」と表記します。
引用返信 編集キー/
■103801 / inTopicNo.13)  Re[10]: Visual Basicで簡易CADを作成
□投稿者/ kiku (485回)-(2025/08/01(Fri) 10:50:02)
No103800 (魔界の仮面弁士 さん) に返信

> ■No103798 (kiku さん) に返信
>>ドラック中は、ドラック中の線のみを描画し、
>>ドラックしていないときは、保持した1本のみを描画しています。
>
> drag (引きずる) にしても
> drug (薬物) にしても、一般的には
> 「ドラック」ではなく
> 「ドラッグ」と表記します。

あうう。おっしゃる通りです。泣き
引用返信 編集キー/
■103802 / inTopicNo.14)  Re[10]: Visual Basicで簡易CADを作成
□投稿者/ shiro (6回)-(2025/08/02(Sat) 03:38:00)
No103800 (魔界の仮面弁士 さん) に返信
> 2025/08/01(Fri) 14:32:02 編集(投稿者)

魔界の仮面弁士 様


> もしも pointList を「配置済みのすべての壁情報」に変更したいのであれば、現在の MouseUp イベント内の
>  pointList.Clear()
> という行をコメントアウトしてください。そうすれば、過去の壁情報がすべて累積保持されます。
>
> その場合、壁を 3 つ描いたとしたら、
>  pointList(0) は、壁1の開始点
>  pointList(1) は、壁1の終了点
>  pointList(2) は、壁2の開始点
>  pointList(3) は、壁2の終了点
>  pointList(4) は、壁3の開始点
>  pointList(5) は、壁3の終了点
> のように管理されることになります。
>
> すなわち、Paint イベントでは
>  If isDragging Then
>   e.Graphics.DrawLine(Pens.Blue, startPoint, endPoint)
>  ElseIf pointList.Count = 2 Then
>   e.Graphics.DrawLine(Pen7, pointList(0), pointList(1))
>  End If
> ではなく、
>  For n = 0 To pointList.Count - 1 Step 2
>   e.Graphics.DrawLine(Pen7, pointList(n + 0), pointList(n + 1))
>  Next
>  If isDragging Then
>   e.Graphics.DrawLine(Pens.Blue, startPoint, endPoint)
>  End If
> にすれば良い、ということになります。
> これにより、全ての壁が描かれるようになることでしょう。
>
>
> 正しく動作することを確認できたら、コメントアウトしていた pointList.Clear() していた行を復活させてみてください。
> 「最新の壁だけが描かれ、過去の壁は消えてしまう」状態に戻ってしまうことを体験できると思います。

柱と壁が全て描けました。有難うございます。
かなり手こずったので嬉しかったです。


> そして先に述べた通り、pointList As List(Of Point) が保持しているのは
> あくまで座標情報の集合にすぎません。
>
> このままでは、蓄えていった座標が「壁」なのか「柱」なのかを区別できません。

柱は既に、

Private snappedRectangles As New List(Of Rectangle)

としてリストに分けていました。こちらは解決済みでした。

壁の位置情報と、柱の位置情報を、それぞれ別々の List(Of Point) 変数で分けて管理しています。
クラスという概念もまだ分からないことが多く、とりあえず計算が動くことをするための簡易ソフトです。

壁にはとりあえず、剛性5kN/mと仮定し、座標から長さを計算し、乗じることでX,Y方向の耐力を算出するものです。
柱は壁が取り付く場所を判別するために、座標情報だけを与えています。

図形の削除はPolygonではないので、紹介して頂いたような方法では出来ないのでしょうか。

また、計算結果はCSVやtxt形式で出力させますが、簡易CADで作成した情報を、
ファイルで保存や読み込みをして修正・追加入力をする機能も付与したいのですが、
この場合の保存方法は一般的にどうするのが妥当でしょうか。


引用返信 編集キー/
■103803 / inTopicNo.15)  Re[10]: Visual Basicで簡易CADを作成
□投稿者/ shiro (7回)-(2025/08/02(Sat) 03:40:48)
No103798 (kiku さん) に返信
> ■No103797 (shiro さん) に返信

kiku 様

> pointListを毎回クリアしているので、
> 保持している線は1本のみで、
> 過去の線は保持していないのではないでしょうか?

クリアしているのが問題でした。
解決できました。
引用返信 編集キー/
■103805 / inTopicNo.16)  Re[11]: Visual Basicで簡易CADを作成
□投稿者/ 魔界の仮面弁士 (3877回)-(2025/08/03(Sun) 11:50:26)
No103802 (shiro さん) に返信
> 図形の削除はPolygonではないので、紹介して頂いたような方法では出来ないのでしょうか。

えぇと…。No103791 の
>> 選択された図形を管理できるようになっているのですが、その List(Of ) コレクションから
>> 不要な図形を RemoveAt (あるいは Remove) すれば、その図形を削除できることになります。
については理解されている、という事でよろしいでしょうか。
RemoveAt と Remove の違いについても、説明しなくて大丈夫ですね?


まず、『配置済みのすべての【矩形】情報』は、VB 中学校でいうところの
「Private polygons As New List(Of Polygon)」でしたよね。

shiro さんのケースでは、
『配置済みのすべての【柱】情報』が「Private snappedRectangles As New List(Of Rectangle)」
『配置済みのすべての【壁】情報』が「Private pointList As New List(Of Point)」
なわけですよね。ここまで理解いただけていると思います。

つまり、図形を保持しているのは、いずれも「List(Of 型パラメータ) クラス」です。

柱が3本立っていた場合、
 柱A = snappedRectangles(0)
 柱B = snappedRectangles(1)
 柱C = snappedRectangles(2)
なわけですが、このうち、柱Bを削除したければ、
 snappedRectangles.RemoveAt(1)
を呼び出せばよいことになります。これにより、残った柱は
 柱A = snappedRectangles(0)
 柱C = snappedRectangles(1)
と変化します。

削除するごとにインデックス番号がズレますので、もしも複数の要素をまとめて削除する場合は
For ループは 0 から最終番号に向けて昇順列挙しながら RemoveAt するのではなく、
For ループを最終番号から 0 にむけて降順列挙しながら RemoveAt するようにします。
No103794 のコードもそうなっていますよね?


削除部の手順は、上記の通りさほど難しくもありませんし、既に回答済みでもあります。
問題となるのは、その柱Bを「どうやってユーザーに選択させるか」という点でしょうか?


No103794 では、削除対象のオブジェクトを選択させる手段について
>> ダブルクリックで削除する方式では操作しにくいという場合は、別途、削除ボタンを用意し、
>> それを押したときに IsSelected = True なものを取り除く方式にしても良いでしょう。
>> あるいは、右クリックやコンテキストメニューなどで指示する手法を採用することもできますね。
という素案を述べています。

実際、VB中学校のサンプルをダブルクリックで削除する方法だと、
複数の矩形が重なりあっている場合、重なっている部分をダブルクリックすると
手前側の矩形が削除された瞬間、奥の矩形が選択状態になってしまい、使いにくいかと思います。

たとえば、Excel の図形の場合を考えてみましょう。
Excel のワークシート上に、矩形や楕円や直線やテキストボックスなどを、適当に 5 つほど貼り付けたうえで
[ホーム]リボン > [編集] グループにある[検索と選択]>[オブジェクトの選択と表示] もしくは
[ページ レイアウト]リボン > [配置] グループにある、[オブジェクトの選択と表示] を選択してみます。
https://hamachan.info/win10/office/object.html

そうすると、シート上に貼った画像アイテム群が列挙され、それを使って選択することができますよね。

これと同じものを作ることは、さほど難しくは無いでしょう。

たとえば、柱の一覧を扱うための ListBox を用意しておき、
snappedRectangles.Add する際には、それと同時に、柱用の ListBox にも .Items.Add しておくのです。
Add する内容は、それぞれの柱を識別できる情報になっていれば、ひとまずなんでも良いです。
"柱1"、"柱2" といった連番情報でも良いですし、
柱のグリッド行列番号でも良いですし、
柱の Rectangle を .ToString() した座標情報でも良いです。

そのうえで、ユーザーは ListBox から削除したい柱を選択してもらいます。

snappedRectangles と ListBox のアイテム番号を常に同期させてておくことで、
ListBox の .SelectedIndex プロパティから「消したい柱のインデックス」が得られますので、
それを snappedRectangles と ListBox の両方から RemoveAt すればよいのです。

これは、複数の柱をまとめて削除したい場合にも使えます。
 Do Until ListBox1.SelectedIndex = -1
  snappedRectangles.RemoveAt(ListBox1.SelectedIndex)
  ListBox1.Items.RemoveAt(ListBox1.SelectedIndex)
 Loop


あるいは ListBox に頼らず、描画済みの壁や柱を直接クリックして選択させたり、
周囲の背景をドラッグして囲むことで範囲内の図形を選択状態にするといった
ユーザーインターフェイスにしてもよいでしょう。

その場合、選択中のアイテムは色を変えて表現するなどの必要があるでしょうから、
Paint イベントで色を変えて描画するために、各アイテムが「選択中かどうか」を
管理するための変数が必要になってきます。これについては、現状の
 Private snappedRectangles As New List(Of Rectangle)()
 Private pointList As New List(Of Point)()
だけでは、それぞれのアイテムの「位置とサイズ」しか分からず、「選択状態かどうか」を管理できません。

上記の ListBox のように、「選択されているアイテムの番号」を別管理する方法でも良いですが、
お奨めされるのは、先に何度も提案している通り、「柱クラス」や「壁クラス」を自作して、
それぞれのクラスに、座標情報だけでなく VB中学校の例のような「IsSelected プロパティ」を設けることです。


> また、計算結果はCSVやtxt形式で出力させますが、簡易CADで作成した情報を、
> ファイルで保存や読み込みをして修正・追加入力をする機能も付与したいのですが、
> この場合の保存方法は一般的にどうするのが妥当でしょうか。

ファイルの読み書きの方法が分からない、ということでしょうか。
その場合、まずは読み書きの方法を習得することから始めましょう。
それを CAD に組み込むのはその後の話になりますね。

ファイルの読み書きの基礎となるのは、System.IO 名前空間のクラス群です。
たとえば、ファイル全体を一括読み取りするなら、System.IO.File.ReadAllLines など。

データ量が多い場合は、全体をまとめて読み取るとメモリ消費量が増えてしまうため、
一行ずつ読み取って順次変換していく方法が採用されることもあります。

ファイル操作については、ひとまず、この辺りを参考にしてみださい。
http://rucio.o.oo7.jp/main/dotnet/shokyu/standard30.htm
https://dobon.net/vb/dotnet/file/index.html


そのうえで、ファイルを扱う際の注意点を幾つか:

・読み書きするファイルのパスは固定にするのか、任意のパスにするのか?

任意のパスにするなら、ユーザーにパスを選択させるための OpenFileDialog / SaveFileDialog の使い方を学んだ方が良いです。

固定とする場合、そのパスは「読み書き可能な場所」でなければなりませんが。EXE と同じフォルダ―にあるファイルは
読み込みはできても書き込みや編集が許可されていないこと(例:Program Files フォルダー)が少なからずあるため、
%LocalAppData% 、デスクトップ、ドキュメント フォルダーなどの、読み書き可能なパスが使われたりします。

これらの特殊フォルダーのパスは、Environment.GetFolderPath メソッドや
My.Computer.FileSystem.SpecialDirectories などから得られます。


・例外処理の方法は習得済みか?

ファイルを保存しようとしたら、ディスク容量不足や書き込み権限不足でエラーになることがあります。
その CSV を Excel で開いていたら、排他ロックがかかっていて上書き保存できないこともあるでしょう。
読み込みも同様に、アクセスできずにエラーになってしまう可能性がありますし、実際に読み込んでみたら
「拡張子が .csv なだけで、中身は Excel ブックだった」といった異常データの可能性もあるかもしれません。

このため、外部ファイルの読み書きを行う際には、「例外処理(Try〜Catch)」を組み込むことがほぼ必須となります。


・ファイルの文字コードは?

保存したいデータが、座標情報の数値だけならば、ASCII 形式でも良いですが、
漢字やカナなどを含めるのであれば、Shift_JIS か UTF-8 を採用するのが良いでしょう。
.NET では、デフォルトで UTF-8 が採用されますが、処理系によっては Shift_JIS の方が望まれることもあります。

ファイルを読み書きする際の文字コードの指定については、System.Text.Encoding クラスについて調べてみてください。

なお、そのファイルを読み書きするのが自アプリだけに限定される場合は、CSV といったテキストデータにせず、
独自のバイナリファイルや、ローカルデータベースファイルを採用するといった選択肢もあります。
引用返信 編集キー/
■103806 / inTopicNo.17)  Re[12]: Visual Basicで簡易CADを作成
□投稿者/ shiro (8回)-(2025/08/04(Mon) 05:10:00)
No103805 (魔界の仮面弁士 さん) に返信
> ■No103802 (shiro さん) に返信
>>図形の削除はPolygonではないので、紹介して頂いたような方法では出来ないのでしょうか。

魔界の仮面弁士 様


いつもご丁寧な回答有難うございます。
休日、酷暑の中感謝致します。

> つまり、図形を保持しているのは、いずれも「List(Of 型パラメータ) クラス」です。
>
> 柱が3本立っていた場合、
>  柱A = snappedRectangles(0)
>  柱B = snappedRectangles(1)
>  柱C = snappedRectangles(2)
> なわけですが、このうち、柱Bを削除したければ、
>  snappedRectangles.RemoveAt(1)
> を呼び出せばよいことになります。これにより、残った柱は
>  柱A = snappedRectangles(0)
>  柱C = snappedRectangles(1)
> と変化します。
>
> 削除するごとにインデックス番号がズレますので、もしも複数の要素をまとめて削除する場合は
> For ループは 0 から最終番号に向けて昇順列挙しながら RemoveAt するのではなく、
> For ループを最終番号から 0 にむけて降順列挙しながら RemoveAt するようにします。
> No103794 のコードもそうなっていますよね?
>
>
> 削除部の手順は、上記の通りさほど難しくもありませんし、既に回答済みでもあります。
> 問題となるのは、その柱Bを「どうやってユーザーに選択させるか」という点でしょうか?
>
>
> No103794 では、削除対象のオブジェクトを選択させる手段について
> >> ダブルクリックで削除する方式では操作しにくいという場合は、別途、削除ボタンを用意し、
> >> それを押したときに IsSelected = True なものを取り除く方式にしても良いでしょう。
> >> あるいは、右クリックやコンテキストメニューなどで指示する手法を採用することもできますね。
> という素案を述べています。

分かりやすい説明でした。番号がズレるのですね。

> たとえば、柱の一覧を扱うための ListBox を用意しておき、
> snappedRectangles.Add する際には、それと同時に、柱用の ListBox にも .Items.Add しておくのです。
> Add する内容は、それぞれの柱を識別できる情報になっていれば、ひとまずなんでも良いです。
> "柱1"、"柱2" といった連番情報でも良いですし、
> 柱のグリッド行列番号でも良いですし、
> 柱の Rectangle を .ToString() した座標情報でも良いです。
>
> そのうえで、ユーザーは ListBox から削除したい柱を選択してもらいます。
>
> snappedRectangles と ListBox のアイテム番号を常に同期させてておくことで、
> ListBox の .SelectedIndex プロパティから「消したい柱のインデックス」が得られますので、
> それを snappedRectangles と ListBox の両方から RemoveAt すればよいのです。
>
> これは、複数の柱をまとめて削除したい場合にも使えます。
>  Do Until ListBox1.SelectedIndex = -1
>   snappedRectangles.RemoveAt(ListBox1.SelectedIndex)
>   ListBox1.Items.RemoveAt(ListBox1.SelectedIndex)
>  Loop
>
>
> あるいは ListBox に頼らず、描画済みの壁や柱を直接クリックして選択させたり、
> 周囲の背景をドラッグして囲むことで範囲内の図形を選択状態にするといった
> ユーザーインターフェイスにしてもよいでしょう。
>
> その場合、選択中のアイテムは色を変えて表現するなどの必要があるでしょうから、
> Paint イベントで色を変えて描画するために、各アイテムが「選択中かどうか」を
> 管理するための変数が必要になってきます。これについては、現状の
>  Private snappedRectangles As New List(Of Rectangle)()
>  Private pointList As New List(Of Point)()
> だけでは、それぞれのアイテムの「位置とサイズ」しか分からず、「選択状態かどうか」を管理できません。
>
> 上記の ListBox のように、「選択されているアイテムの番号」を別管理する方法でも良いですが、
> お奨めされるのは、先に何度も提案している通り、「柱クラス」や「壁クラス」を自作して、
> それぞれのクラスに、座標情報だけでなく VB中学校の例のような「IsSelected プロパティ」を設けることです。

ここら辺の連動した処理が難しそうだと感じました。
座標情報も保存するとなると、やはりクラスを使用しないと難しいのでしょうね。
今まで、subだけで処理していましたが、少し勉強してみます。

> ファイルの読み書きの方法が分からない、ということでしょうか。
> その場合、まずは読み書きの方法を習得することから始めましょう。
> それを CAD に組み込むのはその後の話になりますね。
>
> ファイルの読み書きの基礎となるのは、System.IO 名前空間のクラス群です。
> たとえば、ファイル全体を一括読み取りするなら、System.IO.File.ReadAllLines など。
>
> データ量が多い場合は、全体をまとめて読み取るとメモリ消費量が増えてしまうため、
> 一行ずつ読み取って順次変換していく方法が採用されることもあります。
>
> ファイル操作については、ひとまず、この辺りを参考にしてみださい。
> http://rucio.o.oo7.jp/main/dotnet/shokyu/standard30.htm
> https://dobon.net/vb/dotnet/file/index.html

今までsubだけでデータの入出力はcsvやテキストで実施していました。
やはり保存や読み込みは同様に実施し、グリッド上に座標をスナップさせ、
柱は四角形で、壁はPointで線を繋ぐ逆の処理をし、再度、図形を描かせるということでしょうか。

図形の削除、ファイルへの入出力等、少し時間をかけて実施してみます。

引用返信 編集キー/
■103808 / inTopicNo.18)  Re[13]: Visual Basicで簡易CADを作成
□投稿者/ 魔界の仮面弁士 (3879回)-(2025/08/04(Mon) 11:36:03)
No103806 (shiro さん) に返信
> 柱は四角形で、
「四角形」だと、「凹四角形」や「平行四辺形」も含まれてしまうので、
より正確には「長方形」という表現になりますね。


現状の柱は、Point 構造体ではなく Rectangle 構造体で管理されているわけですが、
広義の意味の「四角形」を表そうとしたら、Rectangle ではなく、
「4 つの Point」を用いる方が良いでしょう。
https://www.umayadia.com/vbsample/VBdotNet-Samples201/Sample279WinFormDrawPolygon.htm


一方、柱が長方形(正方形)に限定されており、斜めに配置する事も無いのであれば
一つの壁を「4 個の Point」で扱うより、「単一の Rectangle」の方が管理しやすいですよね?


であれば同様に壁についても「2 個の Point」ではなく単一のオブジェクトで扱った方が
分かりやすくなるとは思います、クラスや構造体を自作できるだけの経験を積み重ねれば。



> 壁はPointで線を繋ぐ逆の処理をし、
幾つか疑問が。

a)柱を配置しないと、壁を描けない仕様でしょうか?
 あるいは先に壁を描いた後、両端に柱が建つという手順もあるのでしょうか?

b)もしも壁の両端が柱と接触していなければならないという条件なのだとしたら
 柱を撤去した場合、それに付随する壁も一緒に削除されるべきということになりますか?

c)一本の柱に複数の壁が接触している可能性がありますか?
 一つの柱交点から複数の壁が伸びるイメージで(「╋」「┗」「┫」「*」など)


もしも壁の両端に柱が必須であるがゆえに、柱の撤去と共に壁も取り去る必要があるのだとしたら。

柱を削除する際には、削除する柱に接するすべての壁を列挙して、
柱のみならず、接する壁すべても併せて RemoveAt しないといけないでしょうね。

そうして、柱(snappedRectangles)やpointList(壁)の情報を変化させた後は、
再描画を依頼するために、いつもの『PictureBox1.Invalidate()』を呼び出します。

これによって、
> 再度、図形を描かせるということでしょうか。
のための Paint イベントが発生することになります。



あるいは、「柱に接していない壁も配置できる」ルールだが、本来は柱が必要であるというのなら、
接していない壁を別の色で描画したい、といったニーズも出てくるかもしれません。そうした場合も、
 ・位置算出処理(マウス操作あるいはCSV読み取り)で、壁ごとの状態や色情報を記録し、Invalidate を呼び出す
 ・Paint イベントで、既に算出済みの柱や壁の情報を読み取り、適切な位置と色で描いていく
という方針には変わりないでしょうね。


> 今までsubだけでデータの入出力はcsvやテキストで実施していました。
> 柱は四角形で、壁はPointで線を繋ぐ逆の処理をし、再度、図形を描かせるということでしょうか。

もちろん、Sub だけで開発することも可能ですが、
できれば Function や Property についても学んでいった方が良いでしょう。


今回の「壁や柱の情報を保持している CSV」の具体的な内容が分からないので
具体的なコードは書けませんが:

柱と壁を別々に管理するのではなく、No103796 のような統一的な継承実装にしておくと、
 Friend Function LoadFromCSV(csvPath As String) As List(Of DrawingObjectBase)
のようなメソッドを作り込むことができ、今後、
 ・剛性を5kN/m固定とせず、基準剛性の異なる厚壁/薄壁の追加
 ・壁や柱以外のアイテムを新設
など、管理情報が増えた場合にも保守しやすいコードになりますよ。


イメージコード:

'描画アイテム全てを管理しているコレクション
Private drawItems As New List(Of DrawingObjectBase)()

'CSV ファイル読み込みボタン
Private Sub Button1_Click(……
 If OpenFileDialog1.ShowDialog() = DialogResult.Cancel Then
  Return 'CSVファイルが選択されなかった
 End If

 'CSV を読み込み
 Dim newItems As List(Of DrawingObjectBase) = LoadFromCSV(OpenFileDialog1.FileName)
 If newItems IsNot Nothing Then
  '読み込んだデータに基づいて再描画
  Me.drawItems = newItems
  PictureBox1.Invalieda()
 End If
End Sub

Private Sub PictureBox1_Paint(……
 For Each drawItem In drawItems
  drawItem.Draw(e.Graphics) '柱も壁もすべて、このメソッドを呼ぶだけで描画される
 Next
 :
 :
End Sub
引用返信 編集キー/
■103811 / inTopicNo.19)  Re[14]: Visual Basicで簡易CADを作成
□投稿者/ shiro (9回)-(2025/08/04(Mon) 18:59:38)
No103808 (魔界の仮面弁士 さん) に返信
> ■No103806 (shiro さん) に返信

魔界の仮面弁士 様

> 幾つか疑問が。
>
> a)柱を配置しないと、壁を描けない仕様でしょうか?
>  あるいは先に壁を描いた後、両端に柱が建つという手順もあるのでしょうか?
>
> b)もしも壁の両端が柱と接触していなければならないという条件なのだとしたら
>  柱を撤去した場合、それに付随する壁も一緒に削除されるべきということになりますか?
>
> c)一本の柱に複数の壁が接触している可能性がありますか?
>  一つの柱交点から複数の壁が伸びるイメージで(「╋」「┗」「┫」「*」など)

壁は必ず柱間に配置するルールとなっています。
順番はどちらが先でも構いませんが、このようなルールのため、
先に柱を配置し、その間をラバーバンドのように壁を配置しました。

計算したい項目は3つあります。

1つ目は柱情報は関係なく、X,Y方向の壁の長さを座標から計算し、
その壁の剛性を乗じて方向別に耐力を出します。

2つ目は、1つ目の計算を上下左右の1/4の面積に存在する壁だけで計算します。
この際に柱の座標によって平面図の輪郭から1/4の面積を計算することになります。

3つ目は、柱の両側に接続する、壁の左右の剛性の差から引張力を計算します。
その際、2階建ての場合は、その柱の上の柱も同様に計算します。
X,Y方向別なので、最大でも両側に壁がある2つの剛性の差となります。

取り合えず、1つ目の簡単な計算を完成することを目標にしています。
間違って配置した柱・壁の削除、座標情報等のCSVファイルの入出力が成功すれば、
計算自体は方向別に集計するだけですのでうまくいくと思います。

まずは1階(平屋)だけをイメージし、PictureBoxも1つだけで1,2階を選択できません。

また、俄か仕込みですが、CLASSを試してみました。
コンパイルは成功したのですが、柱を選択しグリッドをクリックしても全く配置されなくなりました。
初心者にはクラスの使用は難しいかもしれません。

以下、変更した部分です。

Private Sub PictureBox1_MouseDown(
ByVal sender As System.Object,
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles PictureBox1.MouseDown

Dim pillarList As New List(Of Pillar)

Dim newPillar As New Pillar With {
.Index = pillarList.Count + 1,
.X = SnapToGrid(e.Location).X,
.Y = SnapToGrid(e.Location).Y
}

pillarList.Add(newPillar)

PictureBox1.Invalidate() ' 再描画


Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint

Dim pillarList As New List(Of Pillar)

For Each pillar In pillarList

g.FillRectangle(Brushes.Red, pillar.X - rectangleSize \ 2, pillar.Y - rectangleSize \ 2, rectangleSize, rectangleSize)
g.DrawRectangle(Pens.Black, pillar.X - rectangleSize \ 2, pillar.Y - rectangleSize \ 2, rectangleSize, rectangleSize)

Next


Public Class Pillar

Public Property Index As Integer ' 柱のインデックス
Public Property X As Integer ' X座標
Public Property Y As Integer ' Y座標

End Class

引用返信 編集キー/
■103812 / inTopicNo.20)  Re[15]: Visual Basicで簡易CADを作成
 
□投稿者/ 魔界の仮面弁士 (3881回)-(2025/08/04(Mon) 19:43:28)
No103811 (shiro さん) に返信
> Private Sub PictureBox1_MouseDown(
>  ByVal sender As System.Object,
>  ByVal e As System.Windows.Forms.MouseEventArgs) _
>  Handles PictureBox1.MouseDown
>
>  Dim pillarList As New List(Of Pillar)

Sub や Function の内側で Dim 宣言された変数を「ローカル変数」と呼びます。

そのデータ型が Integer であれ List であれ String であれ、
上記のローカル変数は、MouseDown イベントが発生するたびに初期化され
MouseDown イベントを抜けるたびに破棄されてしまいます。

※変数の有効期間のことを、専門用語で「スコープ」と呼びます。


> Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
>  Dim pillarList As New List(Of Pillar)

Paint イベントで宣言された pillarList 変数と
MouseDown イベントで宣言された pillarList 変数は
名前が同じというだけで、それぞれ別物です。


描画オブジェクトの List は、各イベント間で共有されるべきものですから、
ローカル変数として「Dim pillarList As New List(Of Pillar)」などと宣言するのではなく、
フィールド変数として宣言するようにしてください。

つまり、従来の
 Private pointList As New List(Of Point)
 Private snappedRectangles As New List(Of Rectangle)
と同様に、Form1 の直下にて
 Private pillarList As New List(Of Pillar)
として宣言されるべきである、ということです。


これは、クラスを自作していなかったとしても同じことです。

スコープの概念は、Visual Basic の言語仕様として重要な所なので、
もしも御存知なかったのであれば
 Visual Basic 中学校の 初級講座 [改訂版]
 第9回 変数のスコープと寿命
 https://www.umayadia.com/VBStandard2/VBStandard2Toc.htm
などで学んでおきましょう。


> Dim pillarList As New List(Of Pillar)
> Dim newPillar As New Pillar With {
>  .Index = pillarList.Count + 1,
>  .X = SnapToGrid(e.Location).X,
>  .Y = SnapToGrid(e.Location).Y
> }
> pillarList.Add(newPillar)

これだと、Index 管理に問題があります。
No103805 で 柱B を削除した時のことを覚えていますか?

たとえば、柱が 3 本あった場合、それぞれの Index は
 柱A ⇒ pillarList(0).Index は 1
 柱B ⇒ pillarList(1).Index は 2
 柱C ⇒ pillarList(2).Index は 3
と追加されていくことを想定されているのかと思います。

ではここで、柱A を削除したとすると
 柱B ⇒ pillarList(0).Index は 2
 柱C ⇒ pillarList(1).Index は 3
に変わるわけですよね。

ではその後、さらに柱D を建てるときに
 .Index = pillarList.Count + 1
が実行されたらどうなるでしょう? pillarList には柱が 2 本しかないので
 柱B ⇒ pillarList(0).Index は 2
 柱C ⇒ pillarList(1).Index は 3
 柱D ⇒ pillarList(2).Index は 3
になってしまうわけです。


柱に固定的な番号を付けるのであれば、同じ Index 番号が再利用されることは防ぐべきなので、
例えばこのような書き方もしてみてください。

 Public Class Pillar
  Private Shared LastIndex As Integer = 0
  Public ReadOnly Property Index As Integer
  Public Property X As Integer
  Public Property Y As Integer

  Public Sub New(location As Point)
   LastIndex += 1
   Me.Index = LastIndex
   Me.X = location.x
   Me.Y = location.y
  End Sub
 End Class


そして、柱を建てる際には
  Dim newPillar As New Pillar(SnapToGrid(e.Location))
  pillarList.Add(newPillar)
のようにします。こうすれば Index も自動採番されますし、
pillarList から柱が削除された後も、Index の重複が発生することがありません。
引用返信 編集キー/

このトピックをツリーで一括表示

次の20件>
トピック内ページ移動 / << 0 | 1 | 2 >>

管理者用

- Child Tree -