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

Rubberband Lines

0.00/5 (No votes)
2 Nov 2005 1  
Using the rubberband line as a ruler.

Sample Image - Rubberbands.jpg

Introduction

This article explains the process I went through to create a small class used to create a "Rubberband" style ruler that can be used in any project. It uses a technique in GDI+ that replaces the ControlPaint.DrawReversibleLine which has many issues which I won't go into here. Please note that this code was written using VS2005 but there is no reason it wouldn't work in earlier versions, just make sure you change the Using commands.

Background

I was trying to create an application that required I draw a floor plan, and this lead to an issue of how to display to the user the length of the wall to be drawn. I initially started with the well known and documented .NET method DrawReversibleLine in the ControlPaint class. But this lead to many issues, some of which were world coordinate correlation, painting issues (XOR drawing is flawed in many ways), color choices (there are none), and others, but they were the main ones. So I decided to write my own class.

Using the code

I started by keeping the reversible line code in MouseDown, MouseMove and MouseUp events, this can be found in many articles about drawing reversible lines, so I won't go into the specifics. In brief though, MouseDown stores the start point, MouseMove does the hard work in setting the current point and invalidating the control surface so that it will repaint, and then finally the MouseUp event fires the event to inform listeners that the two points have been defined.

There are a couple of tricks in this implementation of rubberband lines that step outside of the norm, and they are what I'd like to share.

The first is the addition of a text label in the middle of the line (rotating with the line as it moves) which displays the line's length. The second is the way in which the class only draws the sections of the control that it has changed.

Text Label (rotating text)

This was the hardest thing to get working but a few features in the GDI+ library made the task easier. The first thing to overcome was the length itself, but basic Trigonometry answered that:

' length^2 = width^2 + height^2


length = Math.Sqrt(rect.Width ^ 2 + rect.Height ^ 2)

Now all I had to do was to put that value onto the line rotated to follow the line, and to do that I needed the angle to rotate the text by. Again, Trig came to the rescue, giving me the following answer:

angle = Math.Atan((p1.Y - p2.Y) / (p1.X - p2.X)) * (180 / Math.PI)

Now having both the LineLength and the Angle functions, I put together the following section which I added to the Paint event (if the mouse was still pressed). The matrix is used to perform the rotation around the midpoint of the line, so that when the ellipse and text are drawn, they are actually drawn on a rotated graphics device. The string format is just used to ensure the text is centered on the line, this worked much better than trying to draw the text within the rectangle used to draw the ellipse. The purpose of the ellipse is to ensure that the text can always be easily read regardless of the background.

Using mx As New System.Drawing.Drawing2D.Matrix
    mx.Translate(midPoint.X, midPoint.Y)
    mx.Rotate(Angle(_origin, _last))
    e.Graphics.Transform = mx
    Using sf As New StringFormat()
        Dim ls As String = CInt(LineLength(_origin, _last))
        Dim l As SizeF = e.Graphics.MeasureString(ls, _
                         _parent.Font, _parent.ClientSize, sf)
        sf.LineAlignment = StringAlignment.Center
        sf.Alignment = StringAlignment.Center

        Dim rt As New Rectangle(0, 0, l.Width, l.Height)
        rt.Inflate(3, 3)
        rt.Offset(-(l.Width / 2), -(l.Height / 2))
        Using backBrush As New SolidBrush(_backColor)
            e.Graphics.FillEllipse(backBrush, rt)
        End Using
        Using foreBrush As New SolidBrush(_foreColor)
            e.Graphics.DrawString(ls, _parent.Font, foreBrush, 0, 0, sf)
        End Using
    End Using
End Using

Using the Class

To actually use the class is as easy as adding it to your project, then instantiating the class as needed. The following code uses the class in a form. (In the Dispose method, make sure to dispose off the class.) Note the styles that are set on the form to prevent flickering.

Public Class Form1
    Private WithEvents _ruler As MouseRuler

    Public Sub New()

        ' This call is required by the Windows Form Designer.

        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

        Me.SetStyle(ControlStyles.OptimizedDoubleBuffer Or _
                    ControlStyles.AllPaintingInWmPaint, True)

        _ruler = New MouseRuler(Form1)
    End Sub

    Private Sub _ruler_CaptureFinished(ByVal sender As Object, _
                ByVal e As CaptureEventArgs) Handles _ruler.CaptureFinished
        'todo:  Add wall between points in CaptureEventArgs

    End Sub
End Class

History

Changes made to date:

  • None.

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