Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Comic Reader for WP7

0.00/5 (No votes)
30 Nov 2010 1  
Introduction to building a simple Windows phone 7 application that reads and stores comic feeds in WP7

Introduction

In this article, I will explain how to create a simple application that reads Dilbert, xkcd and Phd comic feeds and show them in WP7. By simple modifications to the code, you can add feeds of various other comic series and see them on your Windows Phone 7.

Background

Kudos to Microsoft for providing an awesome platform for WP7 application development using Silverlight. It's easy, quick and fun.

Using the Code

Let's start with the XAML first.

home_screen.png

The home screen of Comic Reader is easy to create. I have a grid containing two rows, one column. The first grid contains a stack panel that contains the Header "Comic Reader" while the other grid contains a Listbox hosting buttons for each type of comic.

<Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">            
            <TextBlock x:Name="PageTitle" Text="Comics Reader" Margin="9,-7,0,0" 
		Style="{StaticResource PhoneTextTitle1Style}"/>
            <TextBlock x:Name="ApplicationTitle" 
		Text="Read your favorite web comics on the move!" 
		Style="{StaticResource PhoneTextNormalStyle}"/>
        </StackPanel>
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ListBox ScrollViewer.HorizontalScrollBarVisibility="Auto" 
		ScrollViewer.VerticalScrollBarVisibility="Auto">
                <controls:ComicButton x:Name="btnComicDilbert" 
		Click="btnComicDilbert_Click"/>
                <controls:ComicButton x:Name="btnComicXkcd" Click="btnComicXkcd_Click" />
                <controls:ComicButton x:Name="btnComicPhdComics" 
		Click="btnComicPhdComics_Click" />
            </ListBox>
        </Grid>
    </Grid>	  

In the code behind, we are forcing Landscape orientation by doing the following:

this.SupportedOrientations = SupportedPageOrientation.PortraitOrLandscape;

Each comic feed has three most important tags for providing:

  1. The comic article image
  2. The comic article title
  3. The article description

Based on this assumption, the comicpage.xaml where the comic articles are hosted is as follows:

<Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="PageTitle" Text="" Margin="9,-7,0,0" 
		Style="{StaticResource PhoneTextTitle1Style}"  
		FontStretch="SemiCondensed" />
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <phone:WebBrowser HorizontalAlignment="Stretch" Margin="0,0,0,203" 
		Name="webBrowser1" VerticalAlignment="Stretch" Width="Auto" />
            <TextBlock Height="76" HorizontalAlignment="Left" Margin="9,451,0,0" 
		Name="DescriptionTextBlock" Text="" VerticalAlignment="Top" Width="441" 
                       TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Auto"
			 ScrollViewer.HorizontalScrollBarVisibility="Auto" />            

            <Grid x:Name="Spinner" Background="Transparent" Visibility="Collapsed" 
		HorizontalAlignment="Center" Margin="168,0,79,-140" Width="209">
                <Grid.RenderTransform>
                    <ScaleTransform x:Name="SpinnerScale" ScaleX="0.5" ScaleY="0.5" />
                </Grid.RenderTransform>

                <Canvas RenderTransformOrigin="0.5,0.5" Width="120" Height="120">
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="20.1696" Canvas.Top="9.76358" 
                             Stretch="Fill" Fill="#E6FFFFFF"/>
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="2.86816" Canvas.Top="29.9581" 
                             Stretch="Fill" Fill="#CDFFFFFF"/>
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="5.03758e-006" Canvas.Top="57.9341" 
                             Stretch="Fill" Fill="#B3FFFFFF"/>
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="12.1203" Canvas.Top="83.3163" 
                             Stretch="Fill" Fill="#9AFFFFFF"/>
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="36.5459" Canvas.Top="98.138" 
                             Stretch="Fill" Fill="#80FFFFFF"/>
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="64.6723" Canvas.Top="96.8411" 
                             Stretch="Fill" Fill="#67FFFFFF"/>
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="87.6176" Canvas.Top="81.2783" 
                             Stretch="Fill" Fill="#4DFFFFFF"/>
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="98.165" Canvas.Top="54.414" 
                             Stretch="Fill" Fill="#34FFFFFF"/>
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="92.9838" Canvas.Top="26.9938" 
                             Stretch="Fill" Fill="#1AFFFFFF"/>
                    <Ellipse Width="21.835" Height="21.862" 
			Canvas.Left="47.2783" Canvas.Top="0.5" 
                             Stretch="Fill" Fill="#FFFFFFFF"/>
                    <Canvas.RenderTransform>
                        <RotateTransform x:Name="SpinnerRotate" Angle="0" />
                    </Canvas.RenderTransform>
                    
                    <Canvas.Triggers>
                        <EventTrigger RoutedEvent="ContentControl.Loaded">
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="SpinnerRotate" 
                                       Storyboard.TargetProperty="(RotateTransform.Angle)" 
                                       From="0" To="360" Duration="0:0:01" 
                                       RepeatBehavior="Forever" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </Canvas.Triggers>
                </Canvas>
            </Grid>
        </Grid>
    </Grid> 

