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

Windows Phone 7的CP Vanity

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (16投票s)

2010年12月14日

CPOL

3分钟阅读

viewsIcon

82490

downloadIcon

354

一个带有 CodeProject RSS 阅读器的 WP7 版 CPVanity。

引言

这个应用开始(并且大部分仍然是)Luc PattynCPVanity 应用到 Windows Phone 7 平台的一个快速而粗糙的移植。完成之后,似乎缺少一个 CodeProject 文章和论坛的 RSS 阅读器。

为什么?因为如果我们还没有一个,我们绝对需要一个!

screenshot4.png

screenshot4.png

screenshot4.png

screenshot4.png

screenshot4.png

screenshot4.png

背景

为了移植 CPVanity,从桌面版本复制了三个文件

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

必备组件

你需要一些东西才能使用这段代码

为了让 CPSite (下载和抓取 HTML 内容的地方) 工作,需要进行一些小的更改。首先,桌面版本告诉 CPSite 类在后台线程上执行其工作。 然后 CPSite 在该线程上创建 HttpWebRequest 并在该线程上同步下载。 由于所有 WP7 Web 通信在调用时都是异步的,因此需要将后台线程移动到该类中。

所以有一些 #if WINDOWS_PHONE 散布在各处

#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

然后,它只是使用 WebClient 来获取 HTML 内容

#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

然后在 ViewModel 中,响应于用户 ID 的更改,它执行

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

其余的只是普通的 Silverlight 数据绑定来填充 UI。

RSS 阅读器

检索和显示 RSS 源使用 WebClient,还有一些基本的 XML 序列化。

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

其中 RssChannelRssItem 看起来像这样

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

视图到 ViewModel 的绑定

虽然 MVVM ViewModelLocator 在视图和视图模型之间存在 1:1 映射时效果很好,但当您想要重用 Page 来呈现不同的 ViewModel 时,它的效果就不是那么好。 ViewModelLocator 在 XAML 中静态设置,创建 1:1 绑定。

由于论坛和文章 RSS 源的呈现是相同的,并且 ViewModel(以及背后的 Models)略有不同,所以我最终得到一个页面和两个 ViewModel

为了解决 ViewModelLocator 中的 1:1 映射,使用 Attribute 来标记 ViewModel

[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{}

然后在处理导航事件的代码中

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

页面代码隐藏确实需要做一些工作,因为所需的 ViewModel 在 URI 的查询字符串中指定

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

ViewViewModel 之间的链接仍然是声明式的和松散耦合的,但它也有一个额外的自由度,这似乎是有利的,因为单个页面可以呈现任何 ViewModel,只要它公开两个属性:NameItems

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

关注点

我学到的最有趣的事情是,一旦你弄清楚了平台,WP7 开发就非常容易。 这只花了几小时就完成了(当然,大部分都是抄袭原始作品)。

历史

  • 2010/12/14 - 初始上传
  • 2010/12/17 - 添加了进度条和更大的 rep 图像页面
  • 2010/12/27 - 添加了 RSS 阅读器
  • 2013/1/17 - 在 WP8 上测试,更新以适应网站更改,错误修复,从游戏中心移除
© . All rights reserved.