在Windows Phone 7平台上,移植CPVanity应用并添加CodeProject文章和论坛的RSS阅读器功能,是一种快速而有效的开发方式。本文将详细介绍这一过程,包括必要的前提条件、代码实现以及一些关键的技术点。
为了将CPVanity应用移植到Windows Phone 7平台,首先从桌面版本复制了三个文件:Article.cs、User.cs和CPSite.cs。这些文件是移植过程中的基础。
在开始之前,需要确保已经具备以下条件:
在移植过程中,需要对CPSite类进行一些修改。桌面版本中,CPSite类在后台线程上执行任务,然后在该线程上同步下载HttpWebRequest。由于Windows Phone7的所有网络通信都是异步的,因此需要将后台线程移动到该类中。
以下是修改后的代码示例:
#if WINDOWS_PHONE
public void GetArticlePage(Action 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
在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源使用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);
}
}
其中RssChannel和RssItem的定义如下:
[XmlRoot("channel")]
public class RssChannel
{
public RssChannel() { }
[XmlElement("title")]
public string Title { get; set; }
[XmlElement("item")]
public List 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; }
}
虽然MVVMViewModelLocator在视图和视图模型之间存在1:1映射时工作得很好,但在需要重用页面来渲染不同的视图模型时,它的效果就不那么理想了。ViewModelLocator在XAML中静态设置,创建了1:1的绑定。
为了解决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());
var page = vm.GetType().GetAttribute();
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);
}
}
<controls:Pivot Title="{Binding Name}" ItemsSource="{Binding Items}" ItemTemplate="{StaticResource DynamicContentTemplate}" />