Archive - WPF

ObserveEveryValueChanged - 全てをRx化する拡張メソッド

ブードゥーの秘術により、INotifyPropertyChanged不要で、値の変更を検知し、IObservable化します。例えばINotifyPropertyChangedじゃないところから、WidthとHeightを引き出してみます。

using Reactive.Bindings.Extensions;
 
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
 
        this.ObserveEveryValueChanged(x => x.Width).Subscribe(x => WidthText.Text = x.ToString());
        this.ObserveEveryValueChanged(x => x.Height).Subscribe(x => HeightText.Text = x.ToString());
    }
}

wpfgif

なるほど的確に追随している。ソースコードはGitHub上に公開しました。

ReactivePropertyと組み合わせることで、そのままバインダブルに変換することも可能です。

public class MyClass
{
    public int MyProperty { get; set; }
}
 
public partial class MainWindow : Window
{
    MyClass model;
    public IReadOnlyReactiveProperty<int> MyClassMyProperty { get; }
 
    public MainWindow()
    {
        InitializeComponent();
 
        model = new MyClass();
        this.MyClassMyProperty = model.ObserveEveryValueChanged(x => x.MyProperty).ToReadOnlyReactiveProperty();
    }
}

ついでにokazukiさんが、ReactiveProperty v2.7.3に組み込んでくれましたので(今のところ).NET版では是非是非に使えます。UWP用とかXamarin用とかもきっとやってくれるでしょう(他人任せ)

仕組み

CompositionTarget.Renderingに引っ掛けて、つまり毎フレーム監視を走らせています。もともとUniRxのために作った機構を、そのままWPFに持ってきました。CompositionTarget.Renderingは、アニメーション描画などでも叩かれている比較的低下層のイベントで、これより遅いと遅れを人間が検知できちゃうので影響が出るし、これより早くても視認できないので意味がない。という、ぐらいの層です。こういった用途ではベストなところ。

毎フレーム監視がありかなしか。ゲームエンジンだと、そもそもほとんどが毎フレームごとの処理になっているので違和感も罪悪感もないのですけれど、全てがイベントドリブンで構築されている世界にそれはどうなのか。もちろん、原則はNoです。素直にINotifyPropertyChangedを書くべきだし、素直にReactivePropertyを書くべきでしょう。

ただ、アニメーションでも使われるしデバイスのインプット(LeapMotionとか)もその辺に引っ掛けるようなので、ここにちょっとプロパティに変更があるかないかのチェック入れるぐらい別にいいじゃん(どうせCPU有り余ってるんだし)、みたいな開き直りはあります。かなり。割と。

ObserveEveryValueChangedは、毎フレーム回っているような低下層の世界から、イベントドリブン(リアクティブ)な世界に引き上げるためのブリッジとしての役割があります。そう思うと不思議と、よく見えてきませんか?ただ「毎フレームポーリングかよ、ぷぷw」とかって一笑するだけだと視野が狭く、もう少しだけ一歩踏み込んで考えてみると思考実験的に面白い。私はコード片に意思を詰め込んでいくのが好きですね。哲学といってもいいし、ポエムでもある。そこには幾重も意味が込められています。

Material Design In XAML Toolkitでお手軽にWPFアプリを美しく

なんとブログ書くのは3ヶ月ぶり近い!えー、うーん、そんな経っちゃってるのか、こりゃいかん。と、いうわけかでWPFアプリを入り用で作ったんですが、見た目がショボくてゲッソリしてました。WPFでアプリ書いても別に綺麗な見た目にならんのですよね、むしろショボいというか。自分でデザイン作りこんだりなんて出来ないし、でもWPFのテーマ集なんかを適用してもクソダサいテーマしかなかったりして一層ダサくなるだけで全く意味ないとかそんなこんなんで、まぁ割とげっそりだったのですが、Material Design In XAML Toolkitは相当良い!良かった、のでちょうど手元に作り中のWPFアプリがあって適用してみたんで紹介してきます。

