WPFでTwitterのタイムラインを表示するサンプル

TwitterのユーザータイムラインをXMLデータで取得して、WPFのフォームグリッドに表示させてみた。

WPFには標準でDataGridが付いてないので、WPF ToolkitのDataGridを使用している、こちらもMicrosoft製らしい。

最近はMVCを考慮したシステム構成をいろいろ考えていたのだが、WPFXAML+C#の構造は、WEBブラウザ上で動作するHTML+JavaScriptの構造と同じといっていいと思う。WEBサービスMVCレイヤーのモデル部分として考えたら、ビューコントローラであるプレゼンテーション部分は、取得したデータをどの様に表示するかを記述するだけでいい。

プレゼンテーションのマークアップ

Presentation.xaml

<!--
///////////////////////////////////////////////////////////////////////////////
//
//  WPF サンプル プレゼンテーションマークアップです。
//
// Copyright 2009 hiroxpepe
// Author hiroxpepe <hiroxpepe@gmail.com>
// Version 1.0.0
// Since  09.08.14
// Update 09.08.14
-->
<Window x:Class="WpfProtoType.Presentation" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wtk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"  
        Title="My TimeLine" Loaded="Window_Loaded" MaxWidth="600" MaxHeight="480">

    <StackPanel Orientation="Vertical" VerticalAlignment="Top" Margin="10">
        
        <!-- ユーザ名入力とデータ取得ボタン -->
        <Border BorderBrush="DarkGray" BorderThickness="1" Padding="5" CornerRadius="5">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="ユーザー名:" VerticalAlignment="center"/>
                <TextBox Name="userName" Width="100"/>
                <Button Name="updateButton" Click="UpdateButton_Click">データ取得</Button>
            </StackPanel>
        </Border>
        
        <Separator Height="10" Visibility="Hidden" />
        
        <!-- XMLデータ表示用のグリッドコントロール -->
        <Border BorderBrush="DarkGray" BorderThickness="1" Padding="5" CornerRadius="5">
            <wtk:DataGrid Name="dataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False">
                <wtk:DataGrid.Columns>
                    <wtk:DataGridTextColumn Binding="{Binding Date}" Header="日付時刻"/>
                    <wtk:DataGridTextColumn Binding="{Binding ID}" Header="投稿ID"/>
                    <wtk:DataGridTextColumn Binding="{Binding Client}" Header="クライアント"/>
                    <wtk:DataGridTextColumn Binding="{Binding Text}" Header="テキスト"/>
                </wtk:DataGrid.Columns>
            </wtk:DataGrid>
        </Border>
    </StackPanel>
</Window>
プレゼンテーションのプロシージャコード

Presentation.xaml.cs

using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Windows;
using System.Xml;

///////////////////////////////////////////////////////////////////////////////
//
//  WPF サンプル プレゼンテーションコードです。
//
// Copyright 2009 hiroxpepe
// Author hiroxpepe <hiroxpepe@gmail.com>
// Version 1.0.0
// Since  09.08.14
// Update 09.08.14

namespace WpfProtoType
{
    /// <summary>
    /// データ構造オブジェクト
    /// </summary>
    public class TimeLine
    {
        public string Date { get; set; }
        public string ID { get; set; }
        public string Client { get; set; }
        public string Text { get; set; }
    }

    /// <summary>
    /// Presentation.xaml の相互作用ロジック
    /// </summary>
    public partial class Presentation : Window
    {
        ///////////////////////////////////////////////////////////////////////
        #region 定数

        /// <summary>
        /// WEBサービス取得用のURLを定義します。
        /// </summary>
        //const string serviceUrl = "{0}.xml";
        const string serviceUrl = "http://twitter.com/statuses/user_timeline/{0}.xml";

        #endregion

        ///////////////////////////////////////////////////////////////////////
        #region フィールド

        /// <summary>
        /// XMLデータを読み込んで処理する、XmlDocumentオブジクトを保存します。
        /// </summary>
        private XmlDocument xmlDoc;

        /// <summary>
        /// データ部分をDOMノードリストとして処理する、XmlNodeListオブジクトを保存します。
        /// </summary>
        private XmlNodeList xmlNodes;

        #endregion

        /// <summary>
        /// 引数なしコンストラクタを実行します。
        /// </summary>
        public Presentation()
        {
            InitializeComponent();
        }

        ///////////////////////////////////////////////////////////////////////
        #region プロテクテッドメソッド

        /// <summary>
        /// フォームロード時に初期化処理を実行します。
        /// </summary>
        /// <param name="sender">イベント送信オブジェクトが提供されます。</param>
        /// <param name="e">イベントのデータが提供されます。</param>
        protected void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.xmlDoc = new XmlDocument();
        }

        /// <summary>
        /// XMLデータを取得してグリッド表示を更新します。
        /// </summary>
        /// <param name="sender">イベント送信オブジェクトが提供されます。</param>
        /// <param name="e">イベントのデータが提供されます。</param>
        protected void UpdateButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                // フォームからユーザ名取得
                var userName = this.userName.Text;

