In the first parts of the series about developing the SilverlightShow Windows Phone app I explained the general development process and the technical details of accessing and optimizing the RSS feeds of the site. Now that we are able to access all of the content from the phone, the next step is to take a look at what was necessary to display what we have to the user while preserving a native look and feel. If you haven't seen the app in action, here is a short video about it.
When you recall the details of the previous part or simply take a quick look at the RSS feeds of the SilverlightShow site again (sample), you will see that the content of each category (news, articles, events) is stored as fully formatted HTML already. All of it is produced by authors in WYSIWYG tools like Windows Live Writer and directly uploaded to the site's content management system. Even though we receive a slightly optimized and cleaned version of that rich content on the phone, it's still HTML. The fundamental decision to make hence was whether we would take that content as-is or transform it into something more native to the phone platform.
HTML or not HTML, that is the question
I have talked about the problems of parsing real-world HTML in the last part already, and how malformed tags and content can easily break a strict approach. We could have used a specialized parser like the Html Agility Pack I've suggested before, to ease some of these problems. However, these tools can only help with certain technical details, to fix low-level issues. SilverlightShow is a platform that is open for anyone to contribute and be an author for their respective area, and that variety is also reflected in the technical structure of the content. An analysis of the existing data revealed that sometimes articles are structured with div elements, sometimes people use paragraph tags; some like to present tabular information, others make heavy use of images, diagrams and screenshots. In addition, we have code snippets for different languages, bulleted lists, and of course the normal styling features you have in HTML for highlighting and font formatting.
All of this quickly lead to the conclusion that any attempt to transform the existing content into something native to Windows Phone (=XAML) would result in various problems:
- Either we would have to spend a large amount of time to support an insane amount of formatting features, or
- We needed to make sure that all (future) content is only using a limited subset of the available options (which would require a very sophisticated technical review process before publishing any new item), or
- We would only support a small subset of the available features in HTML and simply strip the rest, which would result in a more or less significantly worse experience for phone users compared to the normal desktop experience.
In any case, we were sure that none of the above solutions could be implemented in a way that is safe and robust enough for all future content. No matter how thorough you test and create an app like this, since you highly depend on external factors that are completely out of your hands it's almost inevitably that something is going to break at some point in the future.
The logical consequence was to use the content as is, and present it within the app using the web browser control. Obviously the team at Microsoft has much more resources and knowledge, and put more effort into creating a full featured HTML parser and rendering engine than we ever could – why not make use of that and provide the best presentation possible by using this built-in option? This seemed like the perfect compromise to us: creating a nice application to give users the best look and feel as well as a native, integrated experience, and embed the existing content in a way that provides the same nice presentation as accessing the site.
What's in a color?
Using the web browser control in a satisfactory way turned out to be much harder than expected. Along the way we hit quite some obstacles that were hard to overcome. And I'm not talking about advanced or obscure things but pretty basic features. For example, setting the background color of the control can become a challenge if you want it to be perfect. The web browser control is only a shallow wrapper around a native component and in turn makes use of the built-in rendering capabilities of Internet Explorer to display its content. The way the control works unfortunately does not allow to e.g. work with advanced features like transparency for the background.
This analysis of the visual tree of the web browser control I did a while back shows the native Internet Explorer component named "TileHost" as last element. So, with the background color solely determined by the content that is displayed, the natural next step is to simply adjust that data in the HTML content itself. The items we download from the RSS feeds are only HTML fragments and need to be extended to a full and valid HTML page anyway (which means adding html, head and body tags etc.). During that step, we inject some custom styling data, for example for the pre tags of code snippets, and also for the background color:
var styleElement = ownerDoc.CreateElement("style");
// [...]
const string value = "body { font-size: 10pt; background-color: #000000; color: #ffffff; }";
var valueNode = ownerDoc.CreateTextNode(value);
styleElement.ChildNodes.Add(valueNode);
headElement.ChildNodes.Add(styleElement);
The problem with this approach is that before any content is rendered, the web browser control's background color is white by default. This means that when you set it to a black background like we do with the above code, you will see some flickering, because it takes a short amount of time for the content to be loaded and rendered. The background will rapidly switch between the default white and your desired background color every time you change the content.
This annoying behavior cannot be suppressed by any of the built-in features easily. The simplest solution I found on the web was a suggestion by Colin Eberhardt to create an overlay (another UI element, like a rectangle) with the desired background color, and then fade out that overlay once the content in the web browser control has finished rendering. He has used a similar technique in his PhoneGap experiments to overcome the same issue there (you can find his blog post on this here). This workaround is not yet added to the SilverlightShow app – if you play with the app you will therefore see the said flickering when you e.g. navigate back and forth between the content items.
If you think that this is quite some work for something as simple as setting a background color, then you haven't thought about theming yet.
I Want Change
In the above paragraphs, I showed the code used to produce a constant background of black for the web browser control. However, Windows Phone supports both a dark and a light theme, and naturally we wanted to give users of our app the same choice. If a user has set their phone to the light theme, we wanted to invert the presentation scheme of the content items to display black text on white background. Due to the same problems mentioned above, this cannot be achieved by simply setting some properties on the control. Also, we couldn't simply inject the style in the way shown above when we download the content, because obviously the user can switch themes at any time in between uses of the app. So what we had to do instead is find a way to dynamically set the new colors within the HTML content of the control. JavaScript to the rescue!
The idea is to inject some JavaScript into the content that is to displayed, in the same way that we inject the style data above, so we can let that JavaScript code perform the required changes for us whenever it is required. To this end, the web browser control provides a method named InvokeScript, which lets you execute a JavaScript function defined in the current content dynamically. The injected code can be something simple like this:
function changeBackgroundToWhite() {
document.body.style.background = 'white';
document.body.style.color = 'black';
}
Injecting this works the same way as before with the styles:
// create script element and attributes
var scriptElement = ownerDoc.CreateElement("script");
var scriptTypeAttribute = ownerDoc.CreateAttribute("type", "text/javascript");
scriptElement.Attributes.Add(scriptTypeAttribute);
// create script content
string script = "[...]";
var scriptContent = ownerDoc.CreateTextNode(script);
scriptElement.ChildNodes.Add(scriptContent);
// add everything to the head tag
headElement.ChildNodes.Add(scriptElement);
Now we are able to switch the color scheme when the device currently is set to use the light scheme:
var lightThemeVisibility = (Visibility)Application.Current.Resources["PhoneLightThemeVisibility"];
if (lightThemeVisibility == Visibility.Visible)
{
ContentWebBrowser.InvokeScript("changeBackgroundToWhite");
}
Note: Due to an issue with detecting theme changes after you return from tombstoning in the current release of the phone OS switching to the light theme that way (and not having a way to switch back) is actually sufficient in our case.
Where am I?
One of the most annoying problems with the web browser control is that it does not display a scroll bar to give the user an indication of what part of the page they are currently looking at, or how much more content there is during scrolling. I have written a detailed analysis of the problem a while ago in my blog; if you're interested in learning more on the technical details, you can read that post here. The following is a screenshot taken from that post to demonstrate the issue:
In my post I also explain a workaround that tries to emulate the behavior of the built-in browser experience. To achieve that I'm once again injecting some custom JavaScript into the content, but this time I communicate with the outer application from that very JavaScript, using the well-known "external.notify" mechanism. The corresponding JavaScript looks like this:
function initialize() {
window.external.notify("scrollHeight=" + document.body.scrollHeight.toString());
window.external.notify("clientHeight=" + document.body.clientHeight.toString());
window.onscroll = onScroll;
}
function onScroll(e) {
var scrollPosition = document.body.scrollTop;
window.external.notify("scrollTop=" + scrollPosition.toString());
}
window.onload = initialize;
On the Silverlight side of things, you are able to capture these calls and process the information accordingly; in the case of the SilverlightShow app, I'm using it to update a fake scrollbar overlay I've added to the web browser control.
// e.g. in the constructor, or in XAML
ContentWebBrowser.ScriptNotify += ContentWebBrowser_ScriptNotify;
private void ContentWebBrowser_ScriptNotify(object sender, NotifyEventArgs e)
{
// parse e.Value and use it to update a fake ScrollBar element
}
Unfortunately, the way the web browser works on the phone (and I think the web browsers on other mobile platforms and brands work in a similar way) is that they make heavy use of GPU features and deferred rendering to optimize performance. Only by using these tricks can they provide a smooth scrolling and zooming experience. The side effect of this is that the respective scrolling properties and events are not invoked until the panning has come to an end, which in turn means a limitation of this visualization workaround is that it's only updated once the user has finished scrolling.
And Some Fine-Tuning
As the name already says, one of the core features of HTML is its hyperlinked nature. The content available from SilverlightShow is no different in that. Articles and news contain links to external sources and other web sites all the time. Since we have even less control over that external content in a technical sense, and also out of legal reasons and other considerations, we decided that users of the SilverlightShow app should not be able to navigate to that external content within the web browser control, and within the context of the phone app itself. Instead, the idea is to intercept those navigational events and relay them to the built-in web browser of the Windows Phone operating system. This not only makes sure that the target page is displayed correctly and in the best way possible on the phone, but it also makes it as clear as possible to the user that they're leaving the content provided by SilverlightShow. At the same time, features like fast app switching and tombstoning allow a quick and seamless transition back to the app with no drawbacks for the user.
To make this work, you can simply handle the "Navigating" event of the web browser controls, cancel the navigation that is about to happen, and simply hand off that task to the platform's built-in web browser:
private void WebBrowser_Navigating(object sender, NavigatingEventArgs e)
{
// we cancel all internal navigation...
var uri = e.Uri;
e.Cancel = true;
try
{
// ... and instead launch an external web browser for this
WebBrowserTask task = new WebBrowserTask();
task.Uri = uri;
task.Show();
}
catch (Exception ex)
{
// do some error handling
}
}
Note: You may need to add some additional code to prevent that logic from kicking in when you set or refresh your own internal content.
Conclusion
The web browser control that is built into the framework is a powerful tool to display HTML content within your application. Unfortunately, it comes with some limitations that require unconventional and creative workarounds and solutions to provide a good user experience. Some issues like the missing scroll bars even cannot be fully resolved with the current given possibilities. If you can live with these limitations and the additional effort to achieve seemingly simply things, the control still provides a great way to present rich content, especially if it's already available as HTML.