最終的に↑のような感じになりました。サクサクッとテーマ適用してくだけでこの程度に整えられるならば、上等すぎるかな、と。私的にはマテリアルデザイン、相当気に入りました。WindowsのModern UI風のフラットテーマは普通に適用しただけだと超絶ダサくなるという、センスが要求されすぎてキツかったんですが、マテリアルデザインはそれなりに質感が乗っかってるのでまぁまぁ見れる感じになる。また、画像からは分かりませんが結構細かくアニメーションが設定されていて感触が良い(マテリアルデザインの重要な要素だそうで)のも嬉しい。

Before

Beforeはこんな感じです。

TextBoxとボタンの羅列、実にギョーミーな雰囲気。機能的には私の要件はこれで満たしてるんですが(ちなみにコレが何かは後日紹介するしGitHubで公開もするつもりですが今は本題ではないのでスルーします)、いかんせん見た目が悲しいかな、と。そこで現れたMaterial Design In XAML Toolkit!NuGetからのインストールとコピペ一発で素敵な見た目に……。 なるほど世の中はさすがに甘くなかったですね:)

適用は簡単で、NuGetからMaterialDesignThemesをダウンロード、そしてApp.xaml.csにこのApp.xamlのApplication.Resourcesをコピペ。そしてMainWindowに以下の4項目を貼っつけてあげればできあがり。

<MainWindow
    xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf;assembly=MaterialDesignThemes.Wpf"
    TextElement.Foreground="{DynamicResource MaterialDesignBody}"
    Background="{DynamicResource MaterialDesignPaper}"
    FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto">

簡単簡単。これで美しくなるなら素晴らしいですね?そしてその結果がこれ。

うん、ダメ、理想とは程遠いダサさに溢れてます。Bootstrapを適用しただけじゃ普通にダサいままってのと同じ。ボーダーが吹っ飛んだので境目がわからず使いにくくなったし、やっぱダサ……、なんか一部のボタンは文字埋まっちゃってるし。で、引き返そうと思ったんですが、なんとなく良さそうな気配は感じたのでもう少し粘って作業することにしました。

デモアプリを見ながら細工

まずMaterialDesignInXamlToolkitのプロジェクトを落としましょう。CloneしてもいいしDownload Zipでもいいので。で、MainDemo.Wpfをビルドして実行しましょう、特に躓くことなくビルドできるはずですので。このデモアプリが非常によく出来ていて、出来ること全ての解説になってますし、当然それをやりたければそのxamlを開いてコピペすればなんとかなります!

というわけでデモアプリを眺めつつ自分のクソダサアプリのどこから手を入れようか。まず画面の構成要素のうち、上の部分のテキストボックスとボタンが並んでるところはコンフィグに近いので色分けしようかな、と。ヘッダ部分の色分け例はマテリアルデザインでよく見るパターンですしね。よく見るパターンということは、専用のパーツがしっかり用意されています。ColorZoneで囲むことで色がガラッと変わります。

<wpf:ColorZone Mode="Inverted" Padding="0">
    ...
</wpf:ColorZone>

ModeのInvertedは逆転した色、というわけで、これだけでまぁまぁ引き締まった雰囲気が出てきました、これはやって正解。また、ボタンの文字が埋まっているのはMargin入れて小さくしてたせいだったので、Heightを設定する形で小さくすることにしました。この状態でちょっとだけ問題があって、コンボボックスの選択時のフォントが通常カラーのままなので色が薄く見えなくなってしまうことに……。

これはテーマから外れたItemContainerStyleを設定して回避。

<ComboBox ItemsSource="{Binding UseConnectionType}">
    <ComboBox.ItemContainerStyle>
        <Style TargetType="ComboBoxItem">
            <Setter Property="Foreground" Value="Gray" />
        </Style>
    </ComboBox.ItemContainerStyle>
</ComboBox>

よくわからんけどこんなんでいいでしょふ、よくわからんけど。真面目にXAML書くの5年ぶりぐらいなんで正直もう全然覚えてないんですよね。

