C# WinForm编写一个六边形菜单

  using System;

  using System.Drawing;

  using System.Windows.Forms;

  namespace HexagonButton

  {

  public partial class HButton : PictureBox

  {

  //0-6 每个梯形位置,-1 中间位置

  //          4

  //     3         5

  //         -1

  //     2         0

  //          1

  /// 

  /// 菜单点击事件处理

  /// 

  public event Action OnMenuClicked;

  /// 

  /// 每个梯形位置

  /// 

  private Trapezoid[] trapezoids = new Trapezoid[6];

  /// 

  /// 中间的小正六边形位置

  /// 

  private Point[] centerHexagon = new Point[6];

  /// 

  /// 鼠标当前位置

  /// 

  private Point? mouseHoverLocation = null;

  /// 

  /// 鼠标滑过时的层背景

  /// 

  private SolidBrush mouseHoverLayerBrush = new SolidBrush(Color.FromArgb(50, Color.White));

  public HButton()

  {

  InitializeComponent();

  DoubleBuffered = true;

  }

  /// 

  /// 缩放窗体时(调用),自动修正位置

  /// 

  /// 

  /// 

  public void ResetSizeByForm(int formWidth, int formHeight)

  {

  var hHeight = (int)(formHeight * 0.8);

  var hWidth = hHeight;

  var hLeft = (formWidth - hWidth) / 2;

  var hTop = (formHeight - hHeight) / 2;

  this.Location = new Point(hLeft, hTop);

  this.Width = hWidth;

  this.Height = hHeight;

  }

  private void InitHexagonMenus()

  {

  this.BackColor = Color.Transparent;

  //this.Image = Properties.Resources.button_bg;

  this.SizeMode = PictureBoxSizeMode.Zoom;

  this.Cursor = Cursors.Hand;

  //计算图片缩放级别

  //var scale = Properties.Resources.button_bg.Width / this.Width;

  //计算原始图片高度和宽度之差,因为该背景非正六边形,所以计算一下宽度和高度之差,用以计算正确得位置

  //var diffOfImageSize = (Properties.Resources.button_bg.Width - Properties.Resources.button_bg.Height) / scale;

  var diffOfImageSize = 0;

  var sideWidth = (this.Width - diffOfImageSize) / 2;

  var offset = this.Width / 2;

  //计算最外层大六边形顶点位置

  var big = CalculateHexagonVertices((this.Width + diffOfImageSize / 2) / 2, offset);

  //计算内部小六边形顶点位置

  var small = CalculateHexagonVertices(sideWidth / 2, offset);

  //计算两个六边形相交之后,形成得六边形环,分割为6个等腰梯形,用以检测点击事件

  trapezoids = CalculateTrapezoids(big, small);

  //计算内部小的正六边形,用以检测点击事件

  centerHexagon = CalculateHexagonVertices(sideWidth / 2 - 20, offset);

  }

  /// 

  /// 鼠标滑过的时候,重绘界面,然后设置鼠标位置

  /// 

  /// 

  protected override void OnMouseMove(MouseEventArgs e)

  {

  base.OnMouseMove(e);

  mouseHoverLocation = e.Location;

  Invalidate();

  }

  /// 

  /// 鼠标移除

  /// 

  /// 

  protected override void OnMouseLeave(EventArgs e)

  {

  base.OnMouseLeave(e);

  mouseHoverLocation = null;

  }

  /// 

  /// 菜单点击事件

  /// 

  /// 

  protected override void OnMouseClick(MouseEventArgs e)

  {

  var mouseLocation = e.Location;

  //检测鼠标是否在中间的六边形中:

  if (IsPointInPolygon(mouseLocation, centerHexagon))

  {

  OnMenuClicked?.Invoke(this, -1);

  return;

  }

  //检测是否在某个梯形内部

  for (int i = 0; i < trapezoids.Length; i++)

  {

  var trapezoid = trapezoids[i];

  if (IsPointInTrapezoid(mouseLocation, trapezoid))

  {

  OnMenuClicked?.Invoke(this, i);

  return;

  }

  }

  }

  protected override void OnPaint(PaintEventArgs e)

  {

  base.OnPaint(e);

  var g = e.Graphics;

  #if DEBUG

  //以下的代码可以直接删除,这里是作为标识多边形位置

  g.FillPolygon(new SolidBrush(Color.FromArgb(180, Color.Red)), centerHexagon);

  for (int i = 0; i < trapezoids.Length; i++)

  {

  var trapezoid = trapezoids[i];

  g.FillPolygon(new SolidBrush(Color.FromArgb(10 * (i + 1), Color.Yellow)), trapezoid.Points);

  }

  #endif

  if (mouseHoverLocation == null)

  {

  return;

  }

  //检测鼠标是否在中间的六边形中:

  if (IsPointInPolygon(mouseHoverLocation.Value, centerHexagon))

  {

  g.FillPolygon(mouseHoverLayerBrush, centerHexagon);

  return;

  }

  //检测是否在某个梯形内部

  for (int i = 0; i < trapezoids.Length; i++)

  {

  var trapezoid = trapezoids[i];

  if (IsPointInTrapezoid(mouseHoverLocation.Value, trapezoid))

  {

  g.FillPolygon(mouseHoverLayerBrush, trapezoid.Points);

  return;

  }

  }

  }

  /// 

  /// 计算正六边形的顶点坐标

  /// 

  /// 

  /// 

  /// 

  private static Point[] CalculateHexagonVertices(int sideLength, int offset = 0)

  {

  Point[] vertices = new Point[6];

  double angle = 2 * Math.PI / 6;

  for (int i = 0; i < 6; i++)

  {

  int x = offset + (int)(sideLength * Math.Cos(i * angle));

  int y = offset + (int)(sideLength * Math.Sin(i * angle));

  vertices[i] = new Point(x, y);

  }

  return vertices;

  }

  /// 

  /// 计算每个梯形的坐标

  /// 

  /// 

  /// 

  /// 

  private static Trapezoid[] CalculateTrapezoids(Point[] hexagonVertices, Point[] smallHexagonVertices)

  {

  Trapezoid[] trapezoids = new Trapezoid[6];

  for (int i = 0; i < 6; i++)

  {

  Point topLeft = hexagonVertices[i];

  Point topRight = hexagonVertices[(i + 1) % 6];

  Point bottomLeft = smallHexagonVertices[i];

  Point bottomRight = smallHexagonVertices[(i + 1) % 6];

  trapezoids[i] = new Trapezoid(topLeft, topRight, bottomLeft, bottomRight)

  {

  Index = i

  };

  }

  return trapezoids;

  }

  /// 

  /// 判断点是否在梯形内

  /// 

  /// 

  /// 

  /// 

  private static bool IsPointInTrapezoid(Point checkPoint, Trapezoid trapezoid)

  {

  return IsPointInPolygon(checkPoint, trapezoid.Points);

  }

  /// 

  /// 判断点是否在多边形内.

  /// 来源:https://blog.csdn.net/xxdddail/article/details/49093635

  /// ----------原理----------

  /// 注意到如果从P作水平向左的射线的话,如果P在多边形内部,那么这条射线与多边形的交点必为奇数,

  /// 如果P在多边形外部,则交点个数必为偶数(0也在内)。

  /// 

  /// 要判断的点

  /// 多边形的顶点

  /// 

  private static bool IsPointInPolygon(Point checkPoint, Point[] polygonPoints)

  {

  bool inside = false;

  int pointCount = polygonPoints.Length;

  Point p1, p2;

  for (int i = 0, j = pointCount - 1; i < pointCount; j = i, i++)//第一个点和最后一个点作为第一条线,之后是第一个点和第二个点作为第二条线,之后是第二个点与第三个点,第三个点与第四个点...

  {

  p1 = polygonPoints[i];

  p2 = polygonPoints[j];

  if (checkPoint.Y < p2.Y)

  {//p2在射线之上

  if (p1.Y <= checkPoint.Y)

  {//p1正好在射线中或者射线下方

  if ((checkPoint.Y - p1.Y) * (p2.X - p1.X) > (checkPoint.X - p1.X) * (p2.Y - p1.Y))//斜率判断,在P1和P2之间且在P1P2右侧

  {

  //射线与多边形交点为奇数时则在多边形之内,若为偶数个交点时则在多边形之外。

  //由于inside初始值为false,即交点数为零。所以当有第一个交点时,则必为奇数,则在内部,此时为inside=(!inside)

  //所以当有第二个交点时,则必为偶数,则在外部,此时为inside=(!inside)

  inside = (!inside);

  }

  }

  }

  else if (checkPoint.Y < p1.Y)

  {

  //p2正好在射线中或者在射线下方,p1在射线上

  if ((checkPoint.Y - p1.Y) * (p2.X - p1.X) < (checkPoint.X - p1.X) * (p2.Y - p1.Y))//斜率判断,在P1和P2之间且在P1P2右侧

  {

  inside = (!inside);

  }

  }

  }

  return inside;

  }

  /// 

  /// 当窗体改变时,自动计算大小

  /// 

  /// 

  protected override void OnClientSizeChanged(EventArgs e)

  {

  base.OnClientSizeChanged(e);

  InitHexagonMenus();

  }

  }

  /// 

  /// 梯形类

  /// 

  internal sealed class Trapezoid

  {

  public int Index { get; set; }

  public Point TopLeft { get; set; }

  public Point TopRight { get; set; }

  public Point BottomLeft { get; set; }

  public Point BottomRight { get; set; }

  public Point[] Points

  {

  get

  {

  return new Point[] { TopLeft, TopRight, BottomRight, BottomLeft };

  }

  }

  public Trapezoid(Point topLeft, Point topRight, Point bottomLeft, Point bottomRight)

  {

  TopLeft = topLeft;

  TopRight = topRight;

  BottomLeft = bottomLeft;

  BottomRight = bottomRight;

  }

  public override string ToString()

  {

  return $"Trapezoid {{ Index={Index}, TopLeft={TopLeft}, TopRight={TopRight}, BottomLeft={BottomLeft}, BottomRight={BottomRight} }}";

  }

  }

  }