65.9K
CodeProject 正在变化。 阅读更多。
Home

WP7漫画阅读器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (4投票s)

2010年11月30日

CPOL

2分钟阅读

viewsIcon

30596

downloadIcon

758

介绍如何构建一个简单的Windows Phone 7应用程序,用于读取和存储WP7中的漫画feed。

引言

在本文中,我将解释如何创建一个简单的应用程序,它可以读取 Dilbert、xkcd 和 Phd 漫画的订阅源,并在 WP7 上显示它们。通过对代码进行简单的修改,您可以添加其他各种漫画系列的订阅源,并在您的 Windows Phone 7 上查看它们。

背景

感谢微软提供一个出色的平台,用于使用 Silverlight 进行 WP7 应用程序开发。它简单、快速且有趣。

Using the Code

让我们从 XAML 开始。

home_screen.png

漫画阅读器的主屏幕很容易创建。我有一个包含两行一列的网格。第一个网格包含一个堆叠面板,其中包含标题“Comic Reader”,而另一个网格包含一个 Listbox,其中托管着每种漫画类型的按钮。

<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>	  

在代码背后,我们通过以下方式强制横向方向:

this.SupportedOrientations = SupportedPageOrientation.PortraitOrLandscape;

每个漫画订阅源都有三个最重要的标签来提供:

  1. 漫画文章的图片
  2. 漫画文章的标题
  3. 文章描述

基于这个假设,comicpage.xaml,即托管漫画文章的地方,如下所示:

<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> 

在内容加载到浏览器之前,我使用上面显示的故事板动画运行加载器。

代码组织方式如何?

当我们查看代码时,我们有 ComicFeed 类,其中包含一个 ComicArticle 对象列表。每个 ComicArticle 对象包含所有显示ComicPage.xaml 上漫画文章的信息。

虽然所有订阅源都相似,但由于订阅源存在细微差异,我们需要分别解析每个订阅源。因此,对于每个漫画系列,我们都有一个单独的类 DilbertFeedXkcdFeed PhdFeed ,它们继承自 ComicFeed 类。

ComicFeed 类的编写方式如下:

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;
    } 

默认情况下,每个漫画订阅源的文章数量限制为 4。当前的实现将漫画文章存储在隔离存储中,因此需要限制漫画文章的数量,以防止我们的应用程序消耗隔离存储中的所有空间。

对于每个漫画系列,将在隔离存储中创建一个相应的文件夹,并将文章存储在这些文件夹中。GetArticles(int articleNumber) 用于从隔离存储中检索文章,如下所示:

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;
                }
            }
        } 

结果

Dibert 漫画系列...

dilbert.png

Phd 漫画系列...

Phd.png

xkcd 漫画系列...

xkcd.png

待办事项

在创建此应用程序(2010 年 9 月)时,我想要一个快速的解决方案来渲染从订阅源下载的图像。当时没有 GIF 图像解码器,因此我使用了 Web 浏览器。最近,CodePlex 上的 ImageTools 项目提供了在 WP7 上显示所有 GIF 图像的支持。

历史

  • 2010 年 11 月 30 日:初始发布
© . All rights reserved.