Silverlight入门系列的第三部分

这是 Silverlight入门 系列的第三部分。点击 这里可以查看本系列的目录。

现在我们已经有了一个基础布局,同时也在上面放了一些 控件,接下来让我们开始获取数据。因为我们想要 搜索Twitter上的内容,所以我们需要充分使用他们提供的Web服务 API。在继续之前,我要先说明在本次的应用程序中,我们不会自己建立一个数据库或其它数据源,但是我要指出的是你有很多种方式可以通过 Silverlight访问数据。

数据访问选项

对于在Silverlight中访问数据,初学者的误解之一就是他们在Silverlight中寻找ADO.NET类库。别找了,找不到的。记住,Silverlight是部署在互联网上的客户端技术,你不能要求一个浏览器 插件去直接访问你的数据库……除非你想把数据库直接暴露在网络上。我们都知道绝对不能这么做。

所以比较可行的方法是在服务层上暴露数据。这也是Silverlight进行数据通信的方式。这里有一些主要的访问手段:

    * Web服务: SOAP, ASP.NET web services (ASMX), WCF services, POX, REST 终端
    * 套接字: 网络套接字通信(Network Socket Communication)
    * 文件: 通过Web请求访问静态内容

套接字

套接字可能是最先进的数据访问终端。你需要有一个套接字主机,并且在我写这篇文章的时候,还需要通过指定的端口范围通信。如果这些你都可以接受,那么这是非常有效且强大的数据访问方式。但如果你的应用程序是公开面向网络的,那么我并不认为这种方式可以成为主流。就我的观点,这种方式更加通常被用于商业应用。这里有一些套接字的资料:

    * 软件开发工具包(SDK)文档
    * 通过套接字访问数据 (Dan Wahlin, MVP) – 这是Silverlight2的资料,但不会影响你理解概念

在决定使用它之前,你必须先真正理解你的部署方案,不要盲目地使用它。

文件访问

Silverlight可以与本地文件或网络文件进行交互。对于访问本地文件,虽然应用程序无法直接访问文件系统,但是仍然可以通过OpenFileDialog和SaveFileDialog来让用户自己保存数据流到本地,从而进行读写操作。

此外,你还可以通过Silverlight使用标准HTTP命令来读写网上的纯文本文件以及XML文件信息。下面是使用这些方法的一些辅助资料。

    * 打开文件对话框与文件上传
    * 保存文件对话框

你可能发现自己正是用这种方式来保存程序设置数据或者进行简单的数据访问的。

Web服务(Web Services)

这是Silverlight访问数据的核心——通过服务层。Silverlight支持在VS中使用我们熟悉的添加服务引用的方式来访问基础ASP.NET Web Services(ASMX)或者基于WCF的服务,并且还将会为你生成强类型的代理代码。

另外,你还可以通过标准HTTP协议访问POX(Plain old XML)或基于REST的终端。理解这些不同服务类型的应用场合,通常是开发人员学会了解自己的项目最适合什么数据访问方式的最佳途径。这里有一些资料:

    * 使用WCF和ASP.NET Web服务
    * 通过HTTPS调用服务
    * .NET RIA 服务
    * 选择数据访问层 (Shawn Wildermuth,微软最有价值专家)

上面的第三项.NET RIA服务是一个新的 框架,旨在使数据访问更加简单方便。链接的 视频将会为你介绍那个主题。如果你有一个与你的Silverlight应用程序放在一起的数据库,那么如果想要让这个数据库为你的Silverlight应用程序服务的话,.NET RIA服务是最好的方法。

异步访问

所有在Silverlight中进行的数据访问都是异步的。这可能是专门从事Web开发的开发人员跃跃欲试的另一个领域。比如在服务器端,这样的写法看起来可能很合理:
  1. 1 MyWebService svc = new MyWebService();
  2. 2 string foo = svc.GetSomeValue();
  3. 3 MyTextBox.Text = foo;
复制代码
在Sliverlight中你就不能用这种 同步调用的做法了。对于那些没有作过异步编程的开发人员来说可能会觉得迷茫,但这是值得学习的,因为通过它你会成为一名更优秀的开发人员。在Silverlight中,上面的伪代码应该被写成这样:
  1. 1 MyWebService svc = new MyWebService();
  2. 2 svc.Completed += new CompletedHandler(OnCompleted);
  3. 3 svc.GetSomeValue();
  4. 4
  5. 5 void OnCompleted(object sender, EventArgs args)
  6. 6 {
  7. 7    MyTextBox.Text = args.Result;
  8. 8 }
