HTML5

HTML5 is largely mature enough to start using now with ASP.NET websites.

To start with, get the Web Standards update for Visual Studio 2010 SP1: http://visualstudiogallery.msdn.microsoft.com/a15c3ce9-f58f-42b7-8668-53f6cdc2cd83

The following are a list of things that I do to be compatible with HTML5.

Use the html5 doctype at the top of every page:

<!DOCTYPE html>

Set the lang in the html tag:

<html lang="en">

Declare the character incoding in a meta tag in the head:

<meta charset="utf-8" />

Don’t declare the type or language in script tags:

<script src="/Lib/JavaScript/jquery-1.6.2.min.js"></script>

Don’t declare the type in css links:

<link rel="stylesheet" href="/Lib/Styles/colorbox.css" />

Include this so old versions of IE behave:

<!--[if lt IE 9]>
        <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

Use <header>, <nav>, <section>, <article> and <footer> tags for the page structure

Get more info at http://diveintohtml5.org/semantics.html

Advertisements

Camera Hack – About page

About pages are mandatory in Windows Phone apps. You may occasionally come across apps that don’t have them because Microsoft was rather loose with the rules when they were first getting the marketplace approval processes worked out. An application must include the application name, version information, and technical support contact information that are easily discoverable. (I think they could have just supplied that info in the marketplace and made it discoverable via long tap on the app icon, but I’m not in charge.) So now, what was once a complete, simple, one page app of single purpose, gets all muddied up with some kind of navigation and another page and extra things you weren’t planning on adding to your app when you started.

And you figure if you’ve got to add a whole other page anyway, it might as well be pretty, and link to the full version (but only if you’re using the trial) and link to the app’s rating page and show a change log and jeez, this is sounding like an entire other app and a lot more testing to do. At this point, rather than reinventing the wheel, we do as all good developers do, and try to find some code. Preferably open source, free to use in commercial software code. And Windows Phone has a wonderful library for just such an occasion. Known as Your Last About Dialog, it’s hosted on Codeplex if you want to check out the source. But the recommended way of getting it into your project is using NuGet.

NuGet side note: If you use Visual Studio and third party libraries, and you don’t know what NuGet is, go make yourself familiar right now. Once it’s installed, you can right-click on your project in Visual Studio and click “Manage NuGet Packages…” and just add all sorts of stuff your project. It will add all the files you need to your project, make any configuration changes you need, and bam! you’re good to go. And if you start using a package and decide it’s just not working out, go back into the package manager and just uninstall. It will clean out all the settings, all the files, put everything back to how it started (except any code you may have written against it, you’ll still have to fix that yourself.)

Once installed via NuGet, you only need to write one line of code…. if you don’t count the actual button or menu item XAML and the code for the actual event. But still, it’s hardly anything.

So, I used an Application Bar Menu Item so my about page link would be out of the way in the hidden part of the AppBar.

<phone:PhoneApplicationPage.ApplicationBar>
	<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
		<shell:ApplicationBar.MenuItems>
			<shell:ApplicationBarMenuItem Text="About" Click="AboutMenuItem_Click"/>
		</shell:ApplicationBar.MenuItems>
	</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

And then in the code-behind I have my click event.

private void AboutMenuItem_Click(object sender, EventArgs e)
{
	NavigationService.Navigate(new Uri("/YourLastAboutDialog;component/AboutPage.xaml", UriKind.Relative));
}

Everything else is configured and managed by an XML file that magically appeared at \Content\About\Data.xml when the YLAD package was installed. And while I’m sure there is all sorts of extra neat functionality available in the XML file, all I did was enter my name and put my support email information into it. It handled figuring out if the user has the trial version and linking to the marketplace. It handled linking to the review page for users to review the app. It’s easy, it’s quick, it doesn’t require much extra testing. It would probably add less size to my final application file to code it myself, since there are some extra things that it can handle that I probably don’t need, but overall it added less than 60k to my file size, so I believe the ease of use and time savings outweigh that issue.

