Click here to Skip to main content
15,867,308 members
Articles / Mobile Apps / Windows Phone 7

CP Vanity for Windows Phone 7

Rate me:
Please Sign up or sign in to vote.
4.98/5 (16 votes)
17 Jan 2013CPOL3 min read 77.9K   331   24   31
A WP7 port of CPVanity with a CodeProject RSS reader.

Introduction

This app started (and mostly remains) as a quick and dirty port of Luc Pattyn's CPVanity app to the Windows Phone 7 platform. Once that was done, it just seemed lacking an RSS reader of CodeProject articles and forums.

Why? Because if we don't have one already, we definitely need one!

screenshot4.png

screenshot4.png

screenshot4.png

screenshot4.png

screenshot4.png

screenshot4.png

Background

To do the port of CPVanity, three files were copied from the desktop version:

  • Article.cs
  • User.cs
  • CPSite.cs

Prerequisites

There are some things you will need to have in order to play around with this code:

In order to get CPSite (where the HTML content is downloaded and scraped) to work, some small changes were needed. First of all, the desktop version tells the CPSite class to do its work on a background thread. CPSite then creates the HttpWebRequests on that thread and downloads synchronously on that thread. Since all WP7 web communication is asynchronous at the point where it is invoked, the background threading needed to be moved into that class.

So there are some #if WINDOWS_PHONE sprinkled throughout:

C#
#if WINDOWS_PHONE
    public void GetArticlePage(Action<string> callback)
    {
        Debug.Assert(callback != null);
        downloadPage("script/Articles/MemberArticles.aspx?amid="
               + memberID, callback);
    }
#else
    public string GetArticlePage() {
       page=downloadPage("script/Articles/MemberArticles.aspx?amid="
            + memberID);
       return page;
    }
#endif

And then, it just uses the WebClient to get the HTML content:

C#
#if WINDOWS_PHONE
    private void downloadPage(string URL, Action<string> callback)
    {
        if (!URL.StartsWith("http"))
            URL = baseURL + "/" + URL;

        WebClient client = new WebClient();
        client.DownloadStringCompleted += new 
           DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
        client.DownloadStringAsync(new Uri(URL), callback);
    }

    void client_DownloadStringCompleted(object sender, 
           DownloadStringCompletedEventArgs e)
    {
        Debug.Assert(e.UserState is Action<string>);
        var callback = (Action<string>)e.UserState;
        try
        {
            page = e.Result;
            callback(page);
        }
        catch (Exception ex)
        {
            callback(ex.Message);
        }
        finally
        {
           ((WebClient)sender).DownloadStringCompleted -= new
            DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
        }
    }
#endif

And then up in the ViewModel, in response to the user ID changing, it does:

C#
private CPSite _site;

...

if (_site != null)
  _site.GetArticlePage(GotUserPage);

...

private void GotUserPage(string result)
{
    if (_site != null)
    {
        string name = _site.GetName();
        string adornedName = _site.GetAdornedName();
        if (adornedName.Length == 0)
            adornedName = name;

        UserName = adornedName;

        var articles = _site.GetArticles();
        ArticleCount = plural(articles.Count, 
             "article#s available");

        if (articles.Count > 1)
            AverageRating = "Average rating: " + 
                   _site.GetAverageRating() + " / 5";

        foreach (var a in articles.OrderByDescending(a => a.Updated))
            Articles.Add(new ArticleViewModel(a));
    }
}

All the rest is just run of the mill Silverlight databinding to populate the UI.

RSS Reader

Retrieving and displaying the RSS feeds uses the WebClient, but also some basic XML serialization.

C#
public void Load()
{
    WebClient client = new WebClient();
    client.DownloadStringCompleted += 
      new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
    client.DownloadStringAsync(Uri);
}

