xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

Rx入門 (22) - TwitterのTimelineを表示

今回はTwitterのタイムラインを表示するサンプルを紹介します。なんという事はなく、WebClientクラスのDownloadStringAsyncメソッドをRxに乗せてみた、というやつです。Twitterからのタイムライン取得はAtomフィードを利用しています。

サンプルコード

まず、DownloadStringAsyncメソッドをIObservable<T>に変換する部分を拡張メソッドとして切り出します。Observable.Createメソッドを利用して、DownloadStringAsyncAsObservableメソッドからの戻り値のIObservable<T>Subscribeしたタイミングでダウンロード処理が実行されるようにしています。

今回の例では特に必要ありませんが、ダウンロード処理中に購読解除が行われた場合はダウンロードをキャンセルするようにしています。

using System;
using System.Net;
using System.Reactive.Linq;
 
namespace Sample41_Timeline
{
    public static class WebClientExtensions
    {
        public static IObservable<string> DownloadStringAsyncAsObservable(this WebClient client, Uri uri)
        {
            return Observable.Create<string>(observer =>
            {
                //---- 購読
                bool completed   = false;
                var subscription = Observable.FromEvent<DownloadStringCompletedEventHandler, DownloadStringCompletedEventArgs>
                (
                    handler => (sender, e) => handler(e),
                    handler => client.DownloadStringCompleted += handler,
                    handler => client.DownloadStringCompleted -= handler
                )
                .Take(1)
                .Select(e =>
                {
                    completed = true;
                    if (e.Error != null)    throw e.Error;
                    if (e.Cancelled)        throw new InvalidOperationException("Cancelled");
                    return e.Result;
                })
                .Subscribe(observer);
 
                //---- 実行
                client.DownloadStringAsync(uri);
 
                //---- 購読解除
                return () =>
                {
                    subscription.Dispose();
                    if (!completed)
                        client.CancelAsync();
                };
            });
        }
    }
}

次にXAMLコードを示します。ツイートの表示整形するためにちょこっと細々していますが、UIコンポーネントはTweetを表示するためのListBoxが置いてあるだけです。

<Window x:Class="Sample41_Timeline.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Sample41_Timeline" Height="400" Width="400">
    <Window.Resources>
        <!- リストボックスのアイテムの幅を調整するおまじない ->
        <Style TargetType="ListBoxItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>
        <!- ひとつのアイテムの表示内容 ->
        <DataTemplate x:Key="FeedItem">
            <Grid Margin="5,5" Background="MintCream">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Image Source="{Binding Image}" Margin="0,0,5,0" VerticalAlignment="Top" />
                <StackPanel Grid.Column="1">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <TextBlock FontWeight="Bold" Text="{Binding Author}" />
                        <TextBlock FontWeight="Bold" Text="{Binding Published}" Grid.Column="1" HorizontalAlignment="Right" />
                    </Grid>
                    <TextBlock Text="{Binding Content}" TextWrapping="Wrap" />
                </StackPanel>
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <!- UIコンポーネントはListBoxだけ ->
    <ListBox ItemsSource="{Binding}" ItemTemplate="{StaticResource FeedItem}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</Window>

最後に、Atomフィードをダウンロードしてデータを整形する部分のコードを示します。AtomフィードはただのXML形式のファイルなので、それをちょこちょこっとゴネゴネっとしてデータを抜き取っています。

using System;
using System.IO;
using System.Net;
using System.Reactive.Linq;
using System.ServiceModel.Syndication;
using System.Threading;
using System.Web;
using System.Windows;
using System.Xml;
 
namespace Sample41_Timeline
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            var url = "http://search.twitter.com/search.atom?rpp=50&q=" + HttpUtility.UrlEncode("xin9le");
            Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(5)) //--- 5秒間隔で自動更新
            .SelectMany(_ => new WebClient().DownloadStringAsyncAsObservable(new Uri(url, UriKind.Absolute)) //--- Atomをダウンロード
            .Select(text => new StringReader(text))
            .Select(reader => XmlReader.Create(reader))
            .SelectMany(reader => SyndicationFeed.Load(reader).Items) //--- Atomフィードを解析
            .Select(item => new
            {
                Content   = item.Title.Text,
                Published = item.PublishDate.LocalDateTime.ToString(),
                Author    = "@" + item.Authors[0].Name,
                Image     = item.Links[1].Uri,
            })
            .ToArray()
            .Catch(Observable.Never<object>())) //--- エラーは握りつぶす
            .ObserveOn(SynchronizationContext.Current)
            .Subscribe(items => this.DataContext = items); //--- 匿名クラスのコレクションをDataContextとして登録
        }
    }
}

実行結果

実行すると以下のようになります。それっぽく取得できていますね!もちろん、取得中は非同期に処理が実行されます。

Timeline