Question How to draw rubberband line fast?

juhah

Active member
Joined
Apr 16, 2007
Messages
28
Programming Experience
3-5
Now, this is a very basic problem that has been bugged me in many projects and I haven't found an answer. How to draw a rubberband line to a picturebox or panel that follows the mouse in real-time, when it has a background image inserted to it? In VB6 there was the simple Line-method that did that perfectly. In VB.net the closest thing I've got is the DrawLine method in picturebox paint event (e.Graphics.DrawLine), and invalidating the picturebox in mousemove event clears the graphics. But when I have a large enough picturebox the line always stays a bit behind the actual mouse position (especially when moving the cursor fast).

I've tried basically every trick I know for drawing graphic to windows forms, nothing has worked. I can't use the e.Graphics.Clear method because it would replace the background image with a solid color.

Why can't I achieve this with my over 2.0Ghz PC when it was achieved years ago with a lot less effective computers?
 
Last edited:
The fact that invalidating the picture box clears the graphics suggests to me that you are using CreateGraphics instead of the Graphics object provided in the picturebox Paint handler. Don't. Drawing on WinForms controls such as pictureboxes or panels is never going to be as fast as DirectX, XNA, WPF etc. but it shouldn't be too hard to do a decent rubber band using Forms methods.

Here's a simple example. To try it, start a forms project, dock a picturebox (PictureBox1) onto the form (Form1), give it an image and set the picturebox SizeMode to Stretch or Zoom. You can change rubberBandColor to anything that will show up against your image. Code the form as follows, and run.

VB.NET:
Public Class Form1
    Private startCorner As Point
    Private rubberBand As Rectangle
    Private rubberBandColor As Color = Color.Yellow
    Private rubberBanding As Boolean = False

    Private Sub PictureBox1_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown
        If e.Button = Windows.Forms.MouseButtons.Left Then
            rubberBanding = True
            startCorner = e.Location
            rubberBand = Rectangle.Empty
            PictureBox1.Invalidate()
        End If
    End Sub

    Private Sub PictureBox1_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
        If rubberBanding Then         
            rubberBand.Width = Math.Abs(e.X - startCorner.X)
            rubberBand.Height = Math.Abs(e.Y - startCorner.Y)
            rubberBand.X = Math.Min(e.X, startCorner.X)
            rubberBand.Y = Math.Min(e.Y, startCorner.Y)
            PictureBox1.Invalidate()           
        End If
    End Sub

    Private Sub PictureBox1_MouseUp(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseUp
        rubberBanding = False
    End Sub

    Private Sub PictureBox1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
        Using pn As New Pen(rubberBandColor) With {.DashStyle = Drawing2D.DashStyle.Dash}
            e.Graphics.DrawRectangle(pn, rubberBand)
        End Using
    End Sub

End Class

This works satisfactorily on my less than spectacular twincore. See how well it works for you. The responsiveness can be improved a bit at large sizes by invalidating only the affected rectangles in the MouseMove event, instead of the whole picturebox. Here's an improved MouseMove example:

VB.NET:
Private Sub PictureBox1_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
        If rubberBanding Then
            [COLOR=#008000]'erase old rectangle:[/COLOR]
            Dim r As Rectangle = rubberBand
            r.Inflate(1, 1)
            PictureBox1.Invalidate(r)

            [COLOR=#008000]'change rubber band:[/COLOR]
            rubberBand.Width = Math.Abs(e.X - startCorner.X)
            rubberBand.Height = Math.Abs(e.Y - startCorner.Y)
            rubberBand.X = Math.Min(e.X, startCorner.X)
            rubberBand.Y = Math.Min(e.Y, startCorner.Y)
            
            [COLOR=#008000]'invalidate new rectangle:[/COLOR]
            r = rubberBand
            r.Inflate(1, 1)
            PictureBox1.Invalidate(r)
        End If
    End Sub


VicJ
 
Last edited:
Thanks for the tips! I still don't get perfect response, but I think it's the best WinForms can do. Invalidating the picturebox with narrowed area gives a small boost indeed, but not very noticable. However it's more than sufficient for the software our client has requested, so I think using DirectX or XNA or stuff like that would only increase unnecessary costs.
 
I'm glad it's usable. While I was working out the example, I found it only took a few lines of code to implement a rather pleasing "marching ants" effect which doesn't seem to affect the drawing speed.

Here's how I did it. I added a Timer to the form, with Interval=100ms. and Enabled = True. Then I added a variable dashOffset and coded the timer Tick event as follows:
VB.NET:
Private dashOffset As Integer
    Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
        dashOffset -= 2
        Dim r As Rectangle = rubberBand
        r.Inflate(1, 1)
        PictureBox1.Invalidate(r)
    End Sub
Then all I needed to do was set the pen's DashOffset property to dashOffset before drawing the rectangle. But I found it looked slightly better with a custom dash style instead of the standard one. Here's the revised Paint event code:
VB.NET:
    Private Sub PictureBox1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
        Using pn As New Pen(rubberBandColor) With { _
         .DashStyle = Drawing2D.DashStyle.Custom, _
         .DashPattern = {3.0F, 5.0F}, _
         .DashOffset = dashOffset}
            e.Graphics.DrawRectangle(pn, rubberBand)
        End Using
    End Sub

Maybe you could use it to distract your client's attention from the rubberband stretching lag;).

VicJ
 
Last edited:
Back
Top