                // バインドオブジェクトにデータを取得する
                this.DataContext = this.GetDataContext(userName);
            }
            catch (Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

        #endregion

        ///////////////////////////////////////////////////////////////////////
        #region プライベートメソッド

        /// <summary>
        /// リストデータを取得します。
        /// </summary>
        /// <param name="userName">ユーザ名が提供されます。</param>
        /// <returns>データオブジェクトを格納したリストを返します。</returns>
        private List<TimeLine> GetDataContext(string userName)
        {
            try
            {
                // XMLドキュメントを読み込む
                this.LoadXmlDoc(userName);

                // DOMノードを抽出する
                this.SelectDomNodes();

                // リストデータを返す
                return this.CreateList();
            }
            catch (Exception err)
            {
                MessageBox.Show(err.Message);
                return null;
            }
        }

        /// <summary>
        /// XMLドキュメントを読み込みます。
        /// </summary>
        /// <param name="userName">ユーザ名が提供されます。</param>
        private void LoadXmlDoc(string userName)
        {
            this.xmlDoc.LoadXml(this.GetXmlDate(userName));
        }

        /// <summary>
        /// XMLドキュメントからDOMノードを抽出します。
        /// </summary>
        private void SelectDomNodes()
        {
            this.xmlNodes = this.xmlDoc.SelectNodes("//statuses/status");
        }

        /// <summary>
        /// XMLノードをイテレーションしてリストデータを作成します。
        /// </summary>
        /// <returns>データオブジェクトを格納したリストを返します。</returns>
        private List<TimeLine> CreateList()
        {
            var list = new List<TimeLine>();
            foreach (XmlNode node in this.xmlNodes)
            {
                list.Add(new TimeLine
                {
                    Date   = this.FormatDate(node),
                    ID     = this.FormatId(node),
                    Client = this.FormatClient(node),
                    Text   = this.FormatText(node)
                });
            }
            return list;
        }

        /// <summary>
        /// サーバーからXMLデータを取得します。
        /// </summary>
        /// <param name="userName">ユーザ名が提供されます。</param>
        /// <returns>取得したXMLデータ文字列を返します。</returns>
        private string GetXmlDate(string userName)
        {
            var timelineUrl = string.Format(serviceUrl, userName);

            WebClient webClient = new WebClient();
            webClient.Encoding = Encoding.UTF8;
            byte[] data = webClient.DownloadData(timelineUrl);
            return Encoding.UTF8.GetString(data);
        }

        /// <summary>
        /// 日付をフォーマットします。
        /// </summary>
        /// <param name="node">イテレーション中のNodeオブジェクトが提供されます。</param>
        /// <returns>書式変換された日付文字列を返します。</returns>
        private string FormatDate(XmlNode node)
        {
            var date = node["created_at"].InnerText;
            return DateTime.ParseExact(date,
                "ddd MMM dd HH':'mm':'ss zzzz yyyy",
                System.Globalization.DateTimeFormatInfo.InvariantInfo,
                System.Globalization.DateTimeStyles.None
            ).ToString("yyyy/MM/dd HH:mm:ss");
        }

        /// <summary>
        /// 投稿IDをフォーマットします。
        /// </summary>
        /// <param name="node">イテレーション中のNodeオブジェクトが提供されます。</param>
        /// <returns>書式変換された投稿ID文字列を返します。</returns>
        private string FormatId(XmlNode node)
        {
            return node["id"].InnerText;
        }

        /// <summary>
        /// クライアントをフォーマットします。
        /// </summary>
        /// <param name="node">イテレーション中のNodeオブジェクトが提供されます。</param>
        /// <returns>書式変換されたクライアント文字列を返します。</returns>
        private string FormatClient(XmlNode node)
        {
            try
            {
                return node["source"].InnerText.Split('>')[1].Split('<')[0];
            }
            catch
            {
                return node["source"].InnerText;
            }
        }

        /// <summary>
        /// テキスト文をフォーマットします。
        /// </summary>
        /// <param name="node">イテレーション中のNodeオブジェクトが提供されます。</param>
        /// <returns>書式変換されたテキスト文字列を返します。</returns>
        private string FormatText(XmlNode node)
        {
            return node["text"].InnerText;
        }

        #endregion
    }
}

思ったこと

XAML+C#は最高だ!思ったことが実現出来ると思う。しかしXAMLのバインドがなかったらどうなっていたことか?スクラッチでListやTableコントロールをつかってコーディングするところだった、危ない・・

簡単なサンプルの割には細かくメソッド分割しているが、既にコードから嫌な匂いがする。イベントハンドラの部分と実際の表示用整形処理の部分はクラス分割しないといけないだろう・・

そして本命は、同じくXAML+C#のWEB動作環境であるSilverlightである、果たしてどこまで出来るのか?