dcsimg
Results 1 to 5 of 5
  1. #1

    Thread Starter
    New Member
    Join Date
    Jan 2018
    Posts
    11

    Is there a faster way to draw lines and arcs than DrawLing and DrawArc?

    I have a pretty basic program that creates a dial, and spins a line to the corresponding angle. The main problem is when the input is changed rapidly the line and arcs will flash as it seems they can't create the lines fast enough. Is there a faster way to create it so it smooths out, or will I need to add an image of the circle and line, and rotate the line based on the input?

  2. #2
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    98,647

    Re: Is there a faster way to draw lines and arcs than DrawLing and DrawArc?

    The drawing code is not the slow part. It's the painting of pixels that really slows things down. If you're painting everything every time then that will slow things down. Are you invalidating the minimum area each time, such that as few pixels as possible get repainted?

    You should also try to draw on a PictureBox if you can, which is optimised for graphics out of the box. It's possible that double-buffering the form can help too.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  3. #3
    eXtreme Programmer .paul.'s Avatar
    Join Date
    May 2007
    Location
    Chelmsford UK
    Posts
    21,674

    Re: Is there a faster way to draw lines and arcs than DrawLing and DrawArc?

    Quote Originally Posted by zuccj View Post
    I have a pretty basic program that creates a dial, and spins a line to the corresponding angle. The main problem is when the input is changed rapidly the line and arcs will flash as it seems they can't create the lines fast enough. Is there a faster way to create it so it smooths out, or will I need to add an image of the circle and line, and rotate the line based on the input?
    If you mean that it's a gauge, with the only changing part being the pointer line, it's best to save an image of the gauge without the line, then in your drawing use drawimage for the image, and then draw the dynamic line part on top of that... Less painting, less flicker...

  4. #4
    eXtreme Programmer .paul.'s Avatar
    Join Date
    May 2007
    Location
    Chelmsford UK
    Posts
    21,674

    Re: Is there a faster way to draw lines and arcs than DrawLing and DrawArc?

    Also DoubleBuffered = true will greatly improve the graphics performance...

  5. #5
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Is there a faster way to draw lines and arcs than DrawLing and DrawArc?

    When you get flicker like that, you have two possible tools.

    The first is to make sure your Form and its controls are double buffered. Controls have a DoubleBuffered property, which is the easier of two ways to enable it. That causes Windows to draw on a bitmap that's not on the screen, then draw that whole bitmap to the screen. That tends to flicker less than drawing directly to the screen.

    The second is to make sure you don't try to repaint "too fast". The definition of "too fast" varies by system and what you're doing, but trying to draw much faster than 30 frames per second in most WinForms app is wasteful. This can be tricky depending on why you end up repainting, so let's cover some cases.

    If you're using CreateGraphics(), that is the problem. Stop. There is never a reason to use CreateGraphics() in .NET. Ever. The only way to use it appropriately involves doing the thing I'll suggest anyway, so why do twice the work?

    The correct thing looks something like this in a mouse move handler;
    Code:
    Sub WhenMouseMoves(...)
        Dim mousePosition = <get the mouse position>
        Dim newValue = <get the value from the mouse position>
        Me.Value = newValue
        Invalidate()
    End Sub
    Invalidate() tells Windows that you've changed something about your control and require a redraw. When Windows feels it can respond, it'll create a Graphics object and raise the control's Paint event. Windows can collapse many of these such that 10 Invalidates might only cause 1 Paint event, but it only does this if it is under stress. Generally, if you Invalidate(), you expect a Paint to happen fairly soon after. (It's all on the same thread so it's not immediate.)

    So if you're firing off a ton of these, you get flicker. The natural solution is "slow down the rate at which you invalidate". This is sort of tricky, though. Normally when you do this (called "throttling"), you decide whether to invalidate based on how long it's been since the last one. But in this case, we HAVE to make sure that we eventually respond to the invalidation, because you don't want to display an "old" value. So we need an algorithm like this:
    Code:
    Let timeDelta be the duration since the last Invalidate() call.
    If timeDelta > someThreshold:
        Invalidate()
    Else
        If there are no pending invalidates, schedule an Invalidate() _
            for "duration" in the future.
    End If
    In modern VB, this might look like:
    Code:
    Private _invalidateWatcher As Stopwatch = Stopwatch.StartNew()
    Private _pendingInvalidation As Boolean = False
    
    Async Sub WhenMouseMoves(...)
        Dim mousePosition = <get the mouse position>
        Dim newValue = <get the value from the mouse position>
        Me.Value = newValue
    
        If _invalidateWatcher.ElapsedMilliseconds > 100 Then
            Invalidate()
            _invalidateWatcher.Restart()
        Else If Not _pendingInvalidation
            _pendingInvalidation = True
            Await Task.Delay(100)
            Invalidate()
            _invalidateWatcher.Restart()
            _pendingInvalidation = False
        End If
    End Sub
    If you are afraid of Tasks and Await, you have to write a little more code, a timer becomes useful.
    Code:
    Private _invalidateWatcher As Stopwatch = Stopwatch.StartNew()
    Private _pendingInvalidation As Boolean = False
    
    Sub WhenMouseMoves(...)
        Dim mousePosition = <get the mouse position>
        Dim newValue = <get the value from the mouse position>
        Me.Value = newValue
    
        If _invalidateWatcher.ElapsedMilliseconds > 100 Then
            Invalidate()
            _invalidateWatcher.Restart()
        Else If Not _pendingInvalidation
            _pendingInvalidation = True
            _invalidationTimer.Start()
        End If
    End Sub
    
    Sub WhenInvalidationTimerElapses()
        _invalidationTimer.Stop()
        Invalidate()
        _pendingInvalidation = False
        _invalidateWatcher.Restart()
    End Sub
    Either of these examples make sure you call Invalidate() at MOST 10 times per second, and guarantees you'll always draw the last known value.

    Try DoubleBuffered first.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width