One of the neat things about sharing code with the community is hearing how people have learned from it or are using it in their own work. Of course, the more people use something, the more likely they are to identify problems with it - which is great because it provides an opportunity to improve things! This blog post is about addressing two issues that came up around some code I published recently.
The Platform Workaround (WebBrowserExtensions)
Roger Guess contacted me a couple of days after I posted the WebBrowserExtensions code to report a problem he saw when using it on Windows Phone 7 with the platform's NavigationService in a scenario where the user could hit the Back button to return to a page with a WebBrowser control that had its content set by the
WebBrowserExtensions.StringSource property. (Whew!) Instead of seeing the content that was there before, the control was blank! Sure enough, I was able to duplicate the problem after I knew the setup...
My initial theory was that the
WebBrowser was discarding its content during the navigation and not being reinitialized properly when it came back into view. Sure enough, some quick testing confirmed this was the case - and what's more, the same problem happens with the official Source property as well! That made me feel a little better because it suggests a bug with the platform's
WebBrowser control rather than my own code.
The workaround I came up with for
StringSource (and that was kindly verified by Roger) should work just as well for the official
Source property: I created an application-level event handler for the Loaded event on the
WebBrowser and use that event to re-apply the correct content during the "back" navigation. I updated the Windows Phone sample application and added a new button/page to demonstrate the fix in action.
If this scenario is possible with your application, please consider applying a similar workaround!
Aside: Although it should be possible to apply the workaround to the
WebBrowserExtensionscode itself, I decided that wasn't ideal because of the event handler: the entire
StringSourceattached dependency property implementation is static, and tracking per-instance data from static code can be tricky. In this case, it would be necessary to ensure the
Loadedevent handler was added only once, that it was removed when necessary, and that it didn't introduce any memory leaks. Because such logic is often much easier at the application level and because the same basic workaround is necessary for the official
WebBrowser.Sourceproperty and because it applies only to Windows Phone, it seemed best to leave the core
Further aside: This same scenario works fine on Silverlight 4, so it's another example of a Windows Phone quirk that needs to be worked around. (Recall from the previous post that it was already necessary to work around the fact that the Windows Phone
WebBrowserimplementation can't be touched outside the visual tree.) That's a shame because the scenario itself is reasonable and consistent with the platform recommendation to use
NavigationServicefor everything. The fact that it seems broken for the "real"
Sourceproperty as well makes me think other people will run into this, too.
The Layout Implementation Oversight (BestFitPanel)
Eitan Gabay contacted me soon after I posted my BestFitPanel code to report an exception he saw when using one of the
BestFitPanel classes as the ItemsPanel of a ListBox at design-time. I hadn't tried that particular configuration, but once I did, I saw the same message:
"MeasureOverride of element 'Delay.MostBigPanel' should not return PositiveInfinity or NaN as its DesiredSize.". If you've dealt much with custom Panel implementations, this probably isn't all that surprising... Although coding layout is often straightforward, there can be a variety of edge cases depending on how the layout is done. (For example: only one child, no children, no available size, nested inside different kinds of parent containers, etc..)
In this case, it turns out that the constraint passed to MeasureOverride included a value of double.PositiveInfinity and
BestFitPanel was returning that same value. That isn't allowed because the
MeasureOverride method of an element is supposed to return the smallest size the element can occupy without clipping - and nothing should require infinite size! (If you think about it, though, the scenario is a little wacky for
BestFitPanel: what does it mean to make the best use of an infinite amount of space?)
There are two parts to my fix for this problem. The first part is to skip calling the
CalculateBestFit override for infinite bounds (it's unlikely to know what to do anyway) and to Measure all the children at the provided size instead. This ensures all children get a chance to measure during the measure pass - which some controls require in order to render correctly. The second part of the fix is to return a Size with the longest width and height of any child measured when infinite bounds are passed in. Because children are subject to the same rule about not returning an infinite value from
Measure, this approach means
BestFitPanel won't either and that the
Panel will occupy an amount of space that's related to the size of its content (instead of being arbitrary like 0x0, 100x100, etc.).
The combined effect of these changes is to fix the reported exception, provide a better design-time experience, and offer an more versatile run-time experience as well!
The more a piece of code gets looked at and used, the more likely it is that potential problems are uncovered. It can be difficult to catch everything on your own, so it's fantastic to have a community of people looking at stuff and providing feedback when something doesn't work. Thanks again to Robert and Eitan for bringing these issues to my attention and for taking the time to try out early versions of each fix!
I'm always hopeful people won't have problems with my code - but when they do, I really appreciate them taking the time to let me know!