Till the time the content is not loaded into the browser, I run the spinner using storyboard animation shown above.

How is the Code Organizer?

When we see the code, we have ComicFeed class that contains a list of ComicArticle objects. Each ComicArticle object contains all the information for showing a comic article on ComicPage.xaml.

Though all the feeds are similar, we do require to parse each feed separately due to minor differences in the feeds. Hence, for each comic series, we have a separate class DilbertFeed, XkcdFeed and PhdFeed that inherit from ComicFeed class.

The ComicFeed class is written as follows:

public abstract class ComicFeed : INotifyPropertyChanged
    {
        public Uri ImageSource
        {
            get;
            set;
        }

        public string NotificationCount
        {
            get;
            set;
        }

        public string ComicTitle
        {
            get;
            set;
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        virtual protected void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null) 
		{ PropertyChanged(this, new PropertyChangedEventArgs(name)); }
        }

        #endregion

        public EventHandler ArticlesUpdated;
        public TextBlock comicTitle;
        public string rssFeedLink = string.Empty;
        public int latestArticleNumber = 0;
        public bool downloadInProgress = false;

        public virtual string ModifyHtml(string html, string title)
        {
            return html;
        }

        public virtual void SyncLatestArticles()
        {
            WebClient webclient = new WebClient();
            webclient.DownloadStringCompleted += new DownloadStringCompletedEventHandler
					(this.webclient_DownloadStringCompleted);
            webclient.DownloadStringAsync(new Uri(this.rssFeedLink));
        }

        public virtual ComicArticle GetArticle(int counter)
        {
            throw new NotImplementedException();
        }

        public virtual void webclient_DownloadStringCompleted
		(object sender, DownloadStringCompletedEventArgs e)
        {
        }

        public List<ComicArticle> comicArticles = new List<ComicArticle>();
        public int limitOfArticles = 4;
    } 

By default, the limit to the number of articles per comic feed is 4. The current implementation stores the comic articles in the isolated storage hence there was a need to limit the comic articles so that our application does not consume all the space in isolated storage.

For each comic series, a folder by the corresponding name is created in isolated storage and the articles are stored in those folders. The GetArticles(int articleNumber) is used to retrieve the articles from isolated storage as follows:

public override ComicArticle GetArticle(int counter)
        {
            using (var appStorage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (!appStorage.DirectoryExists(this.comicTitle.Text))
                {
                    return null;
                }

                List<string> articleDirectory = appStorage.GetDirectoryNames
		(string.Format(@"{0}\*", this.comicTitle.Text)).ToList();
                if (articleDirectory.Count <= counter)
                {
                    return null;
                }
                else
                {
                    ComicArticle article = new ComicArticle();
                    string filePath = System.IO.Path.Combine
			(this.comicTitle.Text, articleDirectory[counter]);
                    using (var cachedFile = appStorage.OpenFile
		    (string.Format("{0}\\content.html", filePath), FileMode.Open))
                    using (TextReader reader = new StreamReader(cachedFile))
                    {
                        string html = reader.ReadToEnd();
                        article.html = html;
                        article.htmlFilePath = filePath;                       
                        article.publishDate = articleDirectory[counter];
                    }
                    return article;
                }
            }
        } 

The Result

Dibert Comic Series...

dilbert.png

Phd Comic Series...

Phd.png

xkcd Comic Series...

xkcd.png

Things To Do

While creating this application (Sept 2010), I wanted a quick solution to render the images that I was downloading from the feed. During that time, there was no decoder for GIF images, hence I used a web browser. Recently, ImageTools project at CodePlex has provided support for displaying all GIF images on WP7.

History

  • 30th November, 2010: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here