为动态生成的六边形网格中的每个六边形命名并实现点击交互
嘿,我来帮你梳理这个六边形网格的问题!先给你明确几个核心点,再给你重构优化后的代码方案:
六边形网格设计与优化指南
一、先给你吃个定心丸:用Graphics绘制完全可行
你当前用Graphics类绘制六边形的方向是对的,但核心问题是你没有把每个六边形的信息持久化存储——现在你只是在Paint事件里临时生成顶点绘制,后续要点击、引用这些六边形根本找不到对应的对象。所以第一步是要把每个六边形变成可管理的对象。
二、解决“命名/引用”的关键:创建Hexagon类
要给每个六边形命名并方便后续操作,最好的方式是定义一个Hexagon类,把每个六边形的唯一标识(比如行列ID)、中心坐标、顶点、颜色状态都存进去。这样每个六边形都是一个独立的对象,想怎么引用就怎么引用。
比如这个基础的Hexagon类:
Public Class Hexagon ' 唯一命名标识,比如"Hex_2_3"代表第2行第3列的六边形 Public Property Id As String ' 六边形中心坐标 Public Property Center As PointF ' 六个顶点(用于绘制和点击检测) Public Property Vertices As PointF() ' 自定义样式属性 Public Property FillBrush As Brush = Brushes.Green Public Property OutlinePen As Pen = Pens.Black ' 构造函数:传入中心坐标和半径,自动生成顶点 Public Sub New(centerX As Single, centerY As Single, radius As Integer) Center = New PointF(centerX, centerY) Vertices = GenerateVertices(centerX, centerY, radius) End Sub ' 生成六边形顶点的工具方法(用弧度计算,更准确) Private Function GenerateVertices(centerX As Single, centerY As Single, radius As Integer) As PointF() Dim vertices(5) As PointF ' 六边形每个顶点间隔60度(π/3弧度) For angleIndex As Integer = 0 To 5 Dim radian As Double = Math.PI / 3 * angleIndex vertices(angleIndex) = New PointF( centerX + radius * Math.Cos(radian), centerY + radius * Math.Sin(radian) ) Next Return vertices End Function ' 判断点击点是否在六边形内部(用于点击检测) Public Function ContainsPoint(clickPoint As Point) As Boolean Dim isInside As Boolean = False Dim p1 As PointF = Vertices(0) ' 遍历所有边,用射线法判断点是否在多边形内 For i As Integer = 1 To Vertices.Length Dim p2 As PointF = Vertices(i Mod Vertices.Length) If clickPoint.Y > Math.Min(p1.Y, p2.Y) AndAlso clickPoint.Y <= Math.Max(p1.Y, p2.Y) Then If clickPoint.X <= Math.Max(p1.X, p2.X) Then Dim xIntersect As Double = (clickPoint.Y - p1.Y) * (p2.X - p1.X) / (p2.Y - p1.Y) + p1.X If p1.X = p2.X OrElse clickPoint.X <= xIntersect Then isInside = Not isInside End If End If End If p1 = p2 Next Return isInside End Function End Class
三、重构网格生成逻辑:把所有六边形存起来
接下来在Form里定义一个集合来存储所有Hexagon对象,把生成网格的逻辑和绘制逻辑分开——Paint事件只负责绘制,数据都提前存在集合里,这样Paint触发时不会重复计算,效率更高。
Public Class Form1 ' 存储所有六边形的集合 Private _hexGrid As List(Of Hexagon) = New List(Of Hexagon)() ' 网格配置参数(改成常量,方便修改) Private Const HexRadius As Integer = 20 Private Const GridSize As Integer = 10 ' 六边形的间距计算(基于半径,避免硬编码数值) Private ReadOnly HexHorizontalSpacing As Single = HexRadius * 1.5F Private ReadOnly HexVerticalSpacing As Single = HexRadius * Math.Sqrt(3) Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 窗体加载时生成网格并存储所有六边形 GenerateHexGrid() End Sub Private Sub GenerateHexGrid() _hexGrid.Clear() For row As Integer = 0 To GridSize - 1 For col As Integer = 0 To GridSize - 1 ' 计算中心坐标:奇数行要偏移半个垂直间距,实现交错排列 Dim centerX As Single = HexRadius + col * HexHorizontalSpacing Dim centerY As Single = HexRadius + row * HexVerticalSpacing + If(row Mod 2 = 1, HexVerticalSpacing / 2, 0) ' 创建六边形对象,用行列号作为唯一ID Dim hex = New Hexagon(centerX, centerY, HexRadius) hex.Id = $"Hex_{row + 1}_{col + 1}" ' 比如第一行第一列就是Hex_1_1 _hexGrid.Add(hex) Next Next End Sub Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint ' 遍历集合绘制每个六边形,简单高效 For Each hex In _hexGrid e.Graphics.FillPolygon(hex.FillBrush, hex.Vertices) e.Graphics.DrawPolygon(hex.OutlinePen, hex.Vertices) ' 可选:绘制ID文本,方便调试查看命名 e.Graphics.DrawString(hex.Id, Me.Font, Brushes.Black, hex.Center.X - 18, hex.Center.Y - 10) Next End Sub
四、实现点击六边形的功能
现在有了存储的Hexagon对象,就可以通过MouseClick事件来检测点击的是哪个六边形,然后执行对应的操作:
Private Sub Form1_MouseClick(sender As Object, e As MouseEventArgs) Handles Me.MouseClick ' 遍历所有六边形,判断点击点是否在内部 For Each hex In _hexGrid If hex.ContainsPoint(e.Location) Then ' 这里写点击后的操作,比如弹窗提示、改变颜色 MessageBox.Show($"你点击了:{hex.Id}") hex.FillBrush = Brushes.Yellow ' 点击后变色标记 Me.Invalidate() ' 刷新界面显示变化 Exit For ' 找到目标六边形后退出循环 End If Next End Sub End Class
五、给你的代码优化建议
- 去掉硬编码数值:你之前写的
HexagonY +=34、HexagonY=36.75都是基于半径20的固定值,现在用HexVerticalSpacing = HexRadius * Math.Sqrt(3)动态计算,修改半径时不需要改其他代码,灵活性拉满。 - 分离数据与视图:把六边形的数据(顶点、ID、状态)和绘制逻辑分开,符合面向对象设计,后续要加拖动、缩放、状态修改等功能都非常方便。
- 复用计算逻辑:把生成顶点、点是否在多边形内的逻辑封装到Hexagon类里,避免重复写代码,也方便维护。
- 避免Paint事件里的重复计算:Paint事件会频繁触发(比如窗口拖动、最小化恢复),提前把所有六边形生成好存在集合里,每次Paint只需要遍历绘制,性能更好。
内容的提问来源于stack exchange,提问作者Elijah Adams
相关产品推荐
相关产品推荐