Camera Hack – background threads on WP7

For me, coming from a mostly web development background, threads are one of those things that I’m aware of and avoid at all costs. They seem complicated and they’re difficult to debug and they’re just yucky. They are also incredibly necessary if you want a responsive user interface during long running tasks (like manipulating every pixel in a 7 megapixel image multiple times and then saving it to disk.) Keep in mind, based on my background and lack of experience in this area, that anything I write below works for my app, but may in fact be an incredibly poor implementation that could be better optimized. If you see anything that looks stupid, I welcome the feedback so I can get better.

In Camera Hack I used two different threading methods. One used the actual System.Threading.Thread class, which was for the live camera preview feed. The second used the System.ComponentModel.BackgroundWorker to allow the user to see a progress bar for the manipulation of the final image while it’s processing and saving.

For the live camera preview, I’ll refer you again to the excellent samples on MSDN: http://msdn.microsoft.com/en-us/library/hh202982%28v=vs.92%29.aspx. It shows how to set up the function that your thread will be calling, how to start up the thread and how to use the Dispatcher.BeginInvoke function to pass information back to the main thread.

Using that sample as a starting point, everything was going great, and then I needed to save the image, which seemed like it was going fine, but it was hard to tell. I had a lovely status message that said “Saving image…” and eventually that went away and my image was saved and my live preview started back up. But there was no telling how long it was going to take or if it got hung up somewhere. And if I as the developer don’t know if the app has hung, no user is going to wait around. They’re gonna try to use the UI, then they’re gonna exit the app. And if the image hasn’t been saved by that time, they’re gonna assume it’s broken and uninstall. And if they’re really mad because that was a great picture they just lost, then they’re gonna leave a negative review and nobody wants that. So I needed a progress bar.

BackgroundWorkers are really cool for this because they have a handy method called ReportProgress which takes an int and provides good place to set the progress of your progress bar (I want to believe I used the word progress in that sentence enough times, but I really think it needed one more).

Declare the background worker in your page.

private BackgroundWorker backgroundWorker;

Wire up the events in your constructor.

backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.WorkerReportsProgress = true;

On your camera capture event, use the Dispatcher.BeginInvoke to fire off the worker.

private void camera_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
{
	Dispatcher.BeginInvoke(delegate()
	{
		var bitmap = new WriteableBitmap((int)cam.Resolution.Width, (int)cam.Resolution.Height);

		//Load the captured image stream to the bitmap 
		bitmap.LoadJpeg(e.ImageStream);

		if (backgroundWorker.IsBusy != true)
		{
			backgroundWorker.RunWorkerAsync(bitmap);
		}
	});
}

The Do Work event is where we (surprise!) do work. It’s fairly straight forward, loop through all the pixels in the writable bitmap that was passed into the function and update them with the ProcessPixel function. You can get some idea of what I’m doing to the pixels and see my rbg class in my previous blog post. One non-obvious optimization I did here was in calling the ReportProgress every 10000 pixels. Doing the type conversions and division was costly enough that it was significantly slowing down the loop. Once the work is done, there is another Dispatcher.BeginInvoke (those things are everywhere) that pushes the finalized bitmap data to a file.

void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
	var worker = (BackgroundWorker)sender;
	var bitmap = (WriteableBitmap)(e.Argument);
	var inRBG = new rbg();

	for (int i = 0; i < bitmap.Pixels.Length; i++)
	{
		inRBG.ConvertFromInt(bitmap.Pixels[i]);
		bitmap.Pixels[i] = ProcessPixel(inRBG);

		if (i % 10000 == 0)
		{
			worker.ReportProgress((int)(((decimal)i / (decimal)(bitmap.Pixels.Length)) * 100));
		}
	}

	this.Dispatcher.BeginInvoke(delegate()
	{
		try
		{
			var stream = new MemoryStream();
			bitmap.SaveJpeg(stream, bitmap.PixelWidth, bitmap.PixelHeight, 0, 100);

			//Take the stream back to its beginning because it will be read again  
			//when saving to the library 
			stream.Position = 0;

			//Save picture to device media library 
			MediaLibrary library = new MediaLibrary();
			string fileName = string.Format("{0:yyyy-MM-dd-HH-mm-ss}.jpg", DateTime.Now);
			library.SavePicture(fileName, stream);
		}
		catch (Exception)
		{

		}
		finally
		{
			InitializeFilteredDisplay();
		}                
	});
}

