Windows Phone 7的CP Vanity






4.98/5 (16投票s)
一个带有 CodeProject RSS 阅读器的 WP7 版 CPVanity。
引言
这个应用开始(并且大部分仍然是)Luc Pattyn 的 CPVanity 应用到 Windows Phone 7 平台的一个快速而粗糙的移植。完成之后,似乎缺少一个 CodeProject 文章和论坛的 RSS 阅读器。
为什么?因为如果我们还没有一个,我们绝对需要一个!
| |
| |
| |
背景
为了移植 CPVanity
,从桌面版本复制了三个文件
- Article.cs
- User.cs
- CPSite.cs
必备组件
你需要一些东西才能使用这段代码
- WP7 SDK 和相关工具 - 原因很明显
- GalaSoft 的 MVVM Light - 用于 MVVM 支持
- 用于 Windows Phone 的 Silverlight 工具包 - 用于页面转换
为了让 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);
}
}
其中 RssChannel
和 RssItem
看起来像这样
[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);
}
}
View
和 ViewModel
之间的链接仍然是声明式的和松散耦合的,但它也有一个额外的自由度,这似乎是有利的,因为单个页面可以呈现任何 ViewModel
,只要它公开两个属性:Name
和 Items
。
<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 上测试,更新以适应网站更改,错误修复,从游戏中心移除