Elijah Adams
Elijah Adams

Reputation: 41

Giving Dynamically Created Shapes a Name

I'm designing a hexagon grid and I need to be able to name each hexagon, so I can refer to them later. Below is my class, it generates the hexagon grid, and I've labeled the code throughout so you can understand what's happening. I've been searching for a while now reading a lot about Graphics, but I can't get a working design with the answers I've seen offered. Perhaps, I'm going about this wrong by using Graphics, but my plan is to be able to click on each hexagon and do something with it.

Note: If you see a way to improve my code let me know. It's appreciated!

' Generate Hexagon Grid
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint

    ' Hexagon Grid Parameters
    Dim HexagonRadius As Integer = 20 ' Fix "Position Hexagon Grid Columns" Before Changing Hexagon Radius
    Dim GridSize As Integer = 10

    ' Generate Hexagon Grid
    Dim HexagonX As Integer = HexagonRadius
    Dim HexagonY As Integer = HexagonRadius
    For i As Integer = 1 To GridSize
        For j As Integer = 1 To GridSize

            ' Hexagon Vertex Coordinates
            Dim point1 As New Point((HexagonX - HexagonRadius), (HexagonY))
            Dim point2 As New Point((HexagonX - (HexagonRadius / 2)), (HexagonY + ((HexagonRadius / 2) * Math.Sqrt(3))))
            Dim point3 As New Point((HexagonX + (HexagonRadius / 2)), (HexagonY + ((HexagonRadius / 2) * Math.Sqrt(3))))
            Dim point4 As New Point((HexagonX + HexagonRadius), (HexagonY))
            Dim point5 As New Point((HexagonX + (HexagonRadius / 2)), (HexagonY - ((HexagonRadius / 2) * Math.Sqrt(3))))
            Dim point6 As New Point((HexagonX - (HexagonRadius / 2)), (HexagonY - ((HexagonRadius / 2) * Math.Sqrt(3))))
            Dim hexagonPoints As Point() = {point1, point2, point3, point4, point5, point6}

            ' Create Hexagon
            e.Graphics.FillPolygon(Brushes.Green, hexagonPoints)

            ' Hexagon Outline
            e.Graphics.DrawLine(Pens.Black, point1, point2)
            e.Graphics.DrawLine(Pens.Black, point2, point3)
            e.Graphics.DrawLine(Pens.Black, point3, point4)
            e.Graphics.DrawLine(Pens.Black, point4, point5)
            e.Graphics.DrawLine(Pens.Black, point5, point6)
            e.Graphics.DrawLine(Pens.Black, point6, point1)

            ' Position Hexagon Grid Columns
            HexagonY += 34 ' Specific to Hexagon Radius: 20
        Next
        If i Mod 2 > 0 Then
            HexagonY = 36.75 ' Specific to Hexagon Radius: 20
        Else
            HexagonY = 20 ' Specific to Hexagon Radius: 20
        End If
        HexagonX += 30 ' Specific to Hexagon Radius: 20
    Next
End Sub

Upvotes: 0

Views: 55

Answers (1)

PavlinII
PavlinII

Reputation: 1083

You'll need to create some Hexagon class with it's coordinates and (maybe name, if really needed). And save them to some suitable collection (2-dimensional array maybe?) This should happen somewhere outside your Paint event and might be recalculated on grid SizeChanged event.

Inside your Paint event you'll just iterate throught existing collection and render according to pre-computed coordinates.

OnClick event will loop throught the same collection to find specific Hexagon for updating (changing background color for example) and forcing form to repaint to take effect.

For large rendering you should consider rendering to bitmap first and drawing that final bitmap to e.Graphics for faster work. Your bitmap could be cached as well to speed up even more.

EDIT: Code sample added

Turn Option Strict On in your project properties to avoid many problems in your code that you're not aware of.

Public Class frmTest

    Private Const HexagonRadius As Integer = 20
    Private Const GridSize As Integer = 10

    Private fHexagons As New List(Of Hexagon)
    Private fCache As Bitmap
    Private fGraphics As Graphics

    Private Sub ResetHexagons() 'Call when some parameter changes (Radius/GridSize)
        fHexagons.Clear()
        Invalidate()
    End Sub

    Private Function EnsureHexagons() As List(Of Hexagon)
        Dim X, Y As Single, xi, yi As Integer
        If fHexagons.Count = 0 Then
            X = HexagonRadius : Y = HexagonRadius
            For xi = 1 To GridSize
                For yi = 1 To GridSize
                    fHexagons.Add(New Hexagon(HexagonRadius, X, Y))
                    Y += 34
                Next
                'Do your math to get theese values from HexagonRadius value
                If xi Mod 2 > 0 Then
                    Y = 36.75
                Else
                    Y = 20
                End If
                X += 30
            Next
            fCache?.Dispose()
            fGraphics?.Dispose()
            fCache = New Bitmap(GridSize * HexagonRadius * 2, GridSize * HexagonRadius * 2)
            fGraphics = Graphics.FromImage(fCache)
            For Each H As Hexagon In fHexagons
                H.Render(fGraphics)
            Next
        End If
        Return fHexagons
    End Function

    Private Sub frmTest_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
        EnsureHexagons()
        e.Graphics.DrawImageUnscaled(fCache, Point.Empty)
    End Sub

    Private Sub frmTest_MouseClick(sender As Object, e As MouseEventArgs) Handles Me.MouseClick
        Dim H As Hexagon = EnsureHexagons.FirstOrDefault(Function(X) X.Contains(e.Location))
        If H IsNot Nothing Then
            H.Checked = Not H.Checked
            H.Render(fGraphics) 'Update cache without repainting all
            Invalidate()
        End If
    End Sub

End Class

Public Class Hexagon

    Public ReadOnly Radius, X, Y As Single
    Public ReadOnly Points() As PointF
    Public Property Checked As Boolean

    Public Sub New(Radius As Single, X As Single, Y As Single)
        Me.Radius = Radius : Me.X = X : Me.Y = Y
        Points = {New PointF((X - Radius), (Y)),
                    New PointF((X - (Radius / 2)), CSng(Y + ((Radius / 2) * Math.Sqrt(3)))),
                    New PointF((X + (Radius / 2)), CSng(Y + ((Radius / 2) * Math.Sqrt(3)))),
                    New PointF((X + Radius), (Y)),
                    New PointF((X + (Radius / 2)), CSng(Y - ((Radius / 2) * Math.Sqrt(3)))),
                    New PointF((X - (Radius / 2)), CSng(Y - ((Radius / 2) * Math.Sqrt(3.0!))))}
    End Sub

    Public Sub Render(G As Graphics)
        ' Create Hexagon
        G.FillPolygon(If(Checked, Brushes.Blue, Brushes.Green), Points)
        ' Hexagon Outline
        For i As Integer = 0 To Points.Length - 1
            G.DrawLine(Pens.Black, Points(i), Points((i + 1) Mod Points.Length))
        Next
    End Sub

    Public Function Contains(P As Point) As Boolean
        'Do your math here, this is just simplified estimation
        Return X - Radius <= P.X AndAlso P.X <= X + Radius AndAlso Y - Radius <= P.Y AndAlso P.Y <= Y + Radius
    End Function

End Class

Upvotes: 1

Related Questions