Recently I started developing my first WP7 application. Basically it is a news reader, almost like an RSS reader, but not quite. With RSS readers you can rely on a proper xml feed, in my case though, I had to do screen scrapes and html hacking and stripping to get what I want. The site I’m scraping from is an oldish, plain old html site with some flash to make it look acceptable. A second problem I’m sitting with is that the site is hosted in South Africa(.co.za), so it is very slow. In this post I’ll step through a few things I discovered which I thought was tricky, interesting or just plain cool.
Connectivity
Your device won’t always be online. I’m doing a basic check to see if I have connectivity or not. I’m not checking for WiFi or GPRS or edge, just connected, yes or no. The namespace you’re looking for is:
Microsoft.Phone.Net.NetworkInformation;
And then you can do something like this
if (NetworkInterface.GetIsNetworkAvailable())
{ …do connection stuff }
Read more here if you are interested in establishing what sort of connection you have.
Panorama control
I found the Panorama control very easy to work with. You literally slap it in, add a few PanoramaItems and there you go. Even the xaml UI editor in Visual Studio jumps to the next item if you click on it, making it really easy for your drag and drop type of work. The namespace for the control is
xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
A typical implementation like the image below can be achieved by adding code like this
<Grid x:Name="LayoutRoot">
<controls:Panorama Title="people" FontSize="8">
<controls:PanoramaItem Header="recent">
…
<controls:PanoramaItem Header="all">
…
<controls:PanoramaItem Header="what’s new">
…

