I’ve noticed a little problem in WPF.
If you two-way bind properties in a chain (A <-> B, B <-> C, C <-> D ...), everything is fine until you introduce two or more converters.
Now, with one converter, it still works, but when you add two converters in the chain (A <x> B, B <-> C, C <x> D ...), it can get stuck in an infinite loop, if the properties change fast enough.
It seems as if WPF is very smart about avoid infinite recursion, but if the changes come too quick, it hurdles whatever is blocking for you, and goes to infinity.
The point at which I noticed this is when I was prototyping my own color editor. I wanted the color selection to be accomplished by an bitmap crossing hue and lightness, with another slider saturation. To make sure the sliders were in synch I needed to bind with a converter (to and from color). Now, I ran into computation problems, so I created a class that managed converting but only did so when needed, and bound everything to that. Problem is that when I bound outside my control, I hit an infinite recursion if the user moved over the color palettes really fast.
I traced it down to an interesting effect. The converter (when setup as two way), would trigger forward, then quickly back to reflect any conversion loss. But when chained with another converter that did the same, they bounced back and forth endlessly, like ping pong.
This only happened if the converters were triggered fast enough. So I’m guessing I overflowed some value that counted recursion stack.
Has anyone else noticed anything to this effect, and how can you avoid such problems when people use binding against the “result value property” of your control?
If your converters are not accurate (e.g. have numeric calculations) then you will have to break the chain in a way similar to this…
For notifying properties
Instead of:
Use:
Or a similar inaccurate comparison, so that inaccuracies will not create an infinite loop.
For dependency properties
Use
Register(String, Type, Type, PropertyMetadata, ValidateValueCallback)andPropertyMetadata(Object, PropertyChangedCallback, CoerceValueCallback)to define a CoerceValueCallback to prevent small changes (using the same algorithm as above).