wp新人打造专属windows phone博客园客户端
自从wp8面世,一直都想做wp8开发,苦于各种原因(主要是.net基础不好),一直没有学成。六月底换了新工作, 有幸进入一家wp应用的公司,也开始我正式学习wp开发之路。
拽点废话,类似于小说的背景,各位看官请勿吐槽。
首先,本人使用使用诺基亚将近一年了,每天有个习惯,就是稍微闲着点,就经常逛逛博客园,可以说我的零碎时间,让博客园占去了一大半。但应用商店里下载的各种博客园总觉得不是太好,也说不上来哪里不好。现在机会来了,在学习的过程中,一步步打造自己的专属博客园。
原理很简单,就是通过http请求,抓取博客园的页面,文章列表用ListBox显示,点击后,将地址传到另一个页面,进行详细内容的抓取,显示在WebBrowser中。中间用到了HtmlAgilityPack插件,这个插件也是刚刚接触,上次在我的博文《使用HttpWebRequest和HtmlAgilityPack抓取网页(拒绝乱码,拒绝正则表达式)》中提到过,后来被园友们一通吐槽,给我推荐了Jumony(地址),这个比HtmlAgilityPack牛逼多了,瞬间把HtmlAgilityPack抛弃了。苦逼的是,但我开始动手做的时候,发现Jumony竟然不能在wp8中使用,我又瞬间石化了,然后又把HtmlAgilityPack捡了起来。
下面上帮助类代码,及时Post请求,和Get请求,暂时只用到了Get请求, post请求是准备做用户中心的时候使用的。
public class HttpHelper { public static CookieContainer cc = new CookieContainer(); /// <summary> /// 发起异步post的请求,可用于模拟登陆。 /// </summary> /// <param name="url">post的地址</param> /// <param name="poststr">参数字符串</param> /// <param name="action">回调函数</param> public static void HttpPostAsync(string url, string poststr, Action<string> action) { HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(url); req.Method = "POST"; req.CookieContainer = cc; req.Accept = "application/json, text/javascript, */*; q=0.01"; req.ContentType = "application/x-www-form-urlencoded"; req.AllowAutoRedirect = false; req.BeginGetRequestStream(new AsyncCallback(asy => { HttpWebRequest req1 = (HttpWebRequest)asy.AsyncState; using (Stream stream1 = req1.EndGetRequestStream(asy)) { byte[] postData = Encoding.UTF8.GetBytes(poststr); stream1.Write(postData, 0, postData.Length); } //开始异步接收数据 req1.BeginGetResponse(new AsyncCallback(res => { HttpWebRequest req2 = (HttpWebRequest)res.AsyncState; //结束返回的请求数据 HttpWebResponse response = (HttpWebResponse)req2.EndGetResponse(res); using (Stream stream2 = response.GetResponseStream()) { StreamReader reader = new StreamReader(stream2); string resStr = reader.ReadToEnd(); action(resStr); } }), req1); }), req); } public static void HttpGetAsync(string url,Action<string> action) { if (Utils.GetNetStates()=="None") { action("no network");//无网络 return; } HttpWebRequest req1 = (HttpWebRequest)HttpWebRequest.Create(url); req1.CookieContainer = cc; //开始异步接收数据 req1.BeginGetResponse(new AsyncCallback(res => { try { HttpWebRequest req2 = (HttpWebRequest)res.AsyncState; //结束返回的请求数据 HttpWebResponse response = (HttpWebResponse)req2.EndGetResponse(res); using (Stream stream2 = response.GetResponseStream()) { StreamReader reader = new StreamReader(stream2); string resStr = reader.ReadToEnd(); action(resStr); } } catch (Exception) { action("network anomaly");//请求失败,网络异常 } }), req1); } }
下面是判断手机的网络状态的代码
public class Utils { #region wp联网状态 /// <summary> /// 获取设备当前的网络状态 /// </summary> /// <returns></returns> public static string GetNetStates() { var info = Microsoft.Phone.Net.NetworkInformation.NetworkInterface.NetworkInterfaceType; switch (info) { case Microsoft.Phone.Net.NetworkInformation.NetworkInterfaceType.Ethernet: return "Ethernet"; case Microsoft.Phone.Net.NetworkInformation.NetworkInterfaceType.MobileBroadbandCdma: return "CDMA"; case Microsoft.Phone.Net.NetworkInformation.NetworkInterfaceType.MobileBroadbandGsm: return "GSM"; case Microsoft.Phone.Net.NetworkInformation.NetworkInterfaceType.None: return "None"; case Microsoft.Phone.Net.NetworkInformation.NetworkInterfaceType.Wireless80211: return "Wifi"; default: return "None"; } } #endregion }
第一次做wp应用,有点小丑,先上图吧。
页面首页用Panorama布局,有四个栏目,分别是 首页,热门分类,知识库,(个人中心)暂时没做,后期加上。
首先说首页,主要是抓取博客园首页文章列表,然后分析页面后,加载到ListBox中。这里有个我所认为的难点:到页面滚动到底部的时候自动加载下一页。找了好多资料,最后还是找到了,先把主要代码贴出来
1 #region 获取ListBox的滚动条,实现下拉自动刷新 2 private void RegisterScrollListBoxEvent() 3 { 4 List<ScrollBar> controlScrollBarList = UIHelper.GetVisualChildCollection<ScrollBar>(this.artList); 5 if (controlScrollBarList == null) 6 return; 7 8 foreach (ScrollBar queryBar in controlScrollBarList) 9 { 10 if (queryBar.Orientation == System.Windows.Controls.Orientation.Vertical) 11 queryBar.ValueChanged += queryBar_ValueChanged; 12 } 13 } 14 15 async void queryBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) 16 { 17 if (probar.Visibility == Visibility.Visible) 18 { 19 return; 20 } 21 ScrollBar scrollBar = (ScrollBar)sender; 22 object valueObj = scrollBar.GetValue(ScrollBar.ValueProperty); 23 object maxObj = scrollBar.GetValue(ScrollBar.MaximumProperty); 24 object minObj = scrollBar.GetValue(ScrollBar.MinimumProperty); 25 26 if (valueObj != null && maxObj != null) 27 { 28 double value = (double)valueObj; 29 double max = (double)maxObj; 30 double min = (double)minObj; 31 32 if (value >= Math.Floor(max)) 33 { 34 probar.Visibility = System.Windows.Visibility.Visible; 35 page += 1; 36 await GetHomeArtcle(page); 37 } 38 39 if (Math.Floor(value) <= min) 40 { 41 42 } 43 } 44 } 45 #endregion
只需在页面加载的时候注册下事件就OK了。
下面是抓取的方法代码
1 async Task GetHomeArtcle(int pageindex) 2 { 3 string url = "http://www.cnblogs.com/"; 4 if (pageindex > 1) 5 { 6 url = string.Format("http://www.cnblogs.com/sitehome/p/{0}", pageindex); 7 } 8 await Task.Run(() => 9 { 10 HttpHelper.HttpGetAsync(url, html => 11 { 12 if (html == "no network") 13 { 14 Dispatcher.BeginInvoke(() => 15 { 16 MessageBox.Show("sorry,貌似您没有联网哦"); 17 }); 18 return; 19 } 20 if (html == "network anomaly") 21 { 22 Dispatcher.BeginInvoke(() => 23 { 24 MessageBox.Show("sorry,您的网络貌似有异常,请检查后再试吧!"); 25 }); 26 return; 27 } 28 HtmlDocument doc = new HtmlDocument(); 29 doc.LoadHtml(html); 30 var artlist = doc.DocumentNode.SelectNodes("//div[@class='post_item']"); 31 List<art> art1 = new List<art>(); 32 foreach (var item in artlist) 33 { 34 HtmlDocument adoc = new HtmlDocument(); 35 adoc.LoadHtml(item.InnerHtml); 36 var html_a = adoc.DocumentNode.SelectSingleNode("//a[@class='titlelnk']"); 37 var html_content = adoc.DocumentNode.SelectSingleNode("//p[@class='post_item_summary']"); 38 var comment = adoc.DocumentNode.SelectNodes("//a[@class='gray']"); 39 html_content.RemoveChild(html_content.FirstChild, true); 40 var foot = adoc.DocumentNode.SelectSingleNode("//div[@class='post_item_foot']"); 41 foot.RemoveAll(); 42 art1.Add(new art 43 { 44 Title = html_a.InnerText, 45 Content = html_content.InnerText, 46 Comment = comment.First().InnerText.Replace("\r\n", ""), 47 Read = comment.Last().InnerText, 48 AddTime = foot.InnerText, 49 Link = html_a.Attributes["href"].Value 50 }); 51 //Response.Write(string.Format("标题为:{0},链接为:{1}<br>", html_a.InnerText, html_a.Attributes["href"].Value)); 52 } 53 Dispatcher.BeginInvoke(() => 54 { 55 for (int i = 0; i < artlist.Count; i++) 56 { 57 artsource.Add(art1[i]); 58 } 59 60 probar.Visibility = System.Windows.Visibility.Collapsed; 61 }); 62 }); 63 }); 64 }
另外,定义了抓页面所需要的类
1 public class art 2 { 3 private string title; 4 public string Title 5 { 6 get { return title; } 7 set 8 { 9 title = value; 10 NotifyPropertyChanged("Title"); 11 } 12 } 13 private string content; 14 public string Content 15 { 16 get { return content; } 17 set 18 { 19 content = value; 20 NotifyPropertyChanged("Content"); 21 } 22 } 23 private string comment; 24 /// <summary> 25 /// 评论 26 /// </summary> 27 public string Comment 28 { 29 get { return comment; } 30 set 31 { 32 comment = value; 33 NotifyPropertyChanged("Comment"); 34 } 35 } 36 private string read; 37 /// <summary> 38 /// 阅读 39 /// </summary> 40 public string Read 41 { 42 get { return read; } 43 set 44 { 45 read = value; 46 NotifyPropertyChanged("Read"); 47 } 48 } 49 50 /// <summary> 51 /// 发布时间 52 /// </summary> 53 public string AddTime { get; set; } 54 public string Link { get; set; } 55 public event PropertyChangedEventHandler PropertyChanged; 56 private void NotifyPropertyChanged(string info) 57 { 58 if (PropertyChanged != null) 59 { 60 PropertyChanged(this, new PropertyChangedEventArgs(info)); 61 } 62 } 63 } 64 65 public class ArtCollection : ObservableCollection<art> 66 { 67 public ArtCollection() 68 : base() 69 { 70 71 } 72 }
这些就是首页栏目的主要代码了,下面会把源码贴出来,所以就不一一贴在这里了。
绑定后列表后,就要绑定Tap事件了,个人觉得这个事件可以理解为web开发中的单击事件。代码如下:
1 private void Border_Tap(object sender, System.Windows.Input.GestureEventArgs e) 2 { 3 try 4 { 5 string link = ((Border)sender).Tag.ToString();//获取页面链接 6 NavigationService.Navigate(new Uri(string.Format("/show.xaml?linkurl={0}", link), UriKind.Relative)); 7 } 8 catch (Exception) 9 { 10 11 } 12 }
其次说热门分类
热门分类没有做抓取,只是手工将博客园中的一级分类写出来,把单击事件里传递对应的地址,到列表页面。
列表页面是采用的Pivot布局,将分类依次排列,当从热门分类中跳转过来时,根据参数进行判断,加载对应的分类列表
1 protected override void OnNavigatedTo(NavigationEventArgs e) 2 { 3 base.OnNavigatedTo(e); 4 if (e.NavigationMode == NavigationMode.New) 5 { 6 IDictionary<string, string> queryString = this.NavigationContext.QueryString; 7 var selectedItem = pivot.Items.First(item => 8 { 9 PivotItem temp = (PivotItem)item; 10 return temp.Tag.ToString() == queryString["type"]; 11 });//查找对应的PivotItem 12 pivot.SelectedItem = selectedItem;//设置为当前Item 13 // await GetHomeArtcle(pagedb, queryString["type"]); 14 } 15 }
当Pivot进行切换的时候,触发SelectionChanged事件,在事件中,首先判断当前的Item是否已经加载数据,如果尚未加载,就加载,如果已经加载了, 就不做操作了。
1 private async void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e) 2 { 3 PivotItem item = (PivotItem)pivot.SelectedItem; 4 ListBox tempList = (ListBox)item.Content; 5 if (tempList.Items.Count==0) 6 { 7 probar.Visibility = System.Windows.Visibility.Visible; 8 await GetHomeArtcle(1, item.Tag.ToString()); 9 // tempList.Tag = (page + 1).ToString(); 10 } 11 12 }
忽然觉得代码太多了,先写到这吧。 下班回家啦。
博客园的编辑器怎么没找到上传附件的呢???
把源码上传到了云盘里, 下面是地址http://yunpan.cn/Qh4xxMwEHmutb 提取码 d3b4(已失效,360云盘分享只能被下载5次, 我无力吐槽了啊)
百度网盘分享链接:链接: http://pan.baidu.com/s/1bnjayNP 密码: ijch
微云分享链接 链接:http://url.cn/Pqa3vH (密码:7Gim)
已经上传到应用商店了,需要吐槽的点这里:http://www.windowsphone.com/zh-cn/store/app/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E5%AE%A2%E6%88%B7%E7%AB%AF/4ceda3f2-bef6-4231-a6bb-b8d9a0cc7b87
未打完,也要收工。一是东西太多了, 本来打算分几次写的,后来觉得太麻烦了。二是,本人表达能力有限,看不明白的直接看源码吧,源码有注释哦。