带自动重复的 dPad(方向盘)控件






2.83/5 (5投票s)
2005年3月22日
6分钟阅读

34756

119
dPad 控件还具有颜色渐变和自定义事件。
引言
本文介绍了一个具有自动重复功能的 dPad
(方向键)控件。其他功能包括颜色渐变和自定义事件。与我在 Code Project 上发布的其他控件一样,dPad
使用双缓冲来实现平滑的绘制效果。
在考虑如何设计这个控件时,我最初的想法是创建一个复合控件,将四个方向按钮作为单独的控件。我放弃了这种方法,因为我希望中心的“X”形充当控件的对角线执行器。从绘制控件的角度来看,将五个元素合并为一个最终证明更容易。
背景
控件的命中检测逻辑基于控件由五个主要区域组成:四个按钮(上、下、左、右)以及中心的“X”区域。第六个区域在控件的中心提供了一个菱形“死区”。它的目的是忽略控件中心区域的单击。此功能由 IgnoreDiamondHits
属性的值控制。中心菱形的显示或隐藏取决于 ShowDiamond
属性的值。
为该控件定义了两个自定义事件:ButtonDown
和 ButtonUp
。自动重复功能允许控件在按下控件上的按钮时引发多个 ButtonDown
事件。此功能由 Repeat
属性和 RepeatRate
属性控制。当 Repeat
为 False
时,在鼠标按下控件时会引发一个 ButtonDown
事件,在鼠标释放时会引发一个 ButtonUp
事件。当 Repeat
为 True
时,在鼠标按下并按住控件时会引发一系列 ButtonDown
事件,其速率由 RepeatRate
设置,最后在鼠标释放时会引发一个 ButtonUp
事件。
ButtonDown
事件有一个参数,该参数来自 Buttons
枚举,用于指示正在按下哪个按钮或哪些按钮。Buttons
枚举使用 FlagsAttribute
属性进行声明,因此可以通过 OR 运算将值组合在一起。
使用代码
下载并构建解决方案后,您可以将 dPadControl.dll 复制到方便的位置,并将其添加到 Visual Studio 的工具箱中。然后将控件的一个实例添加到您的项目中。
ButtonDown
事件是 dPad
控件的默认事件。在设计视图中双击该控件会将以下代码存根到代码视图中。
Private Sub DPad1_ButtonDown(ByVal btn As dPadControl.dPad.Buttons) _
Handles DPad1.ButtonDown
End Sub
在代码视图中,从类名下拉列表中选择 DPad1
,然后从方法名下拉列表中选择 ButtonUp
,将会存根以下代码。
Private Sub DPad1_ButtonUp() Handles DPad1.ButtonUp
End Sub
整个控件都使用了渐变画笔。如果您希望按钮(例如)是纯色的,则必须为相应的属性选择相同的颜色,例如 ButtonColor
和 ButtonBlendColor
。
关注点
在创建此控件之前,我有一些 .NET 的 GDI+ 经验,但从未进行过任何矩阵运算。这证明是一次相当的学习经历!一个考虑因素是尽可能少地多次执行繁重的工作。为此,我将创建用于绘制控件和进行命中测试的区域的繁重工作放在重写的 OnResize
Sub
中。当然,这意味着需要将区域声明在类级别并在控件的整个生命周期中保留它们,但这似乎比每次绘制控件时都计算它们更划算。以下是 OnResize
Sub
的代码。
Protected Overrides Sub OnResize(ByVal e As EventArgs)
Dim gp As New GraphicsPath
Dim gt As New GraphicsPath
Dim ga As New GraphicsPath
Dim mtx As Matrix
Dim cRectf As RectangleF
Dim pmid As New ArrayList
SetClientSizeCore(ClientSize.Width, ClientSize.Height)
cRectf = [RectangleF].op_Implicit(ClientRectangle)
cRectf.Width -= 1.0F : cRectf.Height -= 1.0F
If mshape = Shapes.Round Then
gp.AddArc(0, 0, cRectf.Width, cRectf.Height, 228.0F, 84.0F)
gp.AddLine(gp.GetLastPoint(), _
New PointF(cRectf.Width / 2.0F, cRectf.Height * 0.375F))
Else
gp.AddPie(0, 0, cRectf.Width, cRectf.Height * 0.75F, 216.87F, 106.26F)
End If
AddPoints(gp.PathPoints, pmid)
rn = New Region(gp)
ga = gp.Clone
' 90 deg
mtx = New Matrix(0, 1, -1, 0, cRectf.Width, 0)
gt = gp.Clone
gt.Transform(mtx)
re = New Region(gt)
ga.AddPath(gt, False)
AddPoints(gt.PathPoints, pmid)
' 180 deg
mtx = New Matrix(-1, 0, 0, -1, cRectf.Width, cRectf.Height)
gt = gp.Clone
gt.Transform(mtx)
rs = New Region(gt)
ga.AddPath(gt, False)
AddPoints(gt.PathPoints, pmid)
' 270 deg
mtx = New Matrix(0, -1, 1, 0, 0, cRectf.Height)
gt = gp.Clone
gt.Transform(mtx)
rw = New Region(gt)
ga.AddPath(gt, False)
AddPoints(gt.PathPoints, pmid, True)
gt.Reset()
gt.AddPolygon(mArray)
rx = New Region(gt)
ga.AddPath(gt, False)
Me.Region = New Region(ga)
gt.Reset()
gt.AddPolygon(cArray)
rd = New Region(gt)
pmid.Clear() : mtx.Dispose()
gt.Dispose() : gp.Dispose() : ga.Dispose()
Me.Invalidate()
End Sub
Private Sub AddPoints(ByVal pa As PointF(), ByRef mi As ArrayList, _
Optional ByVal copy As Boolean = False)
With mi
If mshape = Shapes.Round Then
.Add(pa(0)) : .Add(pa(4)) : .Add(pa(3))
Else
.Add(pa(1)) : .Add(pa(0)) : .Add(pa(4))
End If
If copy Then
.CopyTo(mArray)
Dim m() As Integer = {1, 4, 7, 10}
For n As Integer = 0 To 3
cArray(n) = mArray(m(n))
Next
End If
End With
End Sub
Protected Overrides Sub SetClientSizeCore(ByVal x As Integer, _
ByVal y As Integer)
If x > y Then
MyBase.SetClientSizeCore(x, x)
Else
MyBase.SetClientSizeCore(y, y)
End If
End Sub
第一个有效行调用重写的 SetClientSizeCore
方法。这会强制控件保持正方形。接下来的几行从控件的 ClientRectangle 创建一个 RectangleF
结构,并将宽度和高度减少 1。这考虑了宽度或高度的最后一个点编号小于给定宽度或高度的事实,即宽度为 100 包含编号从 0 到 99 的点。
接下来,gp
GraphicsPath
基于 dPad
控件的 Shape
属性(Round
或 ObRound
)添加一个形状。此路径用于创建第一个区域 rn
(或区域北)。ga
GraphicsPath
从此路径克隆而来,并构成定义整个控件的区域的基础。
接下来是一系列三个变换,每个变换都通过将 gp
路径克隆到临时 gt
GraphicsPath
来开始。每次变换后,下一个区域都从变换后的路径(区域东、南、西)创建,变换后的路径也被添加到 ga
中。
在第一次变换之前以及每次变换之后,通过调用 AddPoints
Sub
,将一组三个点添加到 pmid
ArrayList
中。最后一次调用 AddPoints
时,将 copy 参数设置为 True
,结果是 pmid
中存储的所有 12 个点被复制到数组 mArray
,并且存储的四个点被复制到数组 cArray
。
最后,将 gt
设置为 mArray
中包含的路径,并从该路径创建区域 rx
(中心的“X”形)。此路径形状也添加到 ga
中,并将控件的整个区域设置为路径 ga
。最后一步是从 cArray
中的路径创建区域 rd
(中心的菱形)。然后使控件失效以触发重绘。
事件、事件处理程序和 NDoc
添加自己的事件/事件处理程序的最简单方法是添加如下一行:
''' -------------------------------------------------------------------
''' <summary>
''' A dummy event declaration
''' </summary>
''' <remarks>
''' Generic remarks
''' </remarks>
''' -------------------------------------------------------------------
Public Event MyEvnt()
声明了一个名为 MyEvnt
的事件,没有参数。在声明上方添加了 XML 风格的注释(我使用了 VBCommenter)。现在,如果您启动 NDoc 并编译此代码,您将看到您的帮助文件声称缺少 MyEvntEventHandler
的文档。发生了什么?
嗯,当您不注意时,Visual Basic 决定帮助您。您写了一行代码,但 Visual Basic 实际上在内部写了两行来替换您的代码!
Public MyEvnt As MyEvntEventHandler
Delegate Sub MyEvntEventHandler()
请注意,在代码编辑器窗口中,您仍然只看到原始的一行代码,但通过使用反射,您将看到它被转换为两行。这正是 NDoc 的工作方式,它使用反射来获取您编写的代码的表示形式。因此,如果您打算使用 NDoc 为您的项目创建帮助文件,请用两行代码声明您的事件处理程序并添加适当的注释。这样,您的 帮助 文件将包含有关事件和事件委托的正确信息。
结论
我真的不知道是否会有人能很好地使用这个控件,但我至少希望从这里提供的代码中能获得一些见解。如果您有任何关于改进代码的问题或建议,请在下方留言。谢谢。
历史
- 2005 年 3 月 22 日 - 首次发布。