WebClient and asynchronous events
WebClient is what I used to reach the outside world. I started off by using HttpWebRequest, but that does not work in the WP7 wolrd. WP7 is forcing you down the WebClient asynchronous route and even then you’ll find there are quite a few methods missing if you compare that to your normal silverlight libraries. Make no mistake; this is indeed a cut down version of the framework. You’ll find that in your web connections, working with bitmaps, working with XmlDocument/XDocument. The whole thing is a new discovery, but most things are easily solvable if your favourite class or method is not available. This is more or less what my code looks like to make an asynchronous connection:
WebClient client = new WebClient();
client.OpenReadCompleted += OpenReadCallback;
client.OpenReadAsync(uri);
And then catching the asynchronous call
private void OpenReadCallback(Object sender, OpenReadCompletedEventArgs e)
{
Stream reply = null;
StreamReader s = null;
try
{
reply = (Stream)e.Result;
s = new StreamReader(reply);
…do something with your stream like s.ReadToEnd()
}
….. close all the stuff you need to
IsolatedStorageSettings
IsolatedStorageSettings.ApplicationSettings is an extremely handy little storing class to save all those things that you might want to use when you are offline or for a better user experience to show the user something whilst connecting to get more updated data.
I store my LightNewsItem object using this method. My object contains mainly strings, like content or title for example. The one thing my object had was and image, and that you won’t get stored in your IsolatedStorageSettings. However keep on reading to find out how I ended up storing my bitmaps.
This is more or less how I use IsolatedStorageSettings.ApplicationSettings how to save my light data:
private static IsolatedStorageSettings _newsFeedText = IsolatedStorageSettings.ApplicationSettings;
……
_lightNewsItems = new List<LightNewsItem>();
foreach (var keyValuePair in complexList)
{
…building up my object
_lightNewsItems.Add(keyValuePair.Key);
}
_newsFeedText.Clear();
_newsFeedText.Add(NEWS, _lightNewsItems);
_newsFeedText.Save();
You do not need to call the Save as it will happen automatically when your session closes. But this is just forcing the save to happen there and then. And with regards to me clearing, I want to make sure none of the old stuff exists.
IsolatedStorageFile (Storing Bitmaps)
I found it quite hard to get my images stored and somehow I had to google for quite a long time before I nailed it. There a few variables at play here. Will you try saving in the ApplicationSettings instead, will you use “Image” as your object type or will you use Bitmap. After a long time this is the solution I’m using, and now looking at it, it makes sense and look so easy.
To store a bitmap
Step 1: The object to use is a WriteableBitmap. (So convert what you have to a WriteableBitmap)
Step 2: Convert that into a byte array. (WriteableBitmap is not straight forward to convert, look here for an example)
Step 3: Use an IsolatedStorageFile object to create a directory where you’ll save these images.
Step 4: Create an IsolatedStorageFileStream
Step 5: Write the byte data to the stream.
And then to retrieve your image, open another IsolatedStorageFileStream like above. Make sure the file you are looking for exists, then stream.Read to get your bytes. Now you need to convert the bytes back to a WriteableBitmap and there you go. Make sure to look at Jeremy Likness’ blog, he gives a proper example of how to do the WriteableBitmap conversions.
Popup, VirtualTree and the Back button
In order to use the back button, you need to know where you are coming from. And when using a popup, you don’t necessarily have a back stack, page state or a field like that to tell where you are from. Some food for thought, but for now let’s start at catching the back button press event. To catch the back button press event you need to override the OnBackKeyPress method on your PhoneApplicationPage. And if you are in a popup window and pressing the back button, it will seem like you are jumping 2 steps back, because actually it is not aware of your popup as your popup is a control and not of type PhoneApplicationPage . To avoid that scenario you need to do the following in the event handler; Set the popup.IsOpen to false and do a e.Cancel = true on the CancelEventArgs. That way you suppress the back button press but you close the popup giving the user the effect of the back button taking him back to the previous page.
This sounds fairly straight forward, but there is one bit missing. Listening to the BackKeyPress event needs setting up. And in order for that you need the page from where you want to listen from. The reason I ran into a slight problem is because I have a page of type PhoneApplicationPage that contains a usercontrol. In the user control the actual popup will happen, ie: It contains a popup control. And you can’t setup(page.BackKeyPress += PhoneApplicationPage_BackKeyPress;) the event in the user control because it is not of type PhoneApplicationPage. The first thing you need to do is to give your calling page, that is of type PhoneApplicationPage, a name. That is the name you’ll use to get your page. Then using a combination of FrameworkElement and VisualTreeHelper you need to run upwards in the stack to find your control. So yes we are talking about an ugly while loop with some logic in. This is how I’ve done it:
FrameworkElement fe = VTreeHelper.FindParent("PanoramaPage", this);
……..
public static FrameworkElement FindParent(string parentName, FrameworkElement childControl)
{
FrameworkElement frameworkElement = null;
try
{
while(frameworkElement == null)
{
DependencyObject parent = VisualTreeHelper.GetParent(childControl);
if(parent == null)
return frameworkElement;
if (((FrameworkElement)parent).Name == parentName)
{
frameworkElement = (FrameworkElement)parent;
return frameworkElement;
}
else
{
childControl = (FrameworkElement)parent;
}
}
return frameworkElement;
}
Oh, and one last thing if it wasn’t obvious. To open the popup page you’ll need to do something like this:
(You’ll find the popup control here: System.Windows.Controls.Primitives.Popup)
Add the popup control in your xaml. (In my case add it anywhere, as the popup will consume my whole page.)
When the event fires that will cause the popup to happen, do something like this:
this.popup = new Popup();
this.popup.Child = new NewsArticlePopup(((HyperlinkButton) sender).TargetName);
this.popup.Visibility = Visibility.Visible;
this.popup.IsOpen = true;
(ps: NewsArticlePopup is just another usercontrol)
XDocument and XNode
I started off trying to use XmlDocument and XmlNode, but in WP7 development they don’t work. So be sure to use XDocument format. I didn’t have the luxury to have proper formulated xml, but once you did a whole lot of cleaning up html and making it xml ready you can do something like this to create a proper XDocument. (If this step fails, it means you have dodgy xml. A good way to check is to copy your string of data and paste into a new xml file. Visual Studio will show you all the errors.)
private const string ROOT = @"<?xml version=""1.0"" encoding=""utf-8""?>";
….
cleanXML = ROOT + cleanXML;
var xmlTree = XDocument.Parse(cleanXML);
If this step works successfully you are in business! Now all that’s left is to look at your data and calculate what is coming next, as you’ll be doing a foreach on each and every node.
Basically you wait till you hit the tag you are looking for, then grab some text or attributes that is needed. Sometimes you can get away without having counters but in my case I had to implement a little bit of that because you could never be sure of what was coming next. This is how I did some of my node work. Note that this method rips out all fixtures for matches coming up for different tournaments and different dates.
private List<Fixtures> ParseXMLForFixtures(string cleanXML)
{
cleanXML = ROOT + cleanXML;
var xmlTree = XDocument.Parse(cleanXML);
IEnumerable<XNode> nodes =
from node in xmlTree.Elements("div").DescendantNodes()
select node;
var fixturesList = new List<Fixtures>();
var fixtures = new Fixtures();
var matchDate = new MatchDate();
var match = new Match();
bool isFirstTime = true;
int counter = 0;
foreach (XNode node in nodes)
{
counter++;
switch (node.NodeType)
{
case XmlNodeType.Element:
if (((XElement)node).Name.ToString() == "h2")
{
if(isFirstTime)
{
isFirstTime = false;
fixtures = new Fixtures() { MatchDates = new List<MatchDate>() };
}
else
{
fixturesList.Add(fixtures);
fixtures = new Fixtures() { MatchDates = new List<MatchDate>() };
counter = 1;
}
}
if (((XElement)node).Name.ToString() == "strong")
{
matchDate = new MatchDate();
counter = 4;
}
Debug.WriteLine("Element: {0}", ((XElement)node).Name);
break;
case XmlNodeType.Text:
switch (counter)
{
case 2:
fixtures.Title = ((XText)node).Value.Trim();
break;
case 3:
fixtures.TimeDescription = ((XText)node).Value.Trim();
break;
case 5:
matchDate.DateString = ((XText)node).Value.Trim();
matchDate.Matches = new List<Match>();
fixtures.MatchDates.Add(matchDate);
break;
default:
matchDate.Matches.Add(new Match() { MatchDescription = ((XText)node).Value.Trim() });
break;
}
Debug.WriteLine("Text: {0}", ((XText)node).Value);
break;
}
}
fixturesList.Add(fixtures);
return fixturesList;
}
I don’t like code like this, but I couldn’t find any other way to run through xml and picking off the bits you need. The counter helps you specify which attribute you need to set next. It also prepares you when you need to create new objects in order to avoid Null Exceptions. An alternative was using regular expressions, but my limited knowledge and the thoughts of troubleshooting made me go this way instead. Really hoping someone can respond with a different solution.
Just a random, chat like, blog. If you read till here you probably found it a little bit interesting at least…well I hope so.
References:
Panorama control: http://msdn.microsoft.com/en-us/library/ff941104(v=vs.92).aspx
Connectivity: http://cisforcoder.wordpress.com/2010/11/27/better-way-to-check-for-an-internet-connection-on-wp7/
Converting WriteableBitmaps : http://csharperimage.jeremylikness.com/2009/07/saving-bitmaps-to-isolated-storage-in.html