How can I manually tell an owner-drawn WPF Control to refresh/redraw without executing measure or arrange passes?

Mark A. Donohoe picture Mark A. Donohoe · Oct 18, 2011 · Viewed 25k times · Source

We are doing custom drawing in a control subclass's OnRender. This drawing code is based on an external trigger and data. As such, whenever the trigger fires, we need to re-render the control based on that data. What we're trying to do is find out how to force the control to re-render but without going through an entire layout pass.

As stated above, most answers I've seen revolve around invalidating the Visual which invalidates the layout which forces new measure and arrange passes which is very expensive, especially for very complex visual trees as ours is. But again, the layout does not change, nor does the VisualTree. The only thing that does is the external data which gets rendered differently. As such, this is strictly a pure rendering issue.

Again, we're just looking for a simple way to tell the control that it needs to re-execute OnRender. I have seen one 'hack' in which you create a new DependencyProperty and register it with 'AffectsRender' which you just set to some value when you want to refresh the control, but I'm more interested in what's going on inside the default implementation for those properties: what they call to affect that behavior.


Update:

Well, it looks like there isn't any such call as even the AffectsRender flag still causes an Arrange pass internally (as per CodeNaked's answer below) but I've posted a second answer that shows the built-in behaviors as well as a work-around to suppress your layout pass code from running with a simple nullable size as a flag. See below.

Answer

CodeNaked picture CodeNaked · Oct 18, 2011

Unfortunately, you must call InvalidateVisual, which calls InvalidateArrange internally. The OnRender method is called as part of the arrange phase, so you need to tell WPF to rearrange the control (which InvalidateArrange does) and that it needs to redraw (which InvalidateVisual does).

The FrameworkPropertyMetadata.AffectsRender option simply tells WPF to call InvalidateVisual when the associated property changes.

If you have a control (let's call this MainControl) that overrides OnRender and contains several descendant controls, then calling InvalidateVisual may require the descendant controls to be rearranged, or even remeasured. But I believe WPF has optimizations inplace to prevent descendant controls from being rearranged if their available space is unchanged.

You may be able to get around this by moving your rendering logic to a separate control (say NestedControl), which would be a visual child of MainControl. The MainControl could add this as a visual child automatically or as part of it's ControlTemplate, but it would need to be the lowest child in the z-order. You could then expose a InvalidateNestedControl type method on MainControl that would call InvalidateVisual on the NestedControl.