复制代码
注意到你在一个Completed事件处理器中使用服务返回的结果,这个调用模式你将会在基本数据访问中经常看到。
TOP
 

  
跨域数据访问

因为Silverlight是 Web客户端技术,所以它运行于浏览器的安全沙箱(Sand Box)里,并在访问策略上有所限制。限制的其中之一就是跨域访问。除非某个服务设置成允许跨域调用,否则如果你的应用程序与想要调用的服务处于不同的域名,你是没有办法访问它的。设置这个的途径通常是通过跨域策略文件。Silverlight与其它富客户端插件一样,都要遵循这种策略。作为 Silverlight开发人员你可能会在有些时候遇到这个问题,晚学不如早学,这里有一些资料:

    * Silverlight中的跨域策略文件
    * 跨域策略文件助手
    * 跨域访问的疑难解答以及有用的工具
    * 我的Silverlight无法访问我的服务!
    * 跨域通信概述

在我们的Twitter应用程序中,我们实际上是在访问承载在其它地方的服务,所以我们需要遵循这些协议。幸运的是,Twitter查询API通过他们的跨域策略文件开启了跨域访问( http://search.twitter.com/crossdomain.xml)。 Twitter的其它功能并没有开启跨域访问,这就是为什么你现在不能通过Silverlight直接访问Twitter内容。在这种情况下你将通过自己的服务来调用那些代理服务,使得你可以通过Silverlight的策略文件开启跨域访问。头晕吗?其实它比听起来简单多了。

广泛流传的说法:你需要将Silverlight与Adobe跨域策略文件放在服务端以开启跨域访问。这个说法是不正确的,我常常看见有人说“我已经准备好了 crossdomain.xml和clientaccesspolicy.xml,但它仍然无法工作。”,如果你为Silverlight创建一个跨域访问服务,你只需要clientaccesspolicy.xml文件格式就行了,这也是我们最先找到的,同时对Silverlight来说也最灵活、最安全。

现在我们已经获得了总体的概念,让我们来访问数据吧!

调用Twitter API


Twitter 搜索API是一个简单的基于API的REST——我们只想在应用程序中调用GET请求。他们提供的格式符合Atom规范(Atom Specification),使得我们的工作容易得多,因为这是基础格式,同时受到Silverlight框架库的直接支持。当用户在查询输入框中输入内容,并点击查询按钮以后,我们将会调用这个API。让我们像在第一章创建Hello World示例的那样,为搜索按钮关联一个事件。在我们的Search.xaml页面,我为搜索按钮添加了一个事件。添加按钮事件处理器到查询按钮,并且将方法名称命名为SearchForTweets:
  1. 1 <Button x:Name="SearchButton" Width="75" Content="SEARCH" Click="SearchForTweets" />
复制代码
在 VS中,如果你右击方法名称,你就能导航到事件处理器,并且它会在代码页自动为你生成代码存根。在这个方法中,我们将寻找并使用符合我们标准的 Twitter API。因为这个API是一个简单的REST GET调用,所以我们准备使用简单的WebClient Silverlight API。这是最简单的网络API,允许你通过GET/POST命令来读写数据,前提是你不需要改变头记录。在这之前我还准备了一些成员变量用于追踪并监视我们的查询条目。
  1. 1 const string SEARCH_URI = "http://search.twitter.com/search.atom?q={0}&since_id={1}";
  2. 2 private string _lastId = "0";
  3. 3 private bool _gotLatest = false;
复制代码
现在我们可以建立Twitter搜索功能了。记得我曾经提到在Silverlight中的网络访问是异步的吗?现在就让我们来体验一下。我们将在 WebClient中使用OpenRead API。因为整个过程是异步的,所以我们需要建立一个Completed事件处理器,通过它来接收返回数据并且执行我们需要的操作,像是这样:
  1. 1  private void SearchForTweets(object sender, RoutedEventArgs e)
  2. 2  {
  3. 3      WebClient proxy = new WebClient();
  4. 4      proxy.OpenReadCompleted += new OpenReadCompletedEventHandler(OnReadCompleted);
  5. 5      proxy.OpenReadAsync(new Uri(string.Format(SEARCH_URI, HttpUtility.UrlEncode(SearchTerm.Text), _lastId)));
  6. 6  }
  7. 7
  8. 8  void OnReadCompleted(object sender, OpenReadCompletedEventArgs e)
  9. 9  {
  10. 10      throw new NotImplementedException();
  11. 11 }
复制代码
注意到我们首先创建了一个WebClient实例,然后我们设置了一个Completed事件处理器,最后我们调用了OpenReadAsync方法并传入了一个URI。我们在Completed事件处理器中获取的返回结果(e.Result)将会是数据流。因为我们准备处理返回数据并作数据绑定,所以我们准备创建一个可以用于表示搜索结果的结构的本地类(实体类)。我把它的名字命名为TwitterSearchResult.cs并且放在项目的Model 文件夹下。
  1. 1  using System;
  2. 2  using System.Windows.Media;
  3. 3
  4. 4  namespace TwitterSearchMonitor.Model
  5. 5  {
  6. 6      public class TwitterSearchResult
  7. 7      {
  8. 8          public string Author { get; set; }
  9. 9          public string Tweet { get; set; }
  10. 10        public DateTime PublishDate { get; set; }
  11. 11        public string ID { get; set; }
  12. 12        public ImageSource Avatar { get; set; }
  13. 13    }
  14. 14 }
复制代码
我们可以在实体类的内部构造输出结果或绑定数据。
TOP
 

  
其它网络选项:HttpWebRequest和ClientHttp

我们还可以使用另外两个网络API来访问Twitter API,它们分别是HttpWebRequest和ClientHttp。我们通常通过WebClient来使用HttpWebRequest,因为它是围绕这个API制作的简单包装器。但如果你需要对请求的头部作更加精细的控制,那么你应该使用HttpWebRequest。它们都用到了浏览器的网络协议栈,因此也就带来了一些局限性,那就是不能接收所有的HTTP状态码或是使用一些扩展变量(PUT/DELETE)。Silverlight还推出了一个使用自定义网络协议栈的ClientHttp,使你可以使用更多的变量,还能接收到除了200、404之外的其它HTTP状态码。

更多内容:

    * 如何:指定浏览器或客户端 HTTP 处理原版

下面是一个调用ClientHttp的例子:
  1. 1 private void SearchForTweets(object sender, RoutedEventArgs e)
  2. 2 {
  3. 3    bool httpBinding = WebRequest.RegisterPrefix("http://search.twitter.com",
  4.                                                           WebRequestCreator.ClientHttp);
  5. 4    WebClient proxy = new WebClient();
  6. 5    proxy.OpenReadCompleted += new OpenReadCompletedEventHandler(OnReadCompleted);
  7. 6    proxy.OpenReadAsync(new Uri(string.Format(SEARCH_URI, HttpUtility.UrlEncode(SearchTerm.Text))));
  8. 7 }
复制代码
注意,我们并没有真的在应用程序中使用这个方法,而仅仅只是向你展示它。调用RegisterPrefix方法意味着我们想用ClientHttp网络协议栈来替代浏览器的网络协议栈。在上面的例子中,我们仅仅传入了Twitter的搜索域名,但是实际上我们可以传入任何HTTP请求。

这些对你来说是需要在应用程序中考虑的额外内容。

开始通过智能对象绑定一些简单的数据

因为我们的应用程序需要监视Twitter上面的搜索内容,所以我们需要将结果绑定到一个对象集合中,然后再只对那个集合进行操作(在这个例子中,我们所做的是向里面添加内容)。为此,我们要使用Silverlight中两个有用的对象:ObservableCollection<T> 和PagedCollectionView。ObservableCollection是一种当它的元素被改动时(包括新增、删除、修改)可以自动提供通知的集合类型,PagedCollectionView将帮助我们为对象自动排序。

我们在项目中定义这些成员变量:
  1. 1 ObservableCollection<TwitterSearchResult> searchResults = new ObservableCollection<TwitterSearchResult>();
  2. 2 PagedCollectionView pcv;
复制代码
现在我们已经定义完成员变量了,接下来让我在控件的构造器中初始化PagedCollectionView,好让它尽可能快地可用。我们还需要在XAML中将用户界面绑定到元素上面。不在你的用户控件的构造器中做任何控制用户界面的操作是一个好做法(在我们的例子中是Search.xaml)。正因

如此,我们将在构造器中添加一个Loaded事件处理器,并且在那里执行初始化数据绑定。构造器和事件处理器中的内容应该像是这样:
  1. 1 public Search()
  2. 2 {
  3. 3    InitializeComponent();
  4. 4
  5. 5    pcv = new PagedCollectionView(searchResults);
  6. 6    pcv.SortDescriptions.Add(new System.ComponentModel.SortDescription("PublishDate", System.ComponentModel.ListSortDirection.Ascending));
  7. 7
  8. 8    Loaded += new RoutedEventHandler(Search_Loaded);
  9. 9 }
  10. 10
  11. 11 void Search_Loaded(object sender, RoutedEventArgs e)
  12. 12 {
  13. 13    SearchResults.ItemsSource = pcv;
  14. 14 }
复制代码
注意到我们在Loaded事件处理器中将已经排好序的PagedCollectionView(我们在第6行调用了SortDescription)赋值给了DataGrid(名称叫SearchResults)的ItemsSource属性。现在我们的用户界面已经绑定到了 PagedCollectionView,也许我们应该填充数据了。记住它实际上是我们的 ObservableCollection<TwitterSearchResult>的数据的视图,所以我们要向其中添加项目以查看变化。
TOP
 

  
填充ObservableCollection

现在回到之前创建的OnReadCompleted方法,我们现在要填充ObservableCollection到这个方法中:
  1. 1  void OnReadCompleted(object sender, OpenReadCompletedEventArgs e)
  2. 2  {
  3. 3      if (e.Error == null)
  4. 4      {
  5. 5          _gotLatest = false;
  6. 6          XmlReader rdr = XmlReader.Create(e.Result);
  7. 7
  8. 8          SyndicationFeed feed = SyndicationFeed.Load(rdr);
  9. 9
  10. 10        foreach (var item in feed.Items)
  11. 11        {
  12. 12            searchResults.Add(new TwitterSearchResult() { Author = item.Authors[0].Name, ID = GetTweetId(item.Id), Tweet = item.Title.Text, PublishDate = item.PublishDate.DateTime.ToLocalTime(), Avatar = new BitmapImage(item.Links[1].Uri) });
  13. 13            _gotLatest = true;
  14. 14        }
  15. 15
  16. 16        rdr.Close();
  17. 17    }
  18. 18    else
  19. 19    {
  20. 20        ChildWindow errorWindow = new ErrorWindow(e.Error);
  21. 21        errorWindow.Show();
  22. 22    }
  23. 23 }
  24. 24
  25. 25 private string GetTweetId(string twitterId)
  26. 26 {
  27. 27    string[] parts = twitterId.Split(":".ToCharArray());
  28. 28    if (!_gotLatest)
  29. 29    {
  30. 30        _lastId = parts[2].ToString();
  31. 31    }
  32. 32    return parts[2].ToString();
  33. 33 }
复制代码
这里做了几件事,首先,e.Result包含了我们搜索成功所返回的数据流,如果搜索失败了,我们将使用我们的导航模板提供的ErrorWindow模板。 _gotLatest成员变量可以帮助我们跟踪我们是否需要重新设置最大值(我们只想返回最新的查询结果)。之后我们将获得的数据流加载到 XmlReader,以便于用SyndicationFeed类来解析它。SyndicationFeed在 System.ServiceModel.Syndication类库中,所以你不得不在项目中添加引用。它的内置方法可以处理像是RSS、Atom这样的已知聚合格式。

注意:System.ServiceMode.Syndication使得你的项目还要依赖其它程序集。这个类库虽然不小,但却也很方便。谨慎地使用它,你应该了解使用它的时机和原因。我们在这里使用它是为了让你感觉到它带来的开发效率和功能。还有另一种方法(常见于聚合新闻阅读器)就是仅使用LINQ to SQL,在读取以后查询结果XDocument。同样,出于演示的目的,我想指出作为强类型的类,SyndicationFeed能提升你的开发效率。

更多关于读取聚合数据的资料:

    * 处理聚合数据

一旦我们有了已经加载数据的SyndicationFeed,我们只需要遍历它然后将新的Twitter搜索结果添加到 ObservableCollection对象里。你会注意到我们为了之后绑定更加简单,将图片的URL转换成了图片源(ImageSource)。此外,我们还解析出Twitter的ID,用于设置首个结果(最新的结果),并把它作为上次查询之后最新的ID(_LastId)。

为用户提供回馈

在最后一步,我们想确保当我们执行操作的时候(搜索),用户可以得到一些回馈。幸运的是这很容易通过ActivityControl做到,在我写这篇文章的时候,ActivityControl是.NET RIA Services模板的一部分,但你可以从 大卫的投票博客获取它。你需要自己编译这个控件,然后在项目中添加对它的引用(如果你下载我们的示例应用程序源代码,那么它已经被编译好并被包含在了类库文件夹中)。

添加引用以后,你就可以像我们第二部分中添加DataGrid那样在Search.xaml中添加xaml标记。我们添加这个控件作为根控件,然后把DataGrid作为它的子控件包含在其中。最后我们的Search.xaml文件看起来应该像这样:
  1. 1  <navigation:Page
  2. 2          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. 3          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. 4          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. 5          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. 6          mc:Ignorable="d"
  7. 7          xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
  8. 8          xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="TwitterSearchMonitor.Views.Search"
  9. 9          xmlns:activity="clr-namespace:System.Windows.Controls;assembly=ActivityControl"
  10. 10        d:DesignWidth="640" d:DesignHeight="480"
  11. 11        Title="Twitter Search Page">
  12. 12    <activity:Activity x:Name="ActivityIndicator">
  13. 13          <Grid x:Name="LayoutRoot">
  14. 14              <Grid.RowDefinitions>
  15. 15                  <RowDefinition Height="32"/>
  16. 16                  <RowDefinition/>
  17. 17              </Grid.RowDefinitions>
  18. 18
  19. 19              <StackPanel HorizontalAlignment="Left" Margin="0,-32,0,0" VerticalAlignment="Top" Grid.Row="1" Orientation="Horizontal">
  20. 20                  <TextBox x:Name="SearchTerm" FontSize="14.667" Margin="0,0,10,0" Width="275" TextWrapping="Wrap"/>
  21. 21                  <Button x:Name="SearchButton" Width="75" Content="SEARCH" Click="SearchForTweets" />
  22. 22              </StackPanel>
  23. 23              <data:DataGrid x:Name="SearchResults" Margin="0,8,0,0" Grid.Row="1"/>
  24. 24          </Grid>
  25. 25    </activity:Activity>
  26. 26 </navigation:Page>
复制代码
在开始搜索处还需要添加一行代码:
  1. 1 ActivityIndicator.IsActive = true;
复制代码
在OnReadCompleted事件完成以后添加:
  1. 1 ActivityIndicator.IsActive = false;
复制代码
这样终端用户就能看到一个滚动的进度条。现在让我们按F5(运行程序),并且输入一些搜索条件来看看效果:

_23368
gs3-loadingactivity_2.png(35.86 K)
1/19/2010 1:42:31 PM


在DataGrid中显示的搜索结果视图:

_23369
gs3-resultingwindow_2.png(35.55 K)
1/19/2010 1:42:31 PM
TOP
 

  
添加一些计时器

既然我们要做一个监视服务,那么我们当然需要让程序自动刷新查询结果。Silverlight提供了几种不同的方式来触发自动活动。我们准备在应用程序中使用DispatcherTimer,它只是一个在指定时间间隔触发事件的计时器。我们将添加一个成员变量:
  1. 1 DispatcherTimer _timer;
复制代码
然后在构造器中初始化我们的计时器并传入一个事件处理器:
  1. 1 double interval = 30.0;
  2. 2
  3. 3 _timer = new DispatcherTimer();
  4. 4 #if DEBUG
  5. 5 interval = 10.0;
  6. 6 #endif
  7. 7 _timer.Interval = TimeSpan.FromSeconds(interval);
  8. 8 _timer.Tick += new EventHandler(OnTimerTick);
复制代码
现在我们要重构一些代码。我们想去掉时间事件的SearchForTweets方法。我们将使用Visual Studio的重构工具把SearchForTweets的方法提取到新的方法"SearchForTweetsEx"中,我们还将为它生成一个叫做 OnTimerTick的事件处理器。我们还要修改Loaded事件来启动计时器,同时还初始化搜索(注意在调试模式下间隔为10秒,否则为30秒)。我们重构完成的Search.xaml看起来应该像这样:
  1. 1    using System;
  2. 2    using System.Collections.ObjectModel;
  3. 3    using System.Net;
  4. 4    using System.Net.Browser;
  5. 5    using System.ServiceModel.Syndication;
  6. 6    using System.Windows;
  7. 7    using System.Windows.Browser;
  8. 8    using System.Windows.Controls;
  9. 9    using System.Windows.Data;
  10. 10  using System.Windows.Media.Imaging;
  11. 11  using System.Windows.Navigation;
  12. 12  using System.Windows.Threading;
  13. 13  using System.Xml;
  14. 14  using TwitterSearchMonitor.Model;
  15. 15
  16. 16  namespace TwitterSearchMonitor.Views
  17. 17  {
  18. 18      public partial class Search : Page
  19. 19      {
  20. 20          const string SEARCH_URI = "http://search.twitter.com/search.atom?q={0}&since_id={1}";
  21. 21          private string _lastId = "0";
  22. 22          private bool _gotLatest = false;
  23. 23          ObservableCollection<TwitterSearchResult> searchResults = new ObservableCollection<TwitterSearchResult>();
  24. 24          PagedCollectionView pcv;
  25. 25          DispatcherTimer _timer;
  26. 26
  27. 27          public Search()
  28. 28          {
  29. 29              InitializeComponent();
  30. 30
  31. 31              // set interval value for Timer tick
  32. 32              double interval = 30.0;
  33. 33
  34. 34              _timer = new DispatcherTimer();
  35. 35              #if DEBUG
  36. 36              interval = 10.0;
  37. 37              #endif
  38. 38              _timer.Interval = TimeSpan.FromSeconds(interval);
  39. 39              _timer.Tick += new EventHandler(OnTimerTick);
  40. 40
  41. 41              // initialize our PagedCollectionView with the ObservableCollection
  42. 42              // and add default sort
  43. 43              pcv = new PagedCollectionView(searchResults);
  44. 44              pcv.SortDescriptions.Add(new System.ComponentModel.SortDescription("PublishDate", System.ComponentModel.ListSortDirection.Descending));
  45. 45
  46. 46              Loaded += new RoutedEventHandler(Search_Loaded);
  47. 47          }
  48. 48
  49. 49          void OnTimerTick(object sender, EventArgs e)
  50. 50          {
  51. 51              SearchForTweetsEx();
  52. 52          }
  53. 53
  54. 54          void Search_Loaded(object sender, RoutedEventArgs e)
  55. 55          {
  56. 56              SearchResults.ItemsSource = pcv; // bind the DataGrid
  57. 57              _timer.Start(); // start the timer
  58. 58              SearchForTweetsEx(); // do the initial search
  59. 59          }
  60. 60
  61. 61          // Executes when the user navigates to this page.
  62. 62          protected override void OnNavigatedTo(NavigationEventArgs e)
  63. 63          {
  64. 64          }
  65. 65
  66. 66          private void SearchForTweets(object sender, RoutedEventArgs e)
  67. 67          {
  68. 68              SearchForTweetsEx();
  69. 69          }
  70. 70
  71. 71          /// <summary>
  72. 72          /// Method that actually does the work to search Twitter
  73. 73          /// </summary>
  74. 74          private void SearchForTweetsEx()
  75. 75          {
  76. 76              if (!string.IsNullOrEmpty(SearchTerm.Text))
  77. 77              {
  78. 78                  _timer.Stop(); // stop the timer in case the search takes longer than the interval
  79. 79                  ActivityIndicator.IsActive = true; // set the visual indicator 80
  80. 81                  // do the work to search twitter and handle the completed event
  81. 82                  WebClient proxy = new WebClient();
  82. 83                  proxy.OpenReadCompleted += new OpenReadCompletedEventHandler(OnReadCompleted);
  83. 84                  proxy.OpenReadAsync(new Uri(string.Format(SEARCH_URI, HttpUtility.UrlEncode(SearchTerm.Text), _lastId)));
  84. 85              }
  85. 86          }
  86. 87
  87. 88          /// <summary>
  88. 89          /// Method that fires after our SearchForTweetsEx runs and gets a result
  89. 90          /// </summary>
  90. 91          /// <param name="sender"></param>
  91. 92          /// <param name="e"></param>
  92. 93          void OnReadCompleted(object sender, OpenReadCompletedEventArgs e)
  93. 94          {
  94. 95              if (e.Error == null)
  95. 96              {
  96. 97                  _gotLatest = false; // reset the latest detector
  97. 98                  XmlReader rdr = XmlReader.Create(e.Result); // load stream into a reader
  98. 99
  99. 100                SyndicationFeed feed = SyndicationFeed.Load(rdr); // load syndicated feed (Atom)
  100. 101
  101. 102                // parse each item adding it to our ObservableCollection
  102. 103                foreach (var item in feed.Items)
  103. 104                {
  104. 105                    searchResults.Add(new TwitterSearchResult() { Author = item.Authors[0].Name, ID = GetTweetId(item.Id), Tweet = item.Title.Text, PublishDate = item.PublishDate.DateTime.ToLocalTime(), Avatar = new BitmapImage(item.Links[1].Uri) });
  105. 106                    _gotLatest = true; // reset the fact that we already have the max id needed
  106. 107                }
  107. 108
  108. 109                rdr.Close(); // close the reader
  109. 110            }
  110. 111            else
  111. 112            {
  112. 113                // initialize our ErrorWindow with exception details
  113. 114                ChildWindow errorWindow = new ErrorWindow(e.Error);
  114. 115                errorWindow.Show();
  115. 116            }
  116. 117            ActivityIndicator.IsActive = false; // reset the UI
  117. 118            _timer.Start(); // reset the timer
  118. 119        }
  119. 120
  120. 121        /// <summary>
  121. 122        /// Parses out the Tweet ID from the tweet
  122. 123        /// </summary>
  123. 124        /// <param name="twitterId"></param>
  124. 125        /// <returns></returns>
  125. 126        private string GetTweetId(string twitterId)
  126. 127        {
  127. 128            string[] parts = twitterId.Split(":".ToCharArray());
  128. 129            if (!_gotLatest)
  129. 130            {
  130. 131                _lastId = parts[2].ToString();
  131. 132            }
  132. 133            return parts[2].ToString();
  133. 134        }
  134. 135    }
  135. 136 }
复制代码
当我们的搜索页面被载入时,将会启动一个计时器,同时初始化搜索。然后当刷新间隔到了以后,将会重新搜索。但是记住,程序将搜索上一次搜索之后的新记录,而不会重新读取所有的记录。因为已经将DataGrid绑定到ObservableCollection了,所以新的搜索数据将会被添加到上面,它将会自动以排序的形式显示在用户界面上。

我们还增加了一些检查,确保搜索条件不为空值。

总结

在本部分我们取得了很大的进展。我们建立起了一个调用第三方服务的服务,通过绑定把它关联到DataGrid,添加了一个计时器用于自动获取服务。我们可以结束了,但是我们不能——DataGrid并不是我们想展示给最终用户的界面。让我们进入 第四部分,我们要做一些数据模板,并为你介绍XAML绑定语法。

C#和VB的完整项目代码下载: Silverlight入门:第二部分——定义界面布局和导航

下一篇: Silverlight入门:第四部分——数据绑定
TOP
 

  
你好,小弟初学,请问一下
1  private void SearchForTweets(object sender, RoutedEventArgs e)
2  {
3      WebClient proxy = new WebClient();
4      proxy.OpenReadCompleted += new OpenReadCompletedEventHandler(OnReadCompleted);
5      proxy.OpenReadAsync(new Uri(string.Format(SEARCH_URI, HttpUtility.UrlEncode(SearchTerm.Text), _lastId)));
6  }
7
8  void OnReadCompleted(object sender, OpenReadCompletedEventArgs e)
9  {
10      throw new NotImplementedException();
11 }
中HttpUtility是怎么引用的啊
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值