65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.83/5 (5投票s)

2005年3月22日

6分钟阅读

viewsIcon

34756

downloadIcon

119

dPad 控件还具有颜色渐变和自定义事件。

Sample Image - dPadControl.gif

引言

本文介绍了一个具有自动重复功能的 dPad(方向键)控件。其他功能包括颜色渐变和自定义事件。与我在 Code Project 上发布的其他控件一样,dPad 使用双缓冲来实现平滑的绘制效果。

在考虑如何设计这个控件时,我最初的想法是创建一个复合控件,将四个方向按钮作为单独的控件。我放弃了这种方法,因为我希望中心的“X”形充当控件的对角线执行器。从绘制控件的角度来看,将五个元素合并为一个最终证明更容易。

背景

控件的命中检测逻辑基于控件由五个主要区域组成:四个按钮(上、下、左、右)以及中心的“X”区域。第六个区域在控件的中心提供了一个菱形“死区”。它的目的是忽略控件中心区域的单击。此功能由 IgnoreDiamondHits 属性的值控制。中心菱形的显示或隐藏取决于 ShowDiamond 属性的值。

为该控件定义了两个自定义事件:ButtonDownButtonUp。自动重复功能允许控件在按下控件上的按钮时引发多个 ButtonDown 事件。此功能由 Repeat 属性和 RepeatRate 属性控制。当 RepeatFalse 时,在鼠标按下控件时会引发一个 ButtonDown 事件,在鼠标释放时会引发一个 ButtonUp 事件。当 RepeatTrue 时,在鼠标按下并按住控件时会引发一系列 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

整个控件都使用了渐变画笔。如果您希望按钮(例如)是纯色的,则必须为相应的属性选择相同的颜色,例如 ButtonColorButtonBlendColor

关注点

在创建此控件之前,我有一些 .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 属性(RoundObRound)添加一个形状。此路径用于创建第一个区域 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 日 - 首次发布。
© . All rights reserved.