そういえば、オマケコントロール(?)としてTextBoxにウォーターマークがつけれるのが入ってます。使い方はwpf:TextFieldAssist.Hintを入れるだけ。

<TextBox wpf:TextFieldAssist.Hint="うぉーたーまーく" />

かなり綺麗に出て素敵なので最高だと思いました、まる。

MahAppsの導入

タイトルウィンドウが乖離しててダサいというか気になってきた。ので、ここを手軽に改変できるMahAppsを入れましょう。MahAppsだけだと、Metro風ということでこれ単体では別に素敵な見た目に出来ないんですが(ほんとメトロ風はムズカスぃ!)、Material Design In XAML Toolkitと合わせるとお互いの領域をカバーできる。ちゃんとMaterial Design In XAML Toolkit側で統合のための設定が用意されているので組み合わせるのは簡単です。MahAppsの基本的な導入はQuick Startに従う通り、まずWindowをMetroWindowに差し替えて

// Xaml 
<Controls:MetroWindow
    xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro">
 
// CodeBehind
public partial class MainWindow : MahApps.Metro.Controls.MetroWindow

App.xamlにリソースを投下、なのですが、MaterialDesignInXamlToolkitと統合するためのサンプルがMaterialDesignInXamlToolkit側に用意されているので、リソースはMahMaterialDragablzMashUp/App.xamlからコピペってきましょう。DragablzというChromeみたいなドラッグアンドドロップで切り離せるタブのためのライブラリを使わない場合(今回は使いませんでした)は、Dragablzに対する行は削除しておk(というか削除しないと動きません)。これで

となりました。うーん、よくなってきた!タイトルバーのところにテキストでいい感じなレイアウトで手軽にコマンドを突っ込めるのも嬉しかった。というわけでBeforeではステータスバーのところにやけくそにダサい感じで置いてたDuplicate Windowボタン(ウィンドウを複製する)をタイトルバーに移動。ついでにAlign Window(複数ウィンドウを整列させる)コマンドも追加。ちなみにこのアプリは複数ウィンドウを並べて使うのが前提なので、並べた時に重なって鬱陶しいためウィンドウ枠を光らせるのはあえて切ってるんですが、単体アプリなら光らせたほうが見栄え良いかもですね。入れるの自体は簡単で

<!-- 光らせるところ、GlowBrushを削れば光らない -->
<Controls:MetroWindow
    GlowBrush="{DynamicResource AccentColorBrush}">    
 
    <!-- コマンド入れるところ -->
    <Controls:MetroWindow.RightWindowCommands>
        <Controls:WindowCommands>
            <Button Content="Align Window" Click="AlignWindow_Click" />
            <Button Content="Duplicate Window" Click="DuplicateWindow_Click" />
        </Controls:WindowCommands>
    </Controls:MetroWindow.RightWindowCommands>

をMainWindows.xamlに突っ込むだけです。お手軽素敵。

最終調整

Purpleじゃない色調にしたかったのでテーマをデモアプリのパレットから眺めてBlueGrayに決定。テーマはApp.xamlを弄ればヨイデス。MaterialDesignColor.xxx.xamlの部分ですね、他の色とかはデモアプリのPaletteで確認できます。その他Light/Darkの切り替えやSecondaryColourの設定なんかも、xxx.xamlのそれっぽい部分をなんとなく書き換えれば書き換わります。

<!-- include your primary palette -->
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/MaterialDesignColor.BlueGrey.xaml" />
</ResourceDictionary.MergedDictionaries>

これで全体の色が変わったので、最後に、中央部分がMarginが消えてて区切りめがわからず使いづらいのは変わらずだったので、ここは枠をいれて明確な分離を。最初はボーダー入れて調整とかしてみたんですがイマイチしっくりこなかったんで、まぁ枠かな、と。マテリアルデザイン風のシャドウのある枠はヘッダーで色分けした時と同じく ColorZone で囲むだけです、ModeはStandardを選択。モードがどんなのがあるかもデモアプリを見れば一発で分かります。

<wpf:ColorZone Mode="Standard" Padding="5" CornerRadius="3" Effect="{DynamicResource MaterialDesignShadowDepth1}" Margin="2">
    <local:OperationItem />
</wpf:ColorZone>

影の出方はMaterialDesignShadowDepthの1~5で調整可能で、今回は1にしてます。その他の調整として、ログを表示しているテキストボックスのボーダーを上にも出すようにしたり、中身によって拡縮するようになっちゃたのでVerticalContentAlignmentを設定したりとちょっとした調整を少し入れて、最初に出した画像のものになりました。もっかい同じのを載せますけれど。

アプリの見た目が良くなるってのは純粋にテンション上がるんでいいものですねぇ、機能的には何も変わっちゃいないですが、気分は随分と良いです。まぁギョームコウリツとは関係ないとこなんであんまり手を入れまくってもアレですが、ちょっとテーマ適用して調整するだけで必要最低限整ってくれるのは実に良いです。

+アイコン

あとパラメータのコピペが欲しくなりました、複数ウィンドウ間で貼って回ったりするので。というわけでボタンにアイコンを用意したくて、それもマテリアルデザインなら簡単!

<Button Background="{StaticResource PrimaryHueLightBrush}"
        HorizontalAlignment="Left"
        Width="24" Height="24" Padding="0" Margin="5"
        Command="{Binding PasteCommand}"
        ToolTip="Paste">
    <Viewbox Width="16" Height="16">
        <Canvas Width="24" Height="24">
            <Path Data="M19,20H5V4H7V7H17V4H19M12,2A1,1 0 0,1 13,3A1,1 0 0,1 12,4A1,1 0 0,1 11,3A1,1 0 0,1 12,2M19,2H14.82C14.4,0.84 13.3,0 12,0C10.7,0 9.6,0.84 9.18,2H5A2,2 0 0,0 3,4V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V4A2,2 0 0,0 19,2Z"
                     Fill="{DynamicResource MaterialDesignBody}" />
        </Canvas>
    </Viewbox>
</Button>

これはMaterial Design Iconsにあるアイコンから取ってきてます。そこにはXAMLのPath Dataも載ってるので、タグをそのまま貼り付けるだけでアイコンとして使えます。これは楽ちんでめっちゃ良い!アイコンは揃えるのどうしても面倒ですからねー、このお手軽さは嬉しすぎます。色とかを用意されてるMaterialDesignのスタイルを入れ込んでやればそれだけで中々見栄えのするアイコンの出来上がり。

ReactiveCommand

えむぶいぶいえむ的なのはReactivePropertyで実装してます。で、ReactivePropertyもいーんですが、私的には昔から結構ReactiveCommand押しなんですよ、ReactiveCommandいいんだけどなー。例えば実際こんなコードになってます。

// peer = ReactiveProperty<Connection>
// ObserveStatusChangedで状態の変化の監視 + コネクションは切り替わることがあるので前のを破棄するSwitch
// Disconnectが押せるのはStatusがConnectの時だけ
Disconnect = peer.Select(x => x.ObserveStatusChanged())
    .Switch()
    .Select(x => x == StatusCode.Connect)
    .ToReactiveCommand();
 
// Disconnectの逆、だけどConnectが押せるのはそれに加えて接続先アドレス入力欄が空でない場合
Connect = peer.Select(x => x.ObserveStatusChanged())
    .Switch()
    .CombineLatest(Address, (x, y) => x != StatusCode.Connect && !string.IsNullOrEmpty(y))
    .ToReactiveCommand();

とか。若干込み入って面倒くさいのがスッキリ + ボタンのCanExecuteとぴったり来る。あとはプロセスを監視してて、存在してれば止めるボタンが押せるというのは、一秒毎のチェックにしていて、Observable.Intervalで繋ぎあわせてます。

// PhotonSocketServerが存在すれば押せるコマンド、1秒毎のポーリングで監視
KillPhotonProcess = Observable.Interval(TimeSpan.FromSeconds(1))
    .Select(x => Process.GetProcessesByName("PhotonSocketServer").Any()); 
    .ToReactiveCommand();

こういうの悩まずサクサク書けるのは幸せ度高い。

で、これ何なの?

なんなんでしょーねぇ。ということの一端はMetro.cs #1という勉強会で「IL から Roslyn まで - Metaprogramming Universe in C#」というタイトルでお話しますよ!2015-09-16(水)19:30 - 22:00に渋谷でやりますので、気になる人は是非是非参加くだしあ。内容はRoslyn 20%, C#全般 60%, WPF 10%, Unity 10%ぐらいなイメージですかしらん。このWPFのどこにメタプログラミング要素があるかというと、中身はMono.Cecil使ってアセンブリ解析してるからです。へー。とかそういうことを話します。

ReactiveOAuth - Windows Phone 7対応のOAuthライブラリ

Windows Phone 7用のOAuth認証ライブラリを作成し、公開しました。他のライブラリに比べての特徴は、非同期APIしか用意されていないWindows Phone 7での利用を念頭に置き、Reactive Extensions(Rx)をフル活用しているという点です。そもそもWindows Phone 7対応のOAuthライブラリが少ないので、特にWindows Phone 7開発者は是非どうぞ。

Windows Phone 7専用というわけでもないですが(Console/WPFで使えるよう、DLLとサンプルコードを用意してあります)、Windows Phone 7以外では別途Rxのインストールが必要です。Windows Phone 7環境では最初から入っているのでRxのインストール不要。Silverlight用は、コードコピペで別プロジェクト立てるだけで動くと思うんですが、確認取るのが面倒だった(クロスドメインがー)ので、そのうちに。

ところでそもそもRxって何、という人は Reactive Extensions入門 + メソッド早見解説表 をどうぞ。

何故Rxを使うのか

ReactiveOAuthの説明に入る前に、何故Rxを使うのかということを少し。理由は簡単で、非同期プログラミングは大変だから。論より証拠で、WebRequestのPOSTを全てBegin-Endパターンで構築してみましょう。

var req = (HttpWebRequest)WebRequest.Create("http://google.co.jp/"); // dummy
req.Method = "POST";
req.BeginGetRequestStream(ar =>
{
    var stream = req.EndGetRequestStream(ar);
    stream.BeginWrite(new byte[10], 0, 10, _ar =>
    {
        stream.EndWrite(_ar);
        req.BeginGetResponse(__ar =>
        {
            var res = req.EndGetResponse(__ar);
            var resStream = res.GetResponseStream();
            var s = new StreamReader(resStream).ReadToEnd();
            Console.WriteLine(s);
        }, null);
    }, null);
}, null);

コールバックの連鎖とはこういうことであり、大変酷い。冗談のようだ。こんなに面倒なら、こんなに苦しいのなら、非同期などいらぬ!しかし現実問題、Silverlightには、Windows Phone 7には、非同期APIしか搭載されていません。Begin-Endが強要される世界。理由は分かる。ユーザーエクスペリエンスの為でしょう。モバイル機器は性能が貧弱だから重い処理はいけない、と言うけれど、大事なのは処理が重いか否かではなく、体感。UIを止めさえしなければ、不快感を与えることはない。だから、強制的に非同期操作のみとした。

けれど、それで開発難しくなったり面倒になってしまってはいけない。非同期処理が簡単に出来れば……。その答えが、Rx。「簡単に出来るから」「開発者が幸せで」「全てが非同期になり」「ユーザーも幸せになる」。楽しい開発って大事だよね。本当にそう思っていて。開発者が不幸せで、コードに愛がなければ良いものなんて生まれやしないんだって、本当に思っていて。

// Rxならこう書ける(...AsObservableは拡張メソッドとして別途定義)
req.GetRequestStreamAsObservable()
    .SelectMany(stream => stream.WriteAsObservable(new byte[10], 0, 10))
    .SelectMany(_ => req.GetResponseAsObservable())
    .Select(res => new StreamReader(res.GetResponseStream()).ReadToEnd())
    .Subscribe(Console.WriteLine);
 
// SelectManyが苦手ならばクエリ構文という手もあります
var query = from stream in req.GetRequestStreamAsObservable()
            from _ in stream.WriteAsObservable(new byte[10], 0, 10)
            from res in req.GetResponseAsObservable()
            select new StreamReader(res.GetResponseStream()).ReadToEnd();
query.Subscribe(Console.WriteLine);

ネストが消滅して、メソッドチェーンの形をとって非同期が同期的のように書けるようになります。利点は他にもあって、様々な操作(合成・射影・抽出・待機などなど)が可能になる、ということもありますが、それはまたそのうち。

ところで、最初のコールバックの連鎖って何だか見覚えのあるような雰囲気ありませんか?JavaScriptで。そう、XmlHttpRequestであったりsetTimeoutであったりの連鎖と同じです。RxのJavaScript版、RxJSではJavaScriptでのそれらのネストを殺害することが可能です。興味があれば、そちらも是非試してみてくださいな。

ReactiveOAuthとは?

OAuthであっても、ようするにWebRequestなわけです。GET/POSTしてResponseを取ってくるということにかわりはない。そんなわけで、ネットワーク通信してResponseを取ってくる部分を片っ端から全てIObservableにしたのがReactiveOAuthです。Rxにべったり依存したことで、良くも悪くも、他のOAuthライブラリとは全く毛色の違う仕上がりになっています。

とはいっても、とにかく「簡単に書けること」にこだわりを持ってデザインしたので、利用自体は簡単ですし、Rxの知識もそんなに必要ありません。普通に取得するなら、SelectとSubscribeしか使わないので、全然安心です!まあ、できれば、これが入り口になってRxの世界を知ってもらえると嬉しいなあ、という皮算用もあったりですが。

使いかた1. AccessToken取得

デスクトップアプリケーション上でのOAuthの仕組みを簡単に説明すると、RequestToken取得→認証URL表示→PINコード入力→PINコード+RequestTokenを使ってAccessToken取得。という形になっています。まず、ユーザー名・パスワードの代わりとなるものはAccessTokenです。これを取得して、API呼び出しの時に使い、また、パスワード代わりに保存するわけです。そのRequestTokenやPINコードはAccessTokenを取得するための一時的な認証用キーということです。

AccessToken取得までにはOAuthAuthorizerクラスを使います。

// グローバル変数ということで。
const string ConsumerKey = "consumerkey";
const string ConsumerSecret = "consumersecret";
RequestToken requestToken;
AccessToken accessToken;
 
private void GetRequestTokenButton_Click(object sender, RoutedEventArgs e)
{
    var authorizer = new OAuthAuthorizer(ConsumerKey, ConsumerSecret);
    authorizer.GetRequestToken("http://twitter.com/oauth/request_token")
        .Select(res => res.Token)
        .ObserveOnDispatcher()
        .Subscribe(token =>
        {
            requestToken = token;
            var url = authorizer.BuildAuthorizeUrl("http://twitter.com/oauth/authorize", token);
            webBrowser1.Navigate(new Uri(url)); // navigate browser
        });
}

GetRequestTokenとBuildAuthorizeUrlを使い、RequestTokenの取得と、内蔵ブラウザに認証用URLを表示させました。

private void GetAccessTokenButton_Click(object sender, RoutedEventArgs e)
{
    var pincode = PinCodeTextBox.Text; // ユーザーの入力したピンコード
 
    var authorizer = new OAuthAuthorizer(ConsumerKey, ConsumerSecret);
    authorizer.GetAccessToken("http://twitter.com/oauth/access_token", requestToken, pincode)
        .ObserveOnDispatcher()
        .Subscribe(res =>
        {
            // Token取得時のレスポンスには、Token以外に幾つかのデータが含まれています
            // Twitterの場合はuser_idとscreeen_nameがついてきます
            // ILookup<string,string>なので、First()で取り出してください
            UserIdTextBlock.Text = res.ExtraData["user_id"].First();
            ScreenNameTextBlock.Text = res.ExtraData["screen_name"].First();
            accessToken = res.Token; // AccessToken
        });
}

画像は認証が全て終わった時の図になっています。RequestTokenとPinCodeをGetAccessTokenメソッドに渡すだけです。これでAccessTokenが取得できたので、全ての認証が必要なAPIにアクセス出来るようになりました。

使いかた2. APIへのGet/Post

ここからはConsoleApplicationのサンプルコードで説明。

var client = new OAuthClient(ConsumerKey, ConsumerSecret, accessToken)
{
    Url = "http://api.twitter.com/1/statuses/home_timeline.xml",
    Parameters = { { "count", 20 }, { "page", 1 } },
    ApplyBeforeRequest = req => { req.Timeout = 1000; req.UserAgent = "ReactiveOAuth"; }
};
client.GetResponseText()
    .Select(s => XElement.Parse(s))
    .Run(x => Console.WriteLine(x.ToString()));

IObservable<T>連鎖の最後のメソッドとしてRunを使うと、同期的になります。通常はSubscribeで非同期にすると良いですが、コンソールアプリケーションなどでは、同期的な動作のほうが都合が良いでしょう。

OAuthClientを作成し、オブジェクト初期化子でURL、パラメータ(コレクション初期化子が使えます)を設定したら、GetResponseTextを呼ぶだけ。あとはIObservable<string>になっているので、Linqと同じように操作していけます。

ApplyBeforeRequestではリクエストが発行される前に、生のHttpWebRequestが渡されるので(!)、TimeoutやUserAgentなど細かい設定がしたい場合は、ここにラムダ式を埋めてください。

では、POSTは?

new OAuthClient(ConsumerKey, ConsumerSecret, accessToken)
{
    MethodType = MethodType.Post,
    Url = "http://api.twitter.com/1/statuses/update.xml",
    Parameters = { { "status", "PostTest from ReactiveOAuth" } }
}.GetResponseText()
    .Select(s => XElement.Parse(s))
    .Run(x => Console.WriteLine("Post Success:" + x.Element("text")));

POSTの場合はMethodTypeにMethodType.Postを指定します(デフォルトがGETなので、GETの場合は指定の省略が可)。それ以外はGETと同じです。Urlとパラメータ指定して、GetResponse。

ストリーミングもいけます

OAuthClientには3つのメソッドがあります。GetResponseは生のWebResponseを返すもので細かい制御をしたい時にどうぞ。GetResponseTextはStreamReaderのReadToEndで応答をテキストに変えたものを返してくれるもので、お手軽です。そのままXElement.Parseとかに流すと楽ちん。そして、GetResponseLinesはReadLineで一行ずつ返してくれるもの、となっています。GetResponseTextとGetResponseLinesは型で見ると両方共IObservable<string>なため戸惑ってしまうかもですが、前者は流れてくるのは一つだけ、後者は行数分だけ、となります。

GetResponseLinesはStreamingAPIで使うことを想定しています。とりあえず、WPF用のサンプルを見てください。

var client = new OAuthClient(ConsumerKey, ConsumerSecret, accessToken)
{
    Url = "http://chirpstream.twitter.com/2b/user.json"
};
// streamingHandleはIDisposableで、これのDisposeを呼べばストリーミング停止
streamingHandle = client.GetResponseLines()
    .Where(s => !string.IsNullOrWhiteSpace(s)) // filter invalid data
    .Select(s => DynamicJson.Parse(s))
    .Where(d => d.text()) // has text is status
    .ObserveOnDispatcher()
    .Subscribe(
        d => StreamingViewListBox.Items.Add(d.user.screen_name + ":" + d.text),
        ex => MessageBox.Show(ReadWebException(ex))); // エラー処理