Here is that progress changed event I mentioned earlier, allowing the UI to update with the current percentage complete. (SavingProgress is the name of my ProgressBar.)

void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
	SavingProgress.Value = e.ProgressPercentage;
	MainImageTxt.Text = "Saving image... " + e.ProgressPercentage.ToString() + "%";
}

You can use the RunWorkerCompleted event as a handy way of letting the rest of the app know that this process is complete. I used it to let my main thread know it was OK to resume processing the live preview (which is turned off while an image is being saved) and to reset the progress bar to 0 for the next time that I use it.

void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
	capturingImage = false;

	if (camInit)
	{
		SavingProgress.Value = 0;
	}
}

This code works pretty well overall. There are two items about the behavior of the app I don’t like. One is that the background worker can’t keep going and just finish saving the file if the user exits the app. You don’t want to prevent the user from leaving, because this is a phone and stopping them from taking a call is even worse than losing a picture. The other thing is that even though the Dispatch.BeginInvoke code in the DoWork function basically runs outside of the function, it doesn’t return immediately, so the progress bar sits at 99% for a couple seconds while the image actually saves. It’s not a terrible user experience, but it isn’t as polished as I had hoped for. The only good thing is that once it hits that 99%, it seems to be safe for the user to exit the app and the image will still successfully save.

My 30 to launch app – Camera Hack

I’ve already let the blogging slip, I’ve been preoccupied with getting my app, Camera Hack, done by the deadlines in the http://www.30tolaunch.com/ contest. The first deadline was Feb 13, where my goal was to be one of the first 500 to submit a partially completed app with fast application switching enabled. The result of meeting this goal was a shiny new Nokia Lumia 800 at my door 3 days ago.

Lumia side note: it’s a nice looking phone, but it’s heavier than my Samsung Focus S, almost the same size but with a noticeably smaller screen, and lacks a front facing camera. It also has a Micro SIM which means I can’t even drop my current SIM into it to see how the phone/cell data part works, so it’s a little disappointing. But it has been nice for testing as I’ve been able to see what happens when using my app on a phone without a front facing camera.

Deadline number two was to actually complete my app and get it approved by the end of the contest on March 15. At this point I have “finished” my app and I have submitted it to the Marketplace for approval. It has been taking around 3 days to get through Marketplace testing lately and three of the four apps I have submitted were approved on the first try, so I’m hopeful that this submission will go smoothly. Of course there is always the possibility that because this is my first app that is using the camera and integrated into the Photo Hub of the phone that I may have missed some rules that I was supposed to follow.

Ok, that’s the boring stuff, lets talk about the app.

Camera Hack

Camera Hack Screen Shot

The concept is pretty simple, show a preview of the raw camera feed on the left, show a filtered view in real-time on the right. Have a list of filters to choose from on the bottom. Put some options on the side bar. Use the hardware camera button built into every Windows Phone (great feature btw) to snap the picture and save it to the image library so it can easily be shared using the social services built into Windows Phone (also a great feature).

