If your Silverlight application performs intensive updates to the UI during mouse events, the UI can freeze because it is invalidated before it has a chance to render. This post describes a technique for 'throttling' mouse events to ensure that each time an event occurs, the UI has the opportunity to render.
Introduction - the problem
The visual tree of a Silverlight / WPF uses the retained mode graphics rendering style, where the tree forms a model of the graphics which are rendered to the screen. Any changes that are made to the visual tree by adding / removing objects or changing the properties of an object within the tree, are automatically rendered to your computer screen at some point in the future.
This graphics style makes the development of complex graphics far easier than with the immediate mode counterpart. You add objects, make changes to them, and these changes are reflect on the screen by some background magic. This works just fine most of the time, however, if you continually make changes to the visual tree, without yielding, the 'process' which updates the UI does not have the opportunity to run, and your UI is starved.
I have found that this problem occurs most often when handling mouse events, particularly MouseMove and MouseWheel. Both of these events have the opportunity to fire very rapidly. If, when handling one of these events you make some change to the visual tree, there is the possibility that the event may be raised again before these changes are reflected on the screen. For a demonstration of this, have a play with the following simple example:
The application above creates funky patterns that track the current mouse position:
Everything works just fine when there are < 1000 lines on screen, however as more lines are added, the act of adding a new line to the visual tree becomes more expensive. As a result, the work needed to handle each event becomes so great that the UI is starved. If you move the mouse rapidly across the display, you will find that nothing happens until you stop moving your mouse, then all of a sudden the new lines appear.
Interestingly the background animation (which is performed on the Tick of a DispatcherTimer) does not starve the UI.
Throttling - the solution!
So, what to do? The answer is to throttle the event.
No, I don't mean that kind of throttle, I mean throttle as in "to suppress or regulate the flow of".
What we need to do is ensure that the changes we make to the visual tree within our MouseMove event are reflected in the UI before we make a subsequent change. Fortunately the Silverlight / WPF frameworks expose a static CompositionTarget.Rendering event which can be used for this purpose. This event is fired just before a frame is rendered. Therefore, to throttle the effect of the mouse event, we set a flag to indicate that we are waiting for a change to be rendered, then reset this flag just before rendering occurs.
I have wrapped this concept up in a simple class that provides a ThrottledMouseMove event:
The _awaitingRender flag ensure that we do not starve the UI. Note, the CompositionTarget.Rendering event handler removes itself after handling the event, this is because the presence of the handler causes Silverlight to continuously animate.
To use this code simply substitute the regular MouseMove event handler with our throttled event:
As a result, the UI remains responsive:
Throttling the Mouse Wheel event
The MouseWheel event is another mouse event that fires very rapidly and can result in the UI becoming frozen. Again, the same technique can be used to throttle this event. However, with the MouseMove event, the event arguments carry the current mouse position, the MouseWheel event carries just the mouse wheel position delta. If we simply suppress events that occur before render, the UI will not respond correctly, with a smaller delta being applied than is required.
The code shown below is a slightly more complex class for throttling the MouseWheel event. This class aggregates all the MouseWheel events that occur before the UI is rendered, supplying a total delta:
The following application combines both of these throttled mouse event handlers: