Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A dPad (direction pad) control with auto-repeat

0.00/5 (No votes)
22 Mar 2005 1  
The dPad control also features color gradients and custom events.

Sample Image - dPadControl.gif

Introduction

This article presents a dPad (direction pad) control with auto-repeat. Other features include color gradients and custom events. As with my other controls appearing on Code Project, dPad uses double buffering to produce smooth painting effects.

While considering how to design this control, I initially thought of making a composite control with each of the four direction buttons as separate controls. I abandoned this approach because I wanted the center 'X' shape to act as the diagonal actuators for the control. It ultimately proved easier from the standpoint of drawing the control to simply combine the five elements into one.

Background

The logic behind the hit detection for the control is based on the control being composed of five main regions, the four buttons (up, down, left and right) plus the center 'X' region. A sixth region provides a diamond shaped 'dead zone' in the very center of the control. It's purpose is to ignore clicks in the central area of the control. This feature is controlled by the value of the IgnoreDiamondHits property. The center diamond shape can be shown or hidden based on the value of the ShowDiamond property.

Two custom events are defined for the control, ButtonDown and ButtonUp. The auto repeat feature allows the control to raise multiple ButtonDown events while a button on the control is held down. This feature is controlled by the Repeat property along with the RepeatRate property. When Repeat is False, there is one ButtonDown event raised when the mouse is pressed on the control, and a ButtonUp event raised when the mouse is released. When Repeat is True, a series of ButtonDown events are raised when the mouse is pressed and held on the control, at a rate set by RepeatRate, and finally one ButtonUp event raised when the mouse is released.

The ButtonDown event has one parameter, a value from the Buttons enumeration to indicate which button or buttons are being pressed. The Buttons enumeration is declared with a FlagsAttribute attribute, so that values can be combined by OR-ing them together.

Using the code

After downloading and building the solution, you can copy the dPadControl.dll to a convenient location and add it to the Toolbox in Visual Studio. Then add an instance of the control to your project.

The ButtonDown event is the default event for the dPad control. Double-clicking the control in the Design view will stub the following code into the Code view:

   Private Sub DPad1_ButtonDown(ByVal btn As dPadControl.dPad.Buttons) _
   Handles DPad1.ButtonDown
   End Sub

In Code view, selecting DPad1 from the Class Name dropdown and then selecting ButtonUp from the Method Name dropdown will stub in the following:

   Private Sub DPad1_ButtonUp() Handles DPad1.ButtonUp
   End Sub

Gradient type brushes are used throughout. If you want the buttons, for example, to be of a solid color, you must select the same color for the corresponding properties, e.g. ButtonColor and ButtonBlendColor.

Points of Interest

Before creating this control, I had some experience with .NET's GDI+, but I had never used any matrix operations. This proved to be quite a learning experience! One consideration was to try to do as much of the work as possible as few times as possible. To this end, I placed the heavier work of creating the regions used to draw the control and do the hit testing in the overridden OnResize Sub. Of course, this meant that the regions needed to be declared at the class level and persisted throughout the lifetime of the control, but this seemed a fair tradeoff to the alternative of calculating them each time the control was painted. Here is the code for the 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

The first effective line calls the overridden SetClientSizeCore method. This has the effect of forcing the control to remain square. The next lines create a RectangleF structure from the control's ClientRectangle and reduce the width and height by 1. This accounts for the fact that the last point in the width or height is numbered one less than the given width or height, i.e. a width of 100 contains points numbered from 0 to 99.

Next, the gp GraphicsPath has a shape added to it based on the shape of the dPad control from the Shape property, Round or ObRound. This path is used to create the first region, rn (or region north). The ga GraphicsPath is cloned from this path and forms the basis for the region which defines the entire control.

Next follows a series of three transforms, each starting by cloning the gp path to a temporary gt GraphicsPath. After each transform, the next region in turn is created from the transformed path (regions east, south and west), and the transformed path is also added to ga.

Prior to the first transform and following each transform, a set of three points are added to the pmid ArrayList via a call to the AddPoints Sub. The final call to AddPoints with the copy parameter set to True results in all 12 points stored in pmid being copied to array mArray, and four of the stored points being copied to array cArray.

Finally, gt is set to the path contained in mArray and region rx (the center 'X' shape) is created from this path. This path shape is also added to ga and the control's entire region is set to path ga. The last step is to create region rd (the center diamond shape) from the path in cArray. The control is then invalidated to cause a repaint.

Events, Event Handlers and NDoc

The easiest way to add your own event/event handler is to add a line like the following:

   ''' -------------------------------------------------------------------

   ''' <summary>

   ''' A dummy event declaration

   ''' </summary>

   ''' <remarks>

   ''' Generic remarks

   ''' </remarks>

   ''' -------------------------------------------------------------------

   Public Event MyEvnt()

An event named MyEvnt has been declared with no parameters. XML style comments have been added above the declaration (I used VBCommenter). Now if you fire up NDoc and compile this code, you will see that your help file claims to be missing documentation for MyEvntEventHandler. What is happening?

Well, while you weren't looking, Visual Basic decided to help you out. You wrote one line of code but internally Visual Basic actually wrote two lines to replace yours!

   Public MyEvnt As MyEvntEventHandler
   Delegate Sub MyEvntEventHandler()

Note that in the code editor window, you still see only your original line, but by using Reflection, you will see it transformed into two lines. Well this is exactly how NDoc operates, using Reflection to get at the representation of the code you've written. So if you plan on using NDoc to create a Help file from your project, declare your event handler with the two lines and add appropriate comments. That way your Help file will contain the correct information for both the events and event delegates.

Conclusion

I honestly don't know if anyone will find a good use of this control, but I hope at least that some insight might be gleaned from the code presented here. If you have any questions or suggestions for improving the code, please leave a comment below. Thank you.

History

  • Mar 22nd,2005 - Initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here