0

I have a custom control's library. Now there's a control which looks like a panel, and when it opens up I want to animate its vertical growing like this:

For h As Single = 0 To finalHeight Step 0.5
    Me.Height = CInt(h)
    '  HERE I WANT TO CALL DoEvents'
Next
Me.Height = finalHeight 

If I don't call DoEvents in the loop then the animation is not shown, I only get the final height without a visual feedback along the way.

I can call DoEvents from inside my main WinForm project, but can't inside a library.

How can I do that, without drowning into the deep threads waters?

Ben McCormack
  • 32,086
  • 48
  • 148
  • 223
vulkanino
  • 9,074
  • 7
  • 44
  • 71

6 Answers6

3

Sorry, but it is completely impossible to make using DoEvents safe here. Nothing good is going to happen when the user closes the form while your animation is going. It will crash the program with an ObjectDisposed exception. Making DoEvents safe requires setting the form's Enabled property to false so that the user cannot accidentally cause mishaps like this. A control can not reasonable set the form's Enabled property to false, especially not for an animation.

The workaround is simple enough, just use a Timer with an Interval of 15 msec. Plenty fast enough to make the animation look smooth. You'll find sample code that does this in my answer in this thread.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • That's not true. While the animations runs, the user can't close the form. User input is blocked. The timer works but it is very slow. – vulkanino Sep 21 '10 at 13:43
  • 2
    Erm, it is blocked now, yes. But not when you start calling DoEvents. – Hans Passant Sep 21 '10 at 13:50
  • 2
    Oh, and 15 msec is *not* slow. That's 60 updates per second, well over twice as fast as what you see when you watch a movie. It looks slow because your increment of 0.5 pixel is way too small. Make it 5. – Hans Passant Sep 21 '10 at 14:03
2

Maybe you are just missing a reference to (or import of) System.Windows.Forms? DoEvents is a static method of Application, so you should be able to call it from a library as well.

Imports System.Windows.Forms

...

    Application.DoEvents()

(You already seem to know that using DoEvents is a dangerous thing, so I'll skip the usual lecture here.)

Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • @vulkanino: And the *same code* works when it's located in the WinForm project? – Heinzi Sep 21 '10 at 12:18
  • @vulkanino: Are you sure? Exactly the same UserControl .vb file, the only difference being that it's either located in the library or in the main project? – Heinzi Sep 21 '10 at 13:36
2

Yes, you should be able to call

System.Windows.Forms.Application.DoEvents()

From within your code library. It seems that you understand that DoEvents is a Bad Idea, so I'm not sure why you're calling it. I'm guessing that you have this put inside an override like OnVisibleChanged, or OnPaint - if this is the case you will most likely not get the results you are after, as control refreshing will be suspended during these operations.

What you probably want to do is create a single-tick timer, and on tick increase the height of the control -then disable the timer when finalheight is reached, or schedule another tick if not. Or, create a timer and put your above loop in it on each tick. Make sure you're aware of InvokeRequired and cross-thread calls depending on what type of timer you use.

Philip Rieck
  • 32,368
  • 11
  • 87
  • 99
1

Failure to reproduce.

Just tested with a simple setup, UserControl in an assembly.

A timer on the MainForm keeps ticking when the UC calls DoEvents() in a loop.

So: Look again for your problem, it isn't where you think it is.

H H
  • 263,252
  • 30
  • 330
  • 514
  • THe timer ticks, but the animation is really slow with the it. That's why I decided to use a loop and DoEvents. – vulkanino Sep 21 '10 at 13:42
  • 1
    I used a Timer on the mainform to show that DoEvents in the lib works. But DoWvents is still a really bad idea. – H H Sep 21 '10 at 13:55
0

In your original for loop, place Me.Refresh where you wanted to call the doevents.

For h As Single = 0 To finalHeight Step 0.5
    Me.Height = CInt(h)
    Me.refresh
Next
Me.Height = finalHeight
Toon Krijthe
  • 52,876
  • 38
  • 145
  • 202
Skip
  • 1
-1

This is what I've found: the timer, even at fast intervals, it is really slow. I don't know why but the animation is very jumpy with the timer. Simplified code:

 rolex = New Timer()
 rolex.Interval = 150
 AddHandler rolex.Tick,
            Sub(sender As Object, e As EventArgs)

                Me.Height += 5

                If Me.Height < finalHeight Then Exit Sub

                rolex.Stop()
                rolex = Nothing

                Me.Height = finalHeight 
            End Sub
 rolex.Start()

Without the timer I use a loop:

For i As Single = 0 To finalHeight Step 0.5
            Height = CInt(i)
            Application.DoEvents()
Next
Height = finalHeight 

It works now, but the problem is that the animation speed too much depends on the machine where the loop is executed. For this reason I'd like to use a Timer, but as I said it's too slow.

Any hints?

vulkanino
  • 9,074
  • 7
  • 44
  • 71