Microsoft has great sample code and articles on how to work with the camera and get all the functionality working from using the flash, to switching between the Primary and Rear cameras (http://msdn.microsoft.com/en-us/library/ff431744%28VS.92%29.aspx#BKMK_CameraAndPhotos), so I wasn’t too concerned with getting those parts working. What I started with was how do you create those interesting effects that make the app worth using. And create them in a way that is both understandable and not crazy slow (we need real time preview here after all and we need to keep the processing time of 7MP photos to a minimum or no one will want to use the app.)

Plus I didn’t really know what would make a good effect. How much do you change the contrast, what color do you tint it to, what other values should you change? So I went to Google for guidance and found a JavaScript library that was perfect. Alex Michael wrote this cool library called Filtrr that can take an image on a page, apply a bunch of filters to it and dump it back into a canvas element. You can see it work here: http://alexmic.github.com/filtrr/. (Alex has been working on a second better version since I first started this project, so that link may fail at some point. Check out his site through his name for updates if it doesn’t work.)

Obviously, I wasn’t going to use JavaScript in my app, so some porting to C# was necessary, but his code gave me a big jump start. In the interest of simplicity and faster running code, I left out some things like blurs and blending and focused primary on things that changed one pixel at a time.

Here is my version (please forgive my absolute lack of following even my own naming conventions, never really did go through and clean it up properly):

using System;

public class Filtr
{
		public static double safe(double i)
		{
				i = i <= 255 ? i : 255;
				i = i >= 0 ? i : 0;
				return i;
		}

		public static rbg Adjust(rbg Rbg, Double Rs, Double Gs, Double Bs)
		{
				Rbg.r = Filtr.safe(Rbg.r * (1 + Rs));
				Rbg.g = Filtr.safe(Rbg.g * (1 + Gs));
				Rbg.b = Filtr.safe(Rbg.b * (1 + Bs));

				return Rbg;
		}

		public static rbg Brightness(rbg Rbg, Double T)
		{
				Rbg.r = Filtr.safe(Rbg.r + T);
				Rbg.g = Filtr.safe(Rbg.g + T);
				Rbg.b = Filtr.safe(Rbg.b + T);

				return Rbg;
		}

		public static rbg Fill(rbg Rbg, Double Rf, Double Gf, Double Bf)
		{
				Rbg.r = Filtr.safe(Rf);
				Rbg.g = Filtr.safe(Gf);
				Rbg.b = Filtr.safe(Bf);

				return Rbg;
		}

		public static rbg Opacity(rbg Rbg, Double O)
		{
				Rbg.a = Filtr.safe(Rbg.a * O);

				return Rbg;
		}

		public static rbg Saturation(rbg Rbg, Double T)
		{
				var avg = (Rbg.r + Rbg.g + Rbg.b) / 3;

				Rbg.r = Filtr.safe(Rbg.r + T * (Rbg.r - avg));
				Rbg.g = Filtr.safe(Rbg.g + T * (Rbg.g - avg));
				Rbg.b = Filtr.safe(Rbg.b + T * (Rbg.b - avg));

				return Rbg;
		}

		public static rbg Threshold(rbg Rbg, Double T)
		{
				var c = 255;
				if (Rbg.r < T || Rbg.g < T || Rbg.b < T)
				{
						c = 0;
				}

				Rbg.r = c;
				Rbg.g = c;
				Rbg.b = c;

				return Rbg;
		}

		public static rbg Posterize(rbg Rbg, Double levels)
		{
				var step = Math.Floor(255 / levels);

				Rbg.r = Filtr.safe(Math.Floor(Rbg.r / step) * step);
				Rbg.g = Filtr.safe(Math.Floor(Rbg.g / step) * step);
				Rbg.b = Filtr.safe(Math.Floor(Rbg.b / step) * step);
				
				return Rbg;
		}

		public static rbg Gamma(rbg Rbg, Double value)
		{
				Rbg.r = Filtr.safe(Math.Pow(Rbg.r, value));
				Rbg.g = Filtr.safe(Math.Pow(Rbg.g, value));
				Rbg.b = Filtr.safe(Math.Pow(Rbg.b, value));
				
				return Rbg;
		}

		public static rbg Negative(rbg Rbg)
		{
				Rbg.r = Filtr.safe(255 - Rbg.r);
				Rbg.g = Filtr.safe(255 - Rbg.g);
				Rbg.b = Filtr.safe(255 - Rbg.b);
				
				return Rbg;
		}

		public static rbg GrayScale(rbg Rbg)
		{
				var avg = (Rbg.r + Rbg.g + Rbg.b) / 3;

				Rbg.r = Filtr.safe(avg);
				Rbg.g = Filtr.safe(avg);
				Rbg.b = Filtr.safe(avg);
				
				return Rbg;
		}

		public static rbg Tint(rbg Rbg, rbg minRbg, rbg maxRbg)
		{
				Rbg.r = Filtr.safe((Rbg.r - minRbg.r) * (255 / (maxRbg.r - minRbg.r)));
				Rbg.g = Filtr.safe((Rbg.g - minRbg.g) * (255 / (maxRbg.g - minRbg.g)));
				Rbg.b = Filtr.safe((Rbg.b - minRbg.b) * (255 / (maxRbg.b - minRbg.b)));
				
				return Rbg;
		}

		public static rbg Mask(rbg Rbg, rbg mRbg)
		{
				Rbg.r = Filtr.safe(((int)Rbg.r & (int)mRbg.r));
				Rbg.g = Filtr.safe(((int)Rbg.g & (int)mRbg.g));
				Rbg.b = Filtr.safe(((int)Rbg.b & (int)mRbg.b));
				
				return Rbg;
		}

		public static rbg Sepia(rbg Rbg)
		{
				Rbg.r = Filtr.safe((Rbg.r * 0.393) + (Rbg.g * 0.769) + (Rbg.b * 0.189));
				Rbg.g = Filtr.safe((Rbg.r * 0.349) + (Rbg.g * 0.686) + (Rbg.b * 0.168));
				Rbg.b = Filtr.safe((Rbg.r * 0.272) + (Rbg.g * 0.534) + (Rbg.b * 0.131));
				
				return Rbg;
		}

		public static rbg Bias(rbg Rbg, Double val)
		{
				Rbg.r = Filtr.safe(Rbg.r * (Rbg.r / 255 / ((1 / val - 1.9) * (.9 - Rbg.r / 255) + 1)));
				Rbg.g = Filtr.safe(Rbg.g * (Rbg.g / 255 / ((1 / val - 1.9) * (.9 - Rbg.g / 255) + 1)));
				Rbg.b = Filtr.safe(Rbg.b * (Rbg.b / 255 / ((1 / val - 1.9) * (.9 - Rbg.b / 255) + 1)));
				
				return Rbg;
		}

		public static rbg Contrast(rbg Rbg, Double val)
		{
				Rbg.r = Filtr.safe(255 * (((Rbg.r / 255) - .5) * val + .5));
				Rbg.g = Filtr.safe(255 * (((Rbg.g / 255) - .5) * val + .5));
				Rbg.b = Filtr.safe(255 * (((Rbg.b / 255) - .5) * val + .5));
				
				return Rbg;
		}
}

public class rbg
{
		public double r { get; set; }
		public double b { get; set; }
		public double g { get; set; }
		public double a { get; set; }

		public rbg()
		{

		}

		public rbg(int color)
		{
				a = color >> 24;
				r = (color & 0x00ff0000) >> 16;
				g = (color & 0x0000ff00) >> 8;
				b = (color & 0x000000ff);
		}

		public rbg(double R, double B, double G, double A)
		{
				a = A;
				r = R;
				g = G;
				b = B;
		}

		public void ConvertFromInt(int color)
		{
				a = color >> 24;
				r = (color & 0x00ff0000) >> 16;
				g = (color & 0x0000ff00) >> 8;
				b = (color & 0x000000ff);
		}

		public int ConvertToInt()
		{
				return ((int)a << 24) | ((int)r << 16) | ((int)g << 8) | (int)b;
		}
}

These classes make it nice and easy to loop through every pixel from a writable bitmap, perform one or more transformations to it and end up with a final transformed image.

var bitmap = (WriteableBitmap)(e.Argument);
var inRBG = new rbg();
var minTint1 = new rbg();
minTint1.r = 60;
minTint1.g = 35;
minTint1.b = 10;
var maxTint1 = new rbg();
maxTint1.r = 170;
maxTint1.g = 140;
maxTint1.b = 160;

for (int i = 0; i < bitmap.Pixels.Length; i++)
{
		inRBG.ConvertFromInt(bitmap.Pixels[i]);
		bitmap.Pixels[i] = Filtr.Brightness(Filtr.Contrast(Filtr.Tint(inRbg, minTint1, maxTint1), 1), 10);
}

In future posts I’ll cover how I worked with background threads, adding an about page, how I handled my app bar when not all phones will support both cameras and not all cameras have a flash, and anything else I can come up with that might be interesting.

My first game

I just submitted my first game to the Windows Phone Marketplace. I decided on the name Cloud Catcher.

It’s a fairly simple game, cards with clouds on them drift across the screen from right to left and you have to tap on the two that match in each column before they reach the gold line.

I’m interested to see if I can get any sales on this game. I think not having a trial is going to hurt me because its difficult to judge this game without playing it. Finding the matching clouds seems easy when they aren’t marching off the screen.

If I decide to do a version 2.0 of this game, I would like to figure out some good way to do a trial. Maybe it only allows X matches per game or something. (Time based trials don’t work well on WP because users can uninstall and reinstall after the trial is up and there is nothing the developer can do about it.) I would also add sound effects and music. I usually play games with the music and sounds muted, but I know it can add a lot to a game’s feel. And maybe another mechanic, like birds would block some of the clouds and you’d have to tap them first before you could see where the match was.

But for now I’m pretty happy with my first real video game. It’s always been a goal of mine to be able to make video games, and while I will probably not make any money off it, at least I’ve got a completed project. (Depending on how well or poorly  it sells, I may start posting code from the game and talking about it here, but I don’t want to just post the whole project and have someone completely rip it off, even though I know there are sites that just rip XAP files from the marketplace already.)

Be a Windows Phone developer in 30 days

Why would anyone want to develop for Windows Phone? How many people do you even know with one of these things? So the reviews are good and that’s great, but really shouldn’t I just build for the iPhone so I can make some money with my app?

Yep, probably.

BUT…

Microsoft is trying really hard to not fail with Windows Phone. They know its important for their future to have a mobile platform and are spending boat-loads of money to get developers on board with it. They have started a contest today that will allow you to get up to speed with building apps for free.

The tools are free, the $99 annual subscription to submit apps is free, and a you can get a free Nokia Lumia 800 for testing (and it’s yours to keep).

If you already publish apps for the iPhone or Android, there’s little reason not to spend a few hours this month porting one over to Windows Phone. If you are a .NET web or Windows developer, you’ll find it very easy to get started building simple apps.

Get signed up at http://www.30tolaunch.com/ and take advantage of Microsoft’s generosity. And maybe you’ll make something really amazing and win one of FOUR $3000 home entertainment packages.

(And yes I realize this whole post makes me sound like a MS fanboi, but really, I like making apps for WP, especially games using XNA, so it’s in my best interest that the platform is successful. Plus, a little more competition between platforms is good for everyone.)

Quaking code

After reviewing other earthquake map applications in the Windows Phone marketplace, I decided there wasn’t much for me to do as far as making a better earthquake map. Most of the complaints users have with the existing apps have to do with data, and there’s not much I can do about that.

That said, here’s the bad code I came up with to make my map work.

<my:Map Margin="0,0,0,0" Name="map1" CredentialsProvider="secretfrombing" ZoomBarVisibility="Visible" />

That’s all my XAML. Generally, in a Silverlight phone app, you’ll want more than that. Your view should contain everything needed to drive the view with databinding and your code behind should just be pulling data.

I didn’t realize you could databind pins in a map until I was trying to figure out how to make the pins clickable links (or I guess on the phone they are tappable).

        public MainPage()
        {
            InitializeComponent();
            LoadFeed();
        }

        private void LoadFeed()
        {
            WebClient webClient1 = new WebClient();
            webClient1.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient1_DownloadStringCompleted);
            webClient1.DownloadStringAsync(new System.Uri("http://earthquake.usgs.gov/earthquakes/catalogs/eqs7day-M2.5.xml"));
        }

        private void webClient1_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                Deployment.Current.Dispatcher.BeginInvoke(() =&gt;
                {
                    MessageBox.Show("Failed to load basic feed.");
                });
            }
            else
            {
                this.State["feed1"] = e.Result;
                UpdateFeedList1(e.Result);
            }
        }

        private void UpdateFeedList1(string feedXML)
        {
            XElement xmlFeed = XElement.Parse(feedXML);

            XNamespace geo = "http://www.w3.org/2003/01/geo/wgs84_pos#";
            XNamespace dc = "http://purl.org/dc/elements/1.1/";

            var quakes = from item in xmlFeed.Descendants("channel").Descendants("item")
                         select new SyndicatedItem
                         {
                             Magnitude = double.Parse(item.Element("title").Value.Substring(2, 3)),
                             Lat = double.Parse(item.Element(geo + "lat").Value),
                             Long = double.Parse(item.Element(geo + "long").Value),
                             PublishDate = DateTime.Parse(item.Element("pubDate").Value),
                             Link = item.Element("link").Value
                         };

            foreach (var quake in quakes)
            {
                MapPin(quake.Lat, quake.Long, quake.Magnitude, quake.Magnitude.ToString(), quake.Link);
            }
        }

        public void MapPin(double lat, double lon, double mag, string content, string link)
        {
            var pin = new Pushpin();
            pin.Location = new GeoCoordinate(lat, lon);
            Color magColor;

            switch ((int)(Math.Floor(mag)))
            {
                case 0:
                    magColor = Colors.Gray;
                    break;
                case 1:
                    magColor = Colors.DarkGray;
                    break;
                case 2:
                    magColor = Colors.Magenta;
                    break;
                case 3:
                    magColor = Colors.Purple;
                    break;
                case 4:
                    magColor = Colors.Blue;
                    break;
                case 5:
                    magColor = Colors.Green;
                    break;
                case 6:
                    magColor = Colors.Yellow;
                    break;
                case 7:
                    magColor = Colors.Orange;
                    break;
                case 8:
                    magColor = Colors.Red;
                    break;
                case 9:
                    magColor = Colors.Black;
                    break;
                default:
                    magColor = Colors.Gray;
                    break;
            }

            pin.Background = new SolidColorBrush(magColor);
            pin.Content = content;
            pin.Tag = link;
            pin.Tap += new EventHandler&lt;GestureEventArgs&gt;(pin_Tap);
            map1.Children.Add(pin);
        }

        void pin_Tap(object sender, GestureEventArgs e)
        {
            var pin = sender as Pushpin;

            WebBrowserTask webBrowserTask = new WebBrowserTask();
            webBrowserTask.Uri = new Uri(pin.Tag.ToString());
            webBrowserTask.Show();
        }

And my SyndicatedItem class:

    public class SyndicatedItem
    {
        public DateTime PublishDate { get; set; }
        public double Magnitude { get; set; }
        public double Lat { get; set; }
        public double Long { get; set; }
        public string Link { get; set; }
    }

I create a new webclient and get the RSS xml asynchronously, when it comes back, I used LinqToXML to parse the file. Pay attention to how namespaces are handled. If you don’t declare the namespace as an object and concatenate it, it will not find the node you are looking for. I added a function that would add a pin to the screen so I could for-each over the result set to display the data on the map.

Each pin gets an event handler added to it for the tap event so the pin can link back to the web page with more details on the earthquake and I’m storing the URL in the tag property of the pin. If I was databinding the pins, I would simply refer back to the datamodel to get the URL.

Now that I’ve shown you the wrong way, here’s a blog post that shows a little better way to work with the map using databinding: http://igrali.wordpress.com/2012/01/07/show-a-tooltip-for-tapped-pushpin-on-windows-phone/.