void client_DownloadStringCompleted(object sender, 
            DownloadStringCompletedEventArgs e)
{
    var serializer = new XmlSerializer(typeof(RssChannel));

    try
    {
        using (var text = new StringReader(e.Result))
        using (var reader = XmlReader.Create(text))
        {
            reader.ReadToDescendant("channel");
            Channel = (RssChannel)serializer.Deserialize(reader);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
    finally
    {
        ((WebClient)sender).DownloadStringCompleted -= 
          new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
    }
}

where an RssChannel and RssItem look like this:

C#
[XmlRoot("channel")]
public class RssChannel
{
    public RssChannel()
    {
    }

    [XmlElement("title")]
    public string Title { get; set; }

    [XmlElement("item")]
    public List<rssitem> Items { get; set; }
}

[XmlRoot("item")]
public class RssItem
{
    public RssItem()
    {
    }

    [XmlElement("title")]
    public string Title { get; set; }

    [XmlElement("description")]
    public string Description { get; set; }

    [XmlElement("link")]
    public string Link { get; set; }

    [XmlElement("author")]
    public string Author { get; set; }

    [XmlElement("pubDate")]
    public string Date { get; set; }

    [XmlElement("GUID")]
    public string GUID { get; set; }
}

View to ViewModel Binding

Though the MVVM ViewModelLocator works quite well when there is a 1:1 mapping between the View and the View Model, it doesn't work quite so well when you want to reuse a Page to render different ViewModels. The ViewModelLocator is set statically in the XAML, creating the 1:1 binding.

Since the rendering of the forum and article RSS feeds is identical and the ViewModels (and definitely the Models behind those) are slightly different, I ended up with one page and two ViewModels.

To get around the 1:1 mapping in ViewModelLocator, an Attribute is used to mark up the ViewModel:

C#
[AttributeUsage(AttributeTargets.Class)]
public sealed class PageAttribute : Attribute
{
    public readonly Uri Page;

    public PageAttribute(string address)
    {
        Page = new Uri(address, UriKind.Relative);
    }
}

[Page("/ArticlesPage.xaml?vm=ArticlesStatic")]
public class ArticlesViewModel : ContainerViewModel{}

[Page("/ArticlesPage.xaml?vm=ForumsStatic")]
public class ForumsViewModel : ContainerViewModel{}

and then in the code that handles the navigation events:

C#
private void Select(object o)
{
    var vm = o as CPViewModel;
    if (vm != null)
    {
      Debug.Assert(vm.GetType().HasAttribute<PageAttribute>());
      var page = vm.GetType().GetAttribute<PageAttribute>();
      Navigate(page.Page);
   }
}

The page code-behind does need to do a little bit of the work because the desired ViewModel is specified in the URI's query string:

C#
protected override void OnNavigatedTo(NavigationEventArgs e)
{
   if (NavigationContext.QueryString.ContainsKey("vm"))
   {
      Dispatcher.BeginInvoke(() => DataContext =
        ViewModelLocator.FindViewModel(NavigationContext.QueryString["vm"]));
   }
   base.OnNavigatedTo(e);
}
...
public class ViewModelLocator
{
   public static object FindViewModel(string key)
   {
      var prop = typeof(ViewModelLocator).GetProperty(key,
         BindingFlags.Public | BindingFlags.Static);
      Debug.Assert(prop != null);
      return prop.GetValue(null, null);
   }
}

The linkage between View and the ViewModel is still declarative and loosely coupled, but it also has an additional degree of freedom which seems advantageous in that a single page can render any ViewModel as long at it exposes two properties: Name and Items.

XML
<controls:Pivot Title="{Binding Name}" 
  ItemsSource="{Binding Items}"
  ItemTemplate="{StaticResource DynamicContentTemplate}"/>

Points of Interest

The most interesting thing I learned is that once you figure out the platform, WP7 development is very easy. This took all of a couple of hours to get together (of course, plagiarizing much of the original work wholesale).

History

  • 12/14/2010 - Initial upload
  • 12/17/2010 - Added progress bar and bigger rep image page
  • 12/27/2010 - Added RSS reader
  • 1/17/2013 - Tested on WP8, updated for site changes, bug fixes, removed from Game Hub

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Starkey Laboratories
United States United States
The first computer program I ever wrote was in BASIC on a TRS-80 Model I and it looked something like:
10 PRINT "Don is cool"
20 GOTO 10

It only went downhill from there.

Hey look, I've got a blog

Comments and Discussions

 
GeneralMy vote of 5 Pin
SoMad6-Jan-13 22:14
professionalSoMad6-Jan-13 22:14 
GeneralI found the app in the new collection. Pin
SoMad6-Jan-13 22:26
professionalSoMad6-Jan-13 22:26 
GeneralRe: I found the app in the new collection. Pin
Don Kackman7-Jan-13 3:37
Don Kackman7-Jan-13 3:37 
GeneralMy vote of 5 Pin
Kanasz Robert21-Sep-12 1:42
professionalKanasz Robert21-Sep-12 1:42 
GeneralMy vote of 5 Pin
thatraja11-Feb-11 5:28
professionalthatraja11-Feb-11 5:28 
GeneralRe: My vote of 5 Pin
Don Kackman22-Feb-11 10:33
Don Kackman22-Feb-11 10:33 
GeneralMy vote of 5 Pin
Nish Nishant24-Jan-11 3:23
sitebuilderNish Nishant24-Jan-11 3:23 
GeneralRe: My vote of 5 Pin
Don Kackman24-Jan-11 3:35
Don Kackman24-Jan-11 3:35 
GeneralMy vote of 5 Pin
RaviRanjanKr27-Dec-10 18:11
professionalRaviRanjanKr27-Dec-10 18:11 
GeneralRe: My vote of 5 Pin
Don Kackman1-Jan-11 17:19
Don Kackman1-Jan-11 17:19 
GeneralMy vote of 5 Pin
linuxjr27-Dec-10 15:20
professionallinuxjr27-Dec-10 15:20 
GeneralRe: My vote of 5 Pin
Don Kackman1-Jan-11 17:19
Don Kackman1-Jan-11 17:19 
GeneralNow available in the marketplace Pin
Don Kackman17-Dec-10 16:30
Don Kackman17-Dec-10 16:30 
GeneralRe: Now available in the marketplace Pin
Markus Fischbacher26-Jan-11 9:23
Markus Fischbacher26-Jan-11 9:23 
AnswerRe: Now available in the marketplace Pin
Don Kackman26-Jan-11 10:47
Don Kackman26-Jan-11 10:47 
GeneralRe: Now available in the marketplace Pin
Markus Fischbacher26-Jan-11 12:00
Markus Fischbacher26-Jan-11 12:00 
GeneralRe: Now available in the marketplace Pin
Mr.Jinky11-Aug-11 3:25
Mr.Jinky11-Aug-11 3:25 
GeneralRe: Now available in the marketplace Pin
Don Kackman11-Aug-11 14:54
Don Kackman11-Aug-11 14:54 
GeneralReally good article! 5 Pin
DrABELL16-Dec-10 19:24
DrABELL16-Dec-10 19:24 
GeneralLogo Pin
Luc Pattyn16-Dec-10 5:00
sitebuilderLuc Pattyn16-Dec-10 5:00 
GeneralRe: Logo Pin
Don Kackman16-Dec-10 5:10
Don Kackman16-Dec-10 5:10 
GeneralRe: Logo Pin
Don Kackman16-Dec-10 7:01
Don Kackman16-Dec-10 7:01 
GeneralRe: Logo Pin
Luc Pattyn16-Dec-10 7:06
sitebuilderLuc Pattyn16-Dec-10 7:06 
GeneralGreat. Pin
Luc Pattyn16-Dec-10 4:48
sitebuilderLuc Pattyn16-Dec-10 4:48 
GeneralRe: Great. Pin
Don Kackman16-Dec-10 5:09
Don Kackman16-Dec-10 5:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.