ストリーミングAPIを使うにあたっても、何の面倒くささもなく、至って自然に扱えてしまいます!Dynamicを使ったJsonへの変換にはDynamicJson - C# 4.0のdynamicでスムーズにJSONを扱うライブラリを、また、RxとStreamingAPIとの相性については過去記事C#とLinq to JsonとTwitterのChirpUserStreamsとReactive Extensionsを見てください。

そうそう、それとネットワーク通信で起こったエラーのハンドリングが、SubscribeのOnErrorに書くだけで済むというのもRxを使って嬉しいことの一つです。

実装について

構造は全体的に@ugaya40さんのOAuthAccessがベースになっています(パク……)。そもそもにTwitterTL to HTMLのOAuth対応した時に、OAuthAccessを使って、あー、OAuth周りってRxに乗せると快適になるなー、とか思ったのが作ろうとした最初の動機だったりもして。非常に感謝。

WebClient風の、認証がOAuthなだけのベタなWebRequestラッパーという感じなので、特別なところはありません。インターフェイスとかなくて、本当にただのベタ書き。特に奇をてらってるところはないんですが、ストリーミングAPIで使うために用意したGetResponseLinesは個人的には笑えたり。

var req = WebRequest.Create(Url);
return Observable.Defer(() => req.GetRequestStreamAsObservable())
    .SelectMany(stream => stream.WriteAsObservable(postData, 0, postData.Length))
    .SelectMany(_ => req.GetResponseAsObservable());
    .Select(res => res.GetResponseStream())
    .SelectMany(s => Observable.Using(() => new StreamReader(s), sr => Observable.Repeat(sr)))
    .TakeWhile(sr => !sr.EndOfStream)
    .Select(sr => sr.ReadLine());

うわー……。利用者としては、ただのIObservable<string>としか見えないので、前段階でこんなにチェーンが繋がってるだなんてこと、気にする必要は全くないんですけどねー。これがベストな書き方だとは全然思えないので、誰かアドバイス欲すぃです。

まとめ

RxというとLinq to Events、イベントのLinq化という方向が目につきますが、今回は非同期のLinq化のほうにフォーカスしました。何というか、実に、素晴らしい!asynchronus programming is hard、と、思っていた時もありました。今や私達にはRxがある。恐れることはなにもない。

今回WPFとWindows Phone 7(Silverlight)でサンプルを作ったのですが、コードがコピペで、完全な互換性もって動いちゃうんですね。WPFで書いてSilverlightに持っていく時に、ああ、BeginGetResponseに書き換えなきゃ…… みたいなことが起こらない。最初から非同期で統一することで、全部ライブラリがネットワーク周りを吸収してくれる。非同期→同期にするのも簡単だし(RunやToEnumerableを使えばいい)、そもそも、Rxの土台に乗っている方が、普通に同期的に書くよりもむしろ楽だったりします。

個人的には、Windows Phone 7のローンチに粗製乱造Twitterアプリを送り込むという野望があるんですがねえー。いや、粗製乱造にするつもりはないんですが、機能をザックリと削って、一般性を無視して「私が使うシチュエーションで私が使いやすいような」アプリを出したいなーと思ってます。構想はあって、そこそこ尖った個性ある内容になる予定なので一部の人がフィットしてくれればいいな、と。多くの人に目に触れては欲しいのでローンチのタイミングは外したくない。問題はWorldのローンチタイミングに合わせてもJapanだと実機がないってことですね!開発機欲しい(お金は払いますからどうかー)。

と、思ってたのですがサンプル作りで、あきらかーなXAML知識のなさ(ていうか何も知りません)が露呈したので、ローンチに間にあわせるとか寝言すぎるのですが。せめてJapanのローンチまでにはそれなりな技量を身につけたいところです。

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for .NET(C#)

April 2011
|
March 2017

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc