C# WinForm編寫(xiě)一個(gè)六邊形菜單
背景
閑來(lái)無(wú)事逛群聊,看到一網(wǎng)友發(fā)來(lái)這么一個(gè)需求:

純色圖下面是一張六邊形布局的背景圖層,具體圖片背景內(nèi)容不便透露,所以這里采用色塊形式模擬,ZF類(lèi)的項(xiàng)目都喜歡這種給人新奇的東西,這每一個(gè)色塊,都是一個(gè)可以點(diǎn)擊進(jìn)入的菜單。
分析
乍一看這圖,就是三個(gè)正六邊形嵌套。所以就根據(jù)公式 2 * Math.PI / 6計(jì)算出每個(gè)6邊形每個(gè)頂點(diǎn)偏移角度,再根據(jù)公式計(jì)算出每個(gè)頂點(diǎn)的坐標(biāo)位置。算法如下:
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;
}首先根據(jù) sideLength 邊長(zhǎng),計(jì)算出最外層正六邊形6個(gè)頂點(diǎn)的位置。注意這個(gè)時(shí)候因?yàn)轫旤c(diǎn)是從 (0,0) 計(jì)算的,所以這里要加入 offset 設(shè)置頂點(diǎn)位置,讓每一頂點(diǎn)平移指定的像素。再計(jì)算中間那個(gè)小正六邊形(包括藍(lán)色間隙)的頂點(diǎn)位置。最后再計(jì)算最中間不包括間隙的紫色小正六邊形頂點(diǎn)位置。這時(shí)候有人可能要問(wèn)中間那些色塊的位置怎么確定,這是個(gè)好問(wèn)題,仔細(xì)看就知道了,這每一塊都是一個(gè)等腰梯形,而且每個(gè)等腰梯形,都可以根據(jù)最外層的大正六邊形和中間那個(gè)小正六邊形(包括藍(lán)色間隙)頂點(diǎn)位置計(jì)算出來(lái)。
實(shí)現(xiàn)
先定義一個(gè)梯形類(lèi):
internal?sealed?class?Trapezoid
{
????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;
????}
}然后通過(guò)外層的兩個(gè)正六邊形頂點(diǎn)位置計(jì)算梯形位置,演示代碼如下:
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);
????}
????return?trapezoids;
}我寫(xiě)了一個(gè)繼承自 PictureBox 的控件類(lèi),重寫(xiě)了 OnPaint 方法,實(shí)現(xiàn)了以上色塊的展示。演示代碼如下:
protected?override?void?OnPaint(PaintEventArgs?e)
{
????base.OnPaint(e);
????var?g?=?e.Graphics;
????//centerHexagon?是中間最小的不包括間隙的紫色的六邊形頂點(diǎn)位置
????g.FillPolygon(new?SolidBrush(Color.FromArgb(180,?Color.Red)),?centerHexagon);
????for?(int?i?=?0;?i?<?trapezoids.Length;?i++)
????{
????????//trapezoids?是6個(gè)梯形位置
????????var?trapezoid?=?trapezoids[i];
????????g.FillPolygon(new?SolidBrush(Color.FromArgb(10?*?(i?+?1),?Color.Yellow)),?trapezoid.Points);
????}
}需要實(shí)現(xiàn)鼠標(biāo)點(diǎn)擊事件,我們需要定義一個(gè)事件,記錄鼠標(biāo)當(dāng)前位置,然后在 OnMouseClick 方法中檢測(cè)鼠標(biāo)位置是否在某個(gè)梯形或者最中間位置,然后觸發(fā)事件。演示代碼如下:
//0-6?每個(gè)梯形位置,-1?中間位置
//??????????4?
//?????3?????????5
//?????????-1
//?????2?????????0
//??????????1
///?<summary>
///?菜單點(diǎn)擊事件處理
///?</summary>
public?event?Action<object,?int>?OnMenuClicked;
///?<summary>
///?鼠標(biāo)當(dāng)前位置
///?</summary>
private?Point??mouseHoverLocation?=?null;
///?<summary>
///?鼠標(biāo)滑過(guò)的時(shí)候,重繪界面,然后設(shè)置鼠標(biāo)位置
///?</summary>
///?<param?name="e"></param>
protected?override?void?OnMouseMove(MouseEventArgs?e)
{
????base.OnMouseMove(e);
????mouseHoverLocation?=?e.Location;
????Invalidate();
}
///?<summary>
///?鼠標(biāo)移除
///?</summary>
///?<param?name="e"></param>
protected?override?void?OnMouseLeave(EventArgs?e)
{
????base.OnMouseLeave(e);
????mouseHoverLocation?=?null;
}
///?<summary>
///?菜單點(diǎn)擊事件
///?</summary>
///?<param?name="e"></param>
protected?override?void?OnMouseClick(MouseEventArgs?e)
{
????var?mouseLocation?=?e.Location;
????//檢測(cè)鼠標(biāo)是否在中間的六邊形中:
????if?(IsPointInPolygon(mouseLocation,?centerHexagon))
????{
????????OnMenuClicked?.Invoke(this,?-1);
????????return;
????}
????//檢測(cè)是否在某個(gè)梯形內(nèi)部
????for?(int?i?=?0;?i?<?trapezoids.Length;?i++)
????{
????????var?trapezoid?=?trapezoids[i];
????????if?(IsPointInTrapezoid(mouseLocation,?trapezoid))
????????{
????????????OnMenuClicked?.Invoke(this,?i);
????????????return;
????????}
????}
}里面有一個(gè) IsPointInPolygon 方法,用于檢測(cè)某一點(diǎn)是否在某個(gè)多邊形內(nèi),這里的算法抄襲了某N上的代碼,如下:
///?<summary>
///?判斷點(diǎn)是否在多邊形內(nèi).
///?來(lái)源:https://blog.csdn.net/xxdddail/article/details/49093635
///?----------原理----------
///?注意到如果從P作水平向左的射線(xiàn)的話(huà),如果P在多邊形內(nèi)部,那么這條射線(xiàn)與多邊形的交點(diǎn)必為奇數(shù),
///?如果P在多邊形外部,則交點(diǎn)個(gè)數(shù)必為偶數(shù)(0也在內(nèi))。
///?</summary>
///?<param?name="checkPoint">要判斷的點(diǎn)</param>
///?<param?name="polygonPoints">多邊形的頂點(diǎn)</param>
///?<returns></returns>
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++)//第一個(gè)點(diǎn)和最后一個(gè)點(diǎn)作為第一條線(xiàn),之后是第一個(gè)點(diǎn)和第二個(gè)點(diǎn)作為第二條線(xiàn),之后是第二個(gè)點(diǎn)與第三個(gè)點(diǎn),第三個(gè)點(diǎn)與第四個(gè)點(diǎn)...
????{
????????p1?=?polygonPoints[i];
????????p2?=?polygonPoints[j];
????????if?(checkPoint.Y?<?p2.Y)
????????{//p2在射線(xiàn)之上
????????????if?(p1.Y?<=?checkPoint.Y)
????????????{//p1正好在射線(xiàn)中或者射線(xiàn)下方
????????????????if?((checkPoint.Y?-?p1.Y)?*?(p2.X?-?p1.X)?>?(checkPoint.X?-?p1.X)?*?(p2.Y?-?p1.Y))//斜率判斷,在P1和P2之間且在P1P2右側(cè)
????????????????{
????????????????????//射線(xiàn)與多邊形交點(diǎn)為奇數(shù)時(shí)則在多邊形之內(nèi),若為偶數(shù)個(gè)交點(diǎn)時(shí)則在多邊形之外。
????????????????????//由于inside初始值為false,即交點(diǎn)數(shù)為零。所以當(dāng)有第一個(gè)交點(diǎn)時(shí),則必為奇數(shù),則在內(nèi)部,此時(shí)為inside=(!inside)
????????????????????//所以當(dāng)有第二個(gè)交點(diǎn)時(shí),則必為偶數(shù),則在外部,此時(shí)為inside=(!inside)
????????????????????inside?=?(!inside);
????????????????}
????????????}
????????}
????????else?if?(checkPoint.Y?<?p1.Y)
????????{
????????????//p2正好在射線(xiàn)中或者在射線(xiàn)下方,p1在射線(xiàn)上
????????????if?((checkPoint.Y?-?p1.Y)?*?(p2.X?-?p1.X)?<?(checkPoint.X?-?p1.X)?*?(p2.Y?-?p1.Y))//斜率判斷,在P1和P2之間且在P1P2右側(cè)
????????????{
????????????????inside?=?(!inside);
????????????}
????????}
????}
????return?inside;
}大致就是這樣,再就是縮放窗體時(shí)自動(dòng)計(jì)算位置的細(xì)節(jié)處理,這里先貼一下全部代碼:
using?System;
using?System.Drawing;
using?System.Windows.Forms;
namespace?HexagonButton
{
????public?partial?class?HButton?:?PictureBox
????{
????????//0-6?每個(gè)梯形位置,-1?中間位置
????????//??????????4?
????????//?????3?????????5
????????//?????????-1
????????//?????2?????????0
????????//??????????1
????????///?<summary>
????????///?菜單點(diǎn)擊事件處理
????????///?</summary>
????????public?event?Action<object,?int>?OnMenuClicked;
????????///?<summary>
????????///?每個(gè)梯形位置
????????///?</summary>
????????private?Trapezoid[]?trapezoids?=?new?Trapezoid[6];
????????///?<summary>
????????///?中間的小正六邊形位置
????????///?</summary>
????????private?Point[]?centerHexagon?=?new?Point[6];
????????///?<summary>
????????///?鼠標(biāo)當(dāng)前位置
????????///?</summary>
????????private?Point??mouseHoverLocation?=?null;
????????///?<summary>
????????///?鼠標(biāo)滑過(guò)時(shí)的層背景
????????///?</summary>
????????private?SolidBrush?mouseHoverLayerBrush?=?new?SolidBrush(Color.FromArgb(50,?Color.White));
????????public?HButton()
????????{
????????????InitializeComponent();
????????????DoubleBuffered?=?true;
????????}
????????///?<summary>
????????///?縮放窗體時(shí)(調(diào)用),自動(dòng)修正位置
????????///?</summary>
????????///?<param?name="formWidth"></param>
????????///?<param?name="formHeight"></param>
????????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;
????????????//計(jì)算圖片縮放級(jí)別
????????????//var?scale?=?Properties.Resources.button_bg.Width?/?this.Width;
????????????//計(jì)算原始圖片高度和寬度之差,因?yàn)樵摫尘胺钦呅?,所以?jì)算一下寬度和高度之差,用以計(jì)算正確得位置
????????????//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;
????????????//計(jì)算最外層大六邊形頂點(diǎn)位置
????????????var?big?=?CalculateHexagonVertices((this.Width?+?diffOfImageSize?/?2)?/?2,?offset);
????????????//計(jì)算內(nèi)部小六邊形頂點(diǎn)位置
????????????var?small?=?CalculateHexagonVertices(sideWidth?/?2,?offset);
????????????//計(jì)算兩個(gè)六邊形相交之后,形成得六邊形環(huán),分割為6個(gè)等腰梯形,用以檢測(cè)點(diǎn)擊事件
????????????trapezoids?=?CalculateTrapezoids(big,?small);
????????????//計(jì)算內(nèi)部小的正六邊形,用以檢測(cè)點(diǎn)擊事件
????????????centerHexagon?=?CalculateHexagonVertices(sideWidth?/?2?-?20,?offset);
????????}
????????///?<summary>
????????///?鼠標(biāo)滑過(guò)的時(shí)候,重繪界面,然后設(shè)置鼠標(biāo)位置
????????///?</summary>
????????///?<param?name="e"></param>
????????protected?override?void?OnMouseMove(MouseEventArgs?e)
????????{
????????????base.OnMouseMove(e);
????????????mouseHoverLocation?=?e.Location;
????????????Invalidate();
????????}
????????///?<summary>
????????///?鼠標(biāo)移除
????????///?</summary>
????????///?<param?name="e"></param>
????????protected?override?void?OnMouseLeave(EventArgs?e)
????????{
????????????base.OnMouseLeave(e);
????????????mouseHoverLocation?=?null;
????????}
????????///?<summary>
????????///?菜單點(diǎn)擊事件
????????///?</summary>
????????///?<param?name="e"></param>
????????protected?override?void?OnMouseClick(MouseEventArgs?e)
????????{
????????????var?mouseLocation?=?e.Location;
????????????//檢測(cè)鼠標(biāo)是否在中間的六邊形中:
????????????if?(IsPointInPolygon(mouseLocation,?centerHexagon))
????????????{
????????????????OnMenuClicked?.Invoke(this,?-1);
????????????????return;
????????????}
????????????//檢測(cè)是否在某個(gè)梯形內(nèi)部
????????????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
????????????//以下的代碼可以直接刪除,這里是作為標(biāo)識(shí)多邊形位置
????????????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;
????????????}
????????????//檢測(cè)鼠標(biāo)是否在中間的六邊形中:
????????????if?(IsPointInPolygon(mouseHoverLocation.Value,?centerHexagon))
????????????{
????????????????g.FillPolygon(mouseHoverLayerBrush,?centerHexagon);
????????????????return;
????????????}
????????????//檢測(cè)是否在某個(gè)梯形內(nèi)部
????????????for?(int?i?=?0;?i?<?trapezoids.Length;?i++)
????????????{
????????????????var?trapezoid?=?trapezoids[i];
????????????????if?(IsPointInTrapezoid(mouseHoverLocation.Value,?trapezoid))
????????????????{
????????????????????g.FillPolygon(mouseHoverLayerBrush,?trapezoid.Points);
????????????????????return;
????????????????}
????????????}
????????}
????????///?<summary>
????????///?計(jì)算正六邊形的頂點(diǎn)坐標(biāo)
????????///?</summary>
????????///?<param?name="sideLength"></param>
????????///?<param?name="offset"></param>
????????///?<returns></returns>
????????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;
????????}
????????///?<summary>
????????///?計(jì)算每個(gè)梯形的坐標(biāo)
????????///?</summary>
????????///?<param?name="hexagonVertices"></param>
????????///?<param?name="smallHexagonVertices"></param>
????????///?<returns></returns>
????????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;
????????}
????????///?<summary>
????????///?判斷點(diǎn)是否在梯形內(nèi)
????????///?</summary>
????????///?<param?name="checkPoint"></param>
????????///?<param?name="trapezoid"></param>
????????///?<returns></returns>
????????private?static?bool?IsPointInTrapezoid(Point?checkPoint,?Trapezoid?trapezoid)
????????{
????????????return?IsPointInPolygon(checkPoint,?trapezoid.Points);
????????}
????????///?<summary>
????????///?判斷點(diǎn)是否在多邊形內(nèi).
????????///?來(lái)源:https://blog.csdn.net/xxdddail/article/details/49093635
????????///?----------原理----------
????????///?注意到如果從P作水平向左的射線(xiàn)的話(huà),如果P在多邊形內(nèi)部,那么這條射線(xiàn)與多邊形的交點(diǎn)必為奇數(shù),
????????///?如果P在多邊形外部,則交點(diǎn)個(gè)數(shù)必為偶數(shù)(0也在內(nèi))。
????????///?</summary>
????????///?<param?name="checkPoint">要判斷的點(diǎn)</param>
????????///?<param?name="polygonPoints">多邊形的頂點(diǎn)</param>
????????///?<returns></returns>
????????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++)//第一個(gè)點(diǎn)和最后一個(gè)點(diǎn)作為第一條線(xiàn),之后是第一個(gè)點(diǎn)和第二個(gè)點(diǎn)作為第二條線(xiàn),之后是第二個(gè)點(diǎn)與第三個(gè)點(diǎn),第三個(gè)點(diǎn)與第四個(gè)點(diǎn)...
????????????{
????????????????p1?=?polygonPoints[i];
????????????????p2?=?polygonPoints[j];
????????????????if?(checkPoint.Y?<?p2.Y)
????????????????{//p2在射線(xiàn)之上
????????????????????if?(p1.Y?<=?checkPoint.Y)
????????????????????{//p1正好在射線(xiàn)中或者射線(xiàn)下方
????????????????????????if?((checkPoint.Y?-?p1.Y)?*?(p2.X?-?p1.X)?>?(checkPoint.X?-?p1.X)?*?(p2.Y?-?p1.Y))//斜率判斷,在P1和P2之間且在P1P2右側(cè)
????????????????????????{
????????????????????????????//射線(xiàn)與多邊形交點(diǎn)為奇數(shù)時(shí)則在多邊形之內(nèi),若為偶數(shù)個(gè)交點(diǎn)時(shí)則在多邊形之外。
????????????????????????????//由于inside初始值為false,即交點(diǎn)數(shù)為零。所以當(dāng)有第一個(gè)交點(diǎn)時(shí),則必為奇數(shù),則在內(nèi)部,此時(shí)為inside=(!inside)
????????????????????????????//所以當(dāng)有第二個(gè)交點(diǎn)時(shí),則必為偶數(shù),則在外部,此時(shí)為inside=(!inside)
????????????????????????????inside?=?(!inside);
????????????????????????}
????????????????????}
????????????????}
????????????????else?if?(checkPoint.Y?<?p1.Y)
????????????????{
????????????????????//p2正好在射線(xiàn)中或者在射線(xiàn)下方,p1在射線(xiàn)上
????????????????????if?((checkPoint.Y?-?p1.Y)?*?(p2.X?-?p1.X)?<?(checkPoint.X?-?p1.X)?*?(p2.Y?-?p1.Y))//斜率判斷,在P1和P2之間且在P1P2右側(cè)
????????????????????{
????????????????????????inside?=?(!inside);
????????????????????}
????????????????}
????????????}
????????????return?inside;
????????}
????????///?<summary>
????????///?當(dāng)窗體改變時(shí),自動(dòng)計(jì)算大小
????????///?</summary>
????????///?<param?name="e"></param>
????????protected?override?void?OnClientSizeChanged(EventArgs?e)
????????{
????????????base.OnClientSizeChanged(e);
????????????InitHexagonMenus();
????????}
????}
????///?<summary>
????///?梯形類(lèi)
????///?</summary>
????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}?}}";
????????}
????}
}窗體代碼:
using?System;
using?System.Collections.Generic;
using?System.ComponentModel;
using?System.Data;
using?System.Drawing;
using?System.Linq;
using?System.Text;
using?System.Windows.Forms;
namespace?HexagonButton
{
????public?partial?class?Form1?:?Form
????{
????????private?HButton?HButton;
????????public?Form1()
????????{
????????????InitializeComponent();
????????????this.WindowState?=?FormWindowState.Maximized;
????????????//這個(gè)窗體里面把那個(gè)純色的背景圖放進(jìn)去
????????????this.BackColor?=?Color.FromArgb(9,?56,?128);
????????}
????????protected?override?void?OnSizeChanged(EventArgs?e)
????????{
????????????base.OnSizeChanged(e);
????????????if?(HButton?==?null)
????????????{
????????????????HButton?=?new?HButton();
????????????????HButton.OnMenuClicked?+=?(s,?index)?=>
????????????????{
????????????????????MessageBox.Show($"點(diǎn)擊了菜單:#{index}");
????????????????};
????????????????this.Controls.Add(HButton);
????????????}
????????????else
????????????{
????????????????HButton.ResetSizeByForm(this.Width,?this.Height);
????????????}
????????}
????}
}就是在窗體縮放時(shí),把六邊形菜單動(dòng)態(tài)加進(jìn)去。
結(jié)局
代碼發(fā)給網(wǎng)友之后,網(wǎng)友表示很滿(mǎn)意,然后經(jīng)過(guò)我的指導(dǎo),然后自由發(fā)揮改成了7邊形,然后我又?jǐn)U展了下面這一版,更加智能了。截一些圖片在這里:


最終版演示 DEMO:

做著做著,就一發(fā)不可收拾了,做的越來(lái)越順眼了... 最后,該菜單通過(guò)增加了一些配置,使得可以自定義邊數(shù),是否包含中間菜單等功能,功能更加強(qiáng)大了一些。該項(xiàng)目已全部開(kāi)源,開(kāi)源地址:https://github.com/mrhuo/polymenuNUGET 安裝:
Install-Package MrHuo.PolyMenu -Version 1.0.23.913
以上就是C# WinForm編寫(xiě)一個(gè)六邊形菜單的詳細(xì)內(nèi)容,更多關(guān)于C# WinForm菜單的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#中IsNullOrEmpty和IsNullOrWhiteSpace的使用方法及區(qū)別解析
今天我們將探討C#中兩個(gè)常用的字符串處理方法:IsNullOrEmpty和IsNullOrWhiteSpace,本文中,我們將詳細(xì)解釋這兩個(gè)方法的功能和使用場(chǎng)景,并幫助您更好地理解它們之間的區(qū)別,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-07-07
DevExpress SplitContainerControl用法總結(jié)
這篇文章主要介紹了DevExpress SplitContainerControl用法,對(duì)初學(xué)者有一定的參考借鑒價(jià)值,需要的朋友可以參考下2014-08-08
Unity UGUI的Toggle復(fù)選框組件使用詳解
這篇文章主要為大家介紹了Unity UGUI的Toggle復(fù)選框組件使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
WPF實(shí)現(xiàn)手風(fēng)琴式輪播圖切換效果
這篇文章主要為大家詳細(xì)介紹了WPF實(shí)現(xiàn)手風(fēng)琴式輪播圖切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09
C#如何實(shí)現(xiàn)監(jiān)控手機(jī)屏幕(附源碼下載)
這篇文章主要介紹了C#如何實(shí)現(xiàn)監(jiān)控手機(jī)屏幕(附源碼下載),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
C#學(xué)習(xí)筆記- 隨機(jī)函數(shù)Random()的用法詳解
下面小編就為大家?guī)?lái)一篇C#學(xué)習(xí)筆記- 隨機(jī)函數(shù)Random()的用法詳解。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08
Unity接入高德開(kāi)放API實(shí)現(xiàn)IP定位
這篇文章主要為大家介紹了Unity如何接入高德開(kāi)放API實(shí)現(xiàn)IP定位功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定參考價(jià)值,需要的可以參考一下2022-04-04
C# 將學(xué)生列表轉(zhuǎn)換為字典的實(shí)現(xiàn)
在開(kāi)發(fā)應(yīng)用程序時(shí),管理和處理數(shù)據(jù)結(jié)構(gòu)是非常重要的一環(huán),本文就來(lái)介紹一下C# 將學(xué)生列表轉(zhuǎn)換為字典的實(shí)現(xiàn),感興趣的可以了解一下2025-01-01

