Quantcast
Channel: かずきのBlog@hatena
Viewing all 1388 articles
Browse latest View live

Windows store appで共有コントラクトに対応する

$
0
0

久しぶり過ぎて忘れてたのでメモです。

まずは、こんな感じでDataTransferManagerのDataRequestedイベントを購読しておく。

var manager = DataTransferManager.GetForCurrentView();
manager.DataRequested += this.ShareDataRequested;

そうすると共有ボタンが押されたときにイベントが呼び出される。イベント内では、イベント引数にでーたを突っ込む。args.Request.Data.Properties.Title, args.Request.Data.Properties.Descriptionの2つと、args.Request.Data.SetXXX(XXXは設定したいデータの型の名前が大体はいる)を設定すればOK。

// なんかのデータとってきて
var currentEntry = this.Model.ItemModel.CurrentEntry;

// データをつめる(dataPackage変数がargs.Request.Dataね)
dataPackage.Properties.Title = currentEntry.Title;
dataPackage.Properties.Description = currentEntry.Summary;
dataPackage.SetWebLink(new Uri(currentEntry.Link));

共有コントラクトの共有画面をアプリから呼び出したいときはDataTransferManagerのShowShareUIを呼び出せばよい。

DataTransferManager.ShowShareUI();

本当久しぶり過ぎて忘れてた。


めとべや東京 #7 でReactivePropertyのLTしてきました

$
0
0

ReactivePropertyの紹介してきました!!

資料公開しておきます。

ReactiveProperty v2.0.0-pre3をリリースしました

$
0
0

NuGet Gallery | ReactiveProperty 2.0.0-pre3

今回は以下の変更を含んでます。

CountNotifyerのINotifyPropertyChangedの実装

CountNotifyerクラスがINotifyPropertyChangedインターフェースを実装してCountプロパティの変更通知を受け取れるようにしました。

EventToReactiveCommandの追加

イベントをReactiveCommandに接続するときに、間にRxのレイヤを1枚噛ますことができるEventToReactiveCommandクラスを追加しました。EventTrigger等と一緒に使います。要はコマンドを呼び出す前に任意の処理を挟んで、なおかつ処理の結果をコマンドに渡すことが出来るようになります。

Commandが呼び出される前の処理はReactiveConverter<T, U>かDelegateConverter<T, U>のどちらかを継承して作成します。ReactiveConverterのほうが、IObservable<T>を受け取ってIObservable<U>を返す処理を記述して、DelegateConverterのほうがTを受け取ってUを返す処理を記述します。

ReactivePropertyのサンプルプログラムに入っている例では、ファイルダイアログを出して選択したファイルをCommandに渡すようにしています。コードを以下に示します。

// Converterpublicclass OpenFileDialogConverter : ReactiveConverter<EventArgs, string>
{

    protectedoverride IObservable<string> Convert(IObservable<EventArgs> source)
    {
        return source
            .Select(_ => new OpenFileDialog())
            .Do(x => x.Filter = "*.*|*.*")
            .Where(x => x.ShowDialog() == true) // Show dialog
            .Select(x => x.FileName); // convert to string
    }
}

EventArgsを受け取ったらダイアログを表示して、OKが押された場合のみ選択されたファイルのパスを流しています。

このConverterをCommandと接続するためにEventToReactiveCommandを使用します。EventToReactiveCommandのCommandプロパティに接続先のCommandをバインドして、先ほど作成したConverterをConverterプロパティ(コンテンツプロパティなので中に直接書けます)にセットします。

<Button Content="Select file">

    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <!-- set the command, called after the converter -->
            <Interactivity:EventToReactiveCommand Command="{Binding SelectFileCommand, Mode=OneWay}">
                <Views:OpenFileDialogConverter/>
            </Interactivity:EventToReactiveCommand>
        </i:EventTrigger>
    </i:Interaction.Triggers>

</Button>

ViewModel側は以下のようになっています。

publicclass EventToReactiveCommandViewModel
{
    public ReactiveCommand<string> SelectFileCommand { get; private set; }

    public ReactiveProperty<string> Message { get; private set; }

    public EventToReactiveCommandViewModel()
    {
        // command called, after converterthis.SelectFileCommand = new ReactiveCommand<string>();
        // create ReactiveProperty from ReactiveCommandthis.Message = this.SelectFileCommand
            .Select(x => x + " selected.")
            .ToReactiveProperty();
    }
}

Windows Runtimeの場合

Windows Runtimeのようにダイアログが非同期呼び出しのケースでは、SelectManyでTaskをIObservableに変換することで、対応することができます。ここらへんRxだとさくっと書けていいですね。

publicclass SelectFileConverter : ReactiveConverter<RoutedEventArgs, string>
{
    protectedoverride IObservable<string> Convert(IObservable<RoutedEventArgs> source)
    {
        return source
            .Select(_ => new FileOpenPicker()) // create picker
            .Do(x => x.FileTypeFilter.Add(".txt")) // set extensions
            .SelectMany(x => x.PickSingleFileAsync().AsTask().ToObservable()) // convert task to iobservable
            .Where(x => x != null) // filter
            .Select(x => x.Path); // convert
    }
}

ReactiveProperty 2.0.0-pre4をリリースしました

$
0
0

NuGet Gallery | ReactiveProperty 2.0.0-pre4

さっきpre3リリースしましたが、バージョンアップです。

メソッド名の変更

先ほど追加したEventToReactiveCommand用のReactiveConverterとDelegateConverterクラスのConvertメソッド名をConvertToに変更しました。Convertのままだと、実装しているインターフェースのConvertメソッドとかぶってオーバーライド出来ないことがあったので…。

EventToReactiveのObsolate化

この後紹介する機能とかぶるので非推奨にしました。

EventToReactivePropertyの追加

EventToReactiveCommandのReactiveProperty版です。

以下のようなConverterを定義して。

publicclass EventToReactivePropertyViewModel
{
    // binding from UI, event direct bindpublic ReactiveProperty<Unit> MouseDown { get; private set; }
    // binding from UI, event with converterpublic ReactiveProperty<Tuple<int, int>> MouseMove { get; private set; }
    // binding from UI, IgnoreEventArgs = truepublic ReactiveProperty<Unit> MouseEnter { get; private set; }

    public ReactiveProperty<string> CurrentPoint { get; private set; }
    public ReactiveProperty<string> Entered { get; private set; }

    public ReactiveProperty<string> AlertMessage { get; private set; }

    public EventToReactivePropertyViewModel()
    {
        // mode off RaiseLatestValueOnSubscribe, because initialValue is null.// mode off DistinctUntilChanged, because if Unit no send any values.
        var none = ReactivePropertyMode.None;

        MouseMove = new ReactiveProperty<Tuple<int, int>>(mode: none);
        MouseDown = new ReactiveProperty<Unit>(mode: none);
        MouseEnter = new ReactiveProperty<Unit>(mode: none);

        CurrentPoint = MouseMove
            .Select(p => string.Format("X:{0} Y:{1}", p.Item1, p.Item2))
            .ToReactiveProperty();

        Entered = MouseEnter
            .Select(_ => Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)))
            .Switch()
            .Select(x => "entered:" + x + "sec")
            .ToReactiveProperty();

        this.AlertMessage = MouseDown.Select(_ => "MouseDown!").ToReactiveProperty(mode: none);
    }
}

// EventToReactiveProperty converter.// Converter/IgnoreEventArgs is useful for unit testings.// For example, MouseMovoe.Value = new Point(10, 10) is simulate MouseMove// MouseEnter.Value = new Unit() is simulate raise MouseEnter event.publicclass MouseEventToPointConverter : ReactiveConverter<dynamic, Tuple<int, int>>
{
    protectedoverride IObservable<Tuple<int, int>> OnConvert(IObservable<dynamic> source)
    {
        return source
            .Select(x => x.GetPosition(null))
            .Select(x => Tuple.Create((int)x.X, (int)x.Y));
    }
}

EventTriggerなどと組み合わせて以下のように使います。

<Grid><!-- Use Blend SDK's Interaction Trigger --><!-- Event binding to ReactiveProperty --><i:Interaction.Triggers><i:EventTrigger EventName="MouseMove"><!-- ConvertBack function is Func<object, object>--><r:EventToReactiveProperty ReactiveProperty="{Binding MouseMove}"><vm:MouseEventToPointConverter/></r:EventToReactiveProperty></i:EventTrigger><i:EventTrigger EventName="MouseDown"><!-- direct event bind --><r:EventToReactiveProperty ReactiveProperty="{Binding MouseDown}"IgnoreEventArgs="True" /></i:EventTrigger><i:EventTrigger EventName="MouseEnter"><!-- IgnoreEventArgs = true send Unit --><r:EventToReactiveProperty ReactiveProperty="{Binding MouseEnter}"IgnoreEventArgs="true" /></i:EventTrigger></i:Interaction.Triggers><TextBlock Text="{Binding CurrentPoint.Value}" /><TextBlock Text="{Binding Entered.Value}"Margin="0,100,0,0" /></Grid>

MVVMとリアクティブプログラミングを支援するライブラリ「ReactiveProperty v2.0」オーバービュー

$
0
0
2015/02/22 ReactiveProperty 2.0.0-pre7をベースにしています

ReactivePropertyとは

ReactivePropertyとは、ReactiveProperty<T>クラスを中心とした、MVVMパターンでのリアクティブプログラミングを強力にサポートするライブラリです。

導入方法

NuGetからインストールできます。

Install-Package ReactiveProperty -Pre

対象プラットフォーム

以下のプラットフォームで動作します。

  • .NET Framework 4
  • .NET Framework 4.5
  • Windows store app 8/8.1
  • Windows Phone Sliverlight 8/8.1
  • Windows Phone app 8.1
  • Xamarin.Android
  • Xamarin.iOS

ReactivePropertyの基本

ReactivePropertyはMVVMパターンのViewModelのクラスでReactiveProperty<T>クラスをプロパティとしてもつのが特徴です。

ReactivePropertyを使ったViewModelのコードは以下のようになります。

using Reactive.Bindings;

namespace RxPropEdu
{
    publicclass MainWindowViewModel
    {
        public ReactiveProperty<string> Input { get; private set; }
        public ReactiveProperty<string> Output { get; private set; }
    }
}

そして、ViewModelクラスのコンストラクタでReactiveProperty間の関連を定義していきます。ReactivePropertyは、IObservable<T>を実装しているので、LINQを使って柔軟にプロパティを定義することが出来ます。

以下にコード例を示します。

using Reactive.Bindings;
using System;
using System.Reactive.Linq;

namespace RxPropEdu
{
    publicclass MainWindowViewModel
    {
        public ReactiveProperty<string> Input { get; private set; }
        public ReactiveProperty<string> Output { get; private set; }

        public MainWindowViewModel()
        {
            this.Input = new ReactiveProperty<string>(""); // デフォルト値を指定してReactivePropertyを作成this.Output = this.Input
                .Delay(TimeSpan.FromSeconds(1)) // 1秒間待機して
                .Select(x => x.ToUpper()) // 大文字に変換して
                .ToReactiveProperty(); // ReactiveProperty化する
        }
    }
}

ReactivePropertyを作成する基本的な方法は、以下の2つです。

  • new演算子を使って生成する
    • コンストラクタの引数にデフォルト値を指定する。指定しない場合は、その型のでデフォルト値が使われる
  • IObservable<T>に対してToReactiveProperty拡張メソッドを呼ぶ

上記サンプルでは、Inputプロパティでnewを使って生成して、OutputプロパティでToReactivePropertyを使った生成をしています。

Viewへのバインド

ReactivePropertyをViewへバインドするには、ReactivePropertyのValueメソッドをバインドします。

<Windowxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:RxPropEdu"x:Class="RxPropEdu.MainWindow"Title="MainWindow"Height="350"Width="525"><Window.DataContext><local:MainWindowViewModel/></Window.DataContext><StackPanel><Label Content="入力" /><TextBox Text="{Binding Input.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /><Label Content="出力" /><TextBlock Text="{Binding Output.Value}" /></StackPanel></Window>

実行すると以下のようになります。

f:id:okazuki:20150222170615p:plain

ReactivePropertyのCommand

ReactivePropertyは、ReactiveCommandというICommandを実装したクラスを提供しています。このクラスの特徴は、IObservable<bool>から生成することが出来る点です。Commandの実行可否を予めIObservable<bool>に合成しておいて、ToReactiveCommandでCommand化することで、CanExecuteChangedイベントが適切に発行されるCommandとして使うことが出来ます。もちろんnewで生成することもできます。そのときは、常に実行可能なCommandとしてふるまいます。

CommandのExecuteが呼ばれたときの処理はSubscribeメソッドで指定します。先ほど作成したViewModelのInputプロパティが空じゃないときに、Inputプロパティを空にするCommandを追加したコードは以下のようになります。

using Reactive.Bindings;
using System;
using System.Reactive.Linq;

namespace RxPropEdu
{
    publicclass MainWindowViewModel
    {
        public ReactiveProperty<string> Input { get; private set; }
        public ReactiveProperty<string> Output { get; private set; }

        public ReactiveCommand ClearCommand { get; private set; }

        public MainWindowViewModel()
        {
            this.Input = new ReactiveProperty<string>(""); // デフォルト値を指定してReactivePropertyを作成this.Output = this.Input
                .Delay(TimeSpan.FromSeconds(1)) // 1秒間待機して
                .Select(x => x.ToUpper()) // 大文字に変換して
                .ToReactiveProperty(); // ReactiveProperty化するthis.ClearCommand = this.Input
                .Select(x => !string.IsNullOrWhiteSpace(x)) // Input.Valueが空じゃないとき
                .ToReactiveCommand(); // 実行可能なCommandを作るthis.ClearCommand.Subscribe(_ => this.Input.Value = "");
        }
    }
}

XAMLでは、CommandプロパティにBindingすることで使います。

<Windowxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:RxPropEdu"x:Class="RxPropEdu.MainWindow"Title="MainWindow"Height="350"Width="525"><Window.DataContext><local:MainWindowViewModel/></Window.DataContext><StackPanel><Label Content="入力" /><TextBox Text="{Binding Input.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /><Label Content="出力" /><TextBlock Text="{Binding Output.Value}" /><Button Content="CLEAR"Command="{Binding ClearCommand}" /></StackPanel></Window>

実行すると以下のようになります。

TextBoxが空のときはCLEARボタンは押せない。

f:id:okazuki:20150222172131p:plain

何かを入力するとボタンが押せるようになる。

f:id:okazuki:20150222172212p:plain

ボタンを押すとTextBoxがクリアされ、ボタンがまた押せない状態になる。

f:id:okazuki:20150222172315p:plain

通常のC#のクラスからのReactivePropertyの生成

ReactivePropertyは、MVVMパターンでModelとViewModelを繋ぐために、INotifyPropertyChangedを実装したクラスのプロパティをReactiveProperty<T>に変換する機能を提供しています。

以下のようなINotifyPropertyChangedを実装したPersonクラスがあるとします。

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace RxPropEdu
{
    publicclass Person : INotifyPropertyChanged
    {
        publicevent PropertyChangedEventHandler PropertyChanged;
        privatevoid SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
        {
            if (object.Equals(field, value)) { return; }
            field = value;
            var h = this.PropertyChanged;
            if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); }
        }

        privatestring name;

        publicstring Name
        {
            get { returnthis.name; }
            set { this.SetProperty(refthis.name, value); }
        }

        privateint age;

        publicint Age
        {
            get { returnthis.age; }
            set { this.SetProperty(refthis.age, value); }
        }

    }
}

ModelからViewModelへの単一方向のバインド

ModelのクラスからViewModelへの一方向の値の伝搬機能を持つViewModelをReactivePropertyを使って作成するには、ReactivePropertyで定義されているINotifyPropertyChangedの拡張メソッドのObservePropertyを呼び出してプロパティの変更通知をIObservableに変換してから、ToReactivePropertyでReactivePropertyに変換します。

コード例を以下に示します。

using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System.Reactive.Linq;

namespace RxPropEdu
{
    publicclass PersonViewModel
    {
        public ReactiveProperty<string> Name { get; private set; }
        public ReactiveProperty<string> Age { get; private set; }

        public PersonViewModel(Person model)
        {
            this.Name = model
                .ObserveProperty(x => x.Name) // Nameプロパティを監視するIObservableに変換
                .ToReactiveProperty(); // ReactivePropertyに変換this.Age = model
                .ObserveProperty(x => x.Age) // Ageプロパティを監視するIObservableに変換
                .Select(x => x.ToString()) // LINQで加工して
                .ToReactiveProperty(); // ReactivePropertyに変換1
        }
    }
}

このように宣言的に、ModelからViewModelへ値を伝搬する処理が定義できます。

ModelとViewModelの双方向のバインド

ModelとViewModelのプロパティの双方向の値の伝搬機能を持つViewModelをReactivePropertyを使って作成するには、INotifyPropertyChangedの拡張メソッドであるToReactivePropertyAsSynchronizedメソッドを使用します。引数には、対象のプロパティを選択するラムダ式と、ModelからViewModelへ値を伝搬するときの変換処理のconvert引数と、ViewModelからModelへ値を伝搬するときの変換処理のconvertBackなどがあります。使用例を以下に示します。

using Reactive.Bindings;
using Reactive.Bindings.Extensions;

namespace RxPropEdu
{
    publicclass PersonViewModel
    {
        public ReactiveProperty<string> Name { get; private set; }
        public ReactiveProperty<string> Age { get; private set; }

        public PersonViewModel(Person model)
        {
            this.Name = model
                .ToReactivePropertyAsSynchronized(x => x.Name); // Nameプロパティと双方向で同期するReactivePropertyを作るthis.Age = model
                .ToReactivePropertyAsSynchronized(
                    x => x.Age, // Ageプロパティを                    convert: x => x.ToString(), // M -> VMのときは文字列に変換                    convertBack: x => int.Parse(x)); // VM -> Mの時にはintに変換
        }
    }
}

ViewModelからModelへの単一方向のバインド

INotifyPropertyChangedを実装していないクラスや、ModelからViewModelへの値の伝搬が必要ないケースではReactivePropertyクラスに定義されているFromObjectメソッドを使うことで、初期値がModelのプロパティの値で、ViewModelからModelへの単一方向のバインドが出来ます。これもToReactivePropertyAsSynchronizedメソッドと同様にconvert引数とconvertBack引数があります。

コード例を以下に示します。

using Reactive.Bindings;

namespace RxPropEdu
{
    publicclass PersonViewModel
    {
        public ReactiveProperty<string> Name { get; private set; }
        public ReactiveProperty<string> Age { get; private set; }

        public PersonViewModel(Person model)
        {
            this.Name = ReactiveProperty.FromObject(
                model, // もとになるModelを指定して
                x => x.Name); // プロパティを指定するthis.Age = ReactiveProperty.FromObject(
                model, // もとになるModelを指定して
                x => x.Age, // プロパティを指定する                convert: x => x.ToString(), // M -> VMの変換処理                convertBack: x => int.Parse(x)); // VM -> Mの変換処理
        }
    }
}

バリデーション

ReactivePropertyは、入力値の検証機能を提供しています。様々な方法で値を検証することが出来ます。まずは基本となるメソッドから紹介します。

ReactivePropertyに対して、SetValidateNotifyError(Func<IObservable<T>, IObservable<string>> validator)というメソッドが定義されています。これの引数は、ReactivePropertyの入力を表すIObservableをエラーの有無を表すIObservableに変換する処理を渡します。エラーがあるときはエラーを表す文字列を返し、エラーが無いときはnullを返します。

Nameプロパティに空文字を入れるとエラーになる場合の定義を以下に示します。

using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.Reactive.Linq;

namespace RxPropEdu
{
    publicclass PersonViewModel
    {
        public ReactiveProperty<string> Name { get; private set; }

        public PersonViewModel(Person model)
        {
            this.Name = model
                .ToReactivePropertyAsSynchronized(x => x.Name)
                .SetValidateNotifyError((IObservable<string> ox) => // 入力値のストリーム
                    Observable.Merge(
                        ox.Where(x => string.IsNullOrWhiteSpace(x)).Select(_ => "Error"), // 空文字のときはエラーメッセージを返す
                        ox.Where(x => !string.IsNullOrWhiteSpace(x)).Select(_ => default(string))) // 空文字以外のときはエラーがないのでnull
                );
        }
    }
}

ReactivePropertyクラスは、INotifyDataErrorInfoを実装しているので、WPFではValidation.ErrorsのErrorContentをバインドすることでエラーの内容を取得することが出来ます。これは、ReactiveProperty固有ではなくWPFの内容なので、以下の記事のWPFの値の検証の箇所を参照してください。

WPF4.5入門 その55 「Binding その1」 - かずきのBlog@hatena

WPF以外のプラットフォームでは、INotifyDataErrorInfoによる検証エラーのサポートがないため、以下に紹介する検証エラーに関するReactivePropertyのプロパティを使用してエラーメッセージをハンドリングします。

  • ObserveErrorChangedプロパティ:バリデーションエラーの内容に変更があったときに値が流れてくるIObservable<IEnumerable>
  • HasErrorsプロパティ:エラーの有無を返すプロパティ
  • ObserveHasErrorプロパティ:バリデーションエラーの内容に変更があったときにエラーの有無が流れてくるIObservable<bool>

エラーメッセージを取り出すにはObserveErrorChangedでIEnumerableがnullじゃないときに最初のstring型の要素を取り出します。nullの時はエラーが無いときなので、その時はその時で、エラーメッセージを空にする必要があります。コード例を以下に示します。

this.NameError = Observable.Merge(
        this.Name.ObserveErrorChanged.Where(x => x == null).Select(_ => default(string)), // エラーのないときはnullthis.Name.ObserveErrorChanged.Where(x => x != null).Select(x => x.OfType<string>().FirstOrDefault()) // エラーのあるときは最初のstring
    )
    .ToReactiveProperty();

SetValidateNotifyErrorメソッドにはIObservable以外にも単一の値を受け取り、単一のエラーを返すオーバーライドがあります。これを使うと、リアクティブプログラミングっぽくなくなりますが、素直に書くことができます。

this.Name = model
    .ToReactivePropertyAsSynchronized(x => x.Name)
    .SetValidateNotifyError((string x) =>
    {
        returnstring.IsNullOrWhiteSpace(x) ? "Error" : null;
    });

また、DataAnnotationsをサポートしているプラットフォームでは、以下のようにDataAnnotationsによる値の検証もサポートしています。サポートしているプラットフォームでは、一番手軽に出来る値の検証なのでおすすめです。

使用するメソッドはSetValidateAttributeメソッドです。コード例を以下に示します。

// 定義部分
[Required(ErrorMessage = "Error!!")]
public ReactiveProperty<string> Name { get; private set; }

// インスタンス化処理this.Name = new ReactiveProperty<string>()
    .SetValidateAttribute(() => this.Name);

入力値の検証と、ViewModelからModelへの値の書き戻しの連携

ToReactivePropertyAsSynchronizedメソッドとFromObjectメソッドがViewModelからModelへ値を書き戻すときに、ViewModelでの入力値の検証が終わったもののみを通すようにする機能があります。それぞれのメソッドの引数にあるignoreValidationErrorValueをtrueに設定することで有効になります。。

this.Name = model
    .ToReactivePropertyAsSynchronized(
        x => x.Name,
        ignoreValidationErrorValue:true) // 検証エラーのある値はModelに渡さない
    .SetValidateNotifyError((string x) => string.IsNullOrWhiteSpace(x) ? "Error" : null);

これでModelの状態をクリーンに保つことが出来るようになります。

Viewのイベント引数をReactivePropertyにバインドするEventToReactiveProperty

ReactivePropertyは、Viewで発生したイベントをViewModelのReactivePropertyに渡す機能を提供しています。この機能を使うと、マウス系のイベントからマウスの座標を抜き出して、VMのプロパティにセットするということが可能になります。

EventToReactivePropertyは、イベント引数の値を任意の値に変換するReactiveConverter&ltT, U>を使用します。ReactiveConverterのOnConvertメソッドでイベント引数のIObservableから、変換結果のIObservableにする処理を記述します。

例えば、MouseEventArgsからPointへ変換する処理は以下のように記述します。

using Reactive.Bindings.Interactivity;
using System;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;

namespace RxPropEdu
{
    publicclass MouseEventToPoiintConverter : ReactiveConverter<MouseEventArgs, Point>
    {
        protectedoverride IObservable<Point> OnConvert(IObservable<MouseEventArgs> source)
        {
            return source
                .Select(x => x.GetPosition((IInputElement)this.AssociateObject));
        }
    }
}

XAMLでは、EventTriggerの下に、EventToReactivePropertyを設定します。EventToReactivePropertyのReactivePropertyプロパティに、Converterによる変換結果を格納するReactivePropertyをバインドして、EventToReactivePropertyのタグの下に、Converterを設定します。

<Windowxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:RxPropEdu"xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:Interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.NET45"x:Class="RxPropEdu.MainWindow"Title="MainWindow"Height="350"Width="525"><Window.DataContext><local:MainWindowViewModel/></Window.DataContext><Border Padding="50"><Border Background="Pink"><i:Interaction.Triggers><i:EventTrigger EventName="MouseLeftButtonDown"><Interactivity:EventToReactiveProperty ReactiveProperty="{Binding MousePoint}"><local:MouseEventToPoiintConverter/></Interactivity:EventToReactiveProperty></i:EventTrigger></i:Interaction.Triggers><TextBlock Text="{Binding Message.Value}" /></Border></Border></Window>

この例を実行するとマウスのボタンを押した地点の座標が画面に表示されます。

f:id:okazuki:20150222203551p:plain

Viewのイベント引数をReactiveCommandに渡すEventToReactiveCommand

EventToReactivePropertyが、イベント引数の結果をReactivePropertyに設定します。それのReactiveCommand版がEventToReactiveCommandになります。使用方法は、EventToReactivePropertyと同じです。

以下のようなCommandとReactivePropertyを持ったViewModelがあるとします。

using Reactive.Bindings;
using System.Reactive.Linq;
using System.Windows;

namespace RxPropEdu
{
    publicclass MainWindowViewModel
    {
        public ReactiveCommand<Point> MouseDownCommand { get; private set; }

        public ReactiveProperty<string> Message { get; private set; }

        public MainWindowViewModel()
        {
            this.MouseDownCommand = new ReactiveCommand<Point>();
            this.Message = this.MousePoint
                .Select(x => string.Format("({0}, {1})", x.X, x.Y))
                .ToReactiveProperty();
        }
    }
}

XAMLを以下に示します。Converterは先ほどのEventToReactivePropertyと同様のものを使っています。EventToReactivePropertyとの差分は、EventToReactiveCommandのCommandプロパティにViewModelのCommandをバインドしている点です。

<Windowxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:RxPropEdu"xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:Interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.NET45"x:Class="RxPropEdu.MainWindow"Title="MainWindow"Height="350"Width="525"><Window.DataContext><local:MainWindowViewModel/></Window.DataContext><Border Padding="50"><Border Background="Pink"><i:Interaction.Triggers><i:EventTrigger EventName="MouseLeftButtonDown"><Interactivity:EventToReactiveCommand Command="{Binding MouseDownCommand, Mode=OneWay}"><local:MouseEventToPoiintConverter/></Interactivity:EventToReactiveCommand></i:EventTrigger></i:Interaction.Triggers><TextBlock Text="{Binding Message.Value}" /></Border></Border></Window>

EventToReactivePropertyとEventToReactiveCommandでは、ConverterでWhereメソッドなどを使って、ViewからViewModelに渡す処理をフィルタリングしたり、Bufferを使ってバッファリングしたりThrottleを使って連続的に発生するイベントを間引いたりなどView側でViewModelに流す値を加工することが出来る点が特徴になります。

ReactivePropertyのコレクション ReadOnlyReactiveCollection

MVVMパターンでは、ModelのコレクションからViewModelのコレクションへ変換するというケースが多々あります。そのようなケースに対応するためModelのObservableCollectionからViewModel用のReadOnlyReactiveCollectionに変換する処理を提供しています。

PersonのObservableCollectionを持つModelからReadOnlyReactiveCollectionを生成するコード例を以下に示します。

public ReadOnlyReactiveCollection<PersonViewModel> People { get; private set; }

// 構築処理this.People = model.People
    .ToReadOnlyReactiveCollection(x => new PeopleViewModel(x));

追加や更新削除といったModelでのコレクションの変更に追随してくれるので便利です。さらに、ReadOnlyReactiveCollectionから要素が削除されるタイミングで、コレクションの中身がIDisposableな場合はDisposeの呼び出しを行います。

ReadOnlyReactiveCollectionでは、ObservableCollectionから生成する以外に、細やかに登録・更新・削除を制御することができます。

追加・更新・削除・リセットを制御するにはReactivePropertyのCollectionChanged>T<型のIObservableからToReadOnlyReactiveCollectionメソッドで作成します。

CollectionChanged<T&gt型には、staticメソッドでAdd, Remove, Replaceが定義されていて、static readonlyなフィールドでResetという値が定義されています。これらの値を流すIObservableを作成することで、柔軟にコレクションの変更が出来るようになります。例えば、ボタンが押された時間を表す文字列を記録するアプリを考えます。以下のように4つのコマンドと選択項目を表すプロパティ、そして、記録を残すコレクションをプロパティに持ち、これらを組み合わせて登録更新削除などを行います。

publicclass MainViewModel
{
    public ReactiveProperty<string> SelectedItem { get; private set; }
    public ReactiveCommand AddCommand { get; private set; }
    public ReactiveCommand ResetCommand { get; private set; }
    public ReactiveCommand UpdateCommand { get; private set; }
    public ReactiveCommand DeleteCommand { get; private set; }

    public ReadOnlyReactiveCollection<string> TimestampLog { get; private set; }

    public MainViewModel()
    {
        this.SelectedItem = new ReactiveProperty<string>();

        this.AddCommand = new ReactiveCommand();
        this.ResetCommand = new ReactiveCommand();
        this.UpdateCommand = this.SelectedItem.Select(v => v != null).ToReactiveCommand();
        this.DeleteCommand = this.SelectedItem.Select(v => v != null).ToReactiveCommand();

        this.TimestampLog = Observable.Merge(
            this.AddCommand
                .Select(_ => CollectionChanged<string>.Add(0, DateTime.Now.ToString())),
            this.ResetCommand.Select(_ => CollectionChanged<string>.Reset),
            this.UpdateCommand
                .Select(_ => this.SelectedItem.Value)
                .Select(v => CollectionChanged<string>.Replace(this.TimestampLog.IndexOf(v), DateTime.Now.ToString())),
            this.DeleteCommand
                .Select(_ => this.SelectedItem.Value)
                .Select(v => CollectionChanged<string>.Remove(this.TimestampLog.IndexOf(v))))
            .ToReadOnlyReactiveCollection();
    }
}

MergeメソッドでAddCommand、ResetCommand、UpdateCommand、DeleteCommandを1本のIObservable<CollectionChanged<T>>にまとめてからコレクションに変換しています。

その他ユーテリティクラス

ReactivePropertyには、そのほかに、便利な小物系クラスがいくつか定義されています。

Notifier系クラス

BooleanNotifierクラスは、IObservable<bool>として扱うことのできる、bool型の値を切り替える機能を持ったクラスです。以下のような動きをします。

var n = new BooleanNotifier();
n.Subscribe(x => Console.WriteLine(x));
n.SwitchValue(); // True
n.SwitchValue(); // False

IObservable<bool>型なのでReactiveCommandに変換して使うことも出来ます。

CountNotifierクラスは、カウンター機能を持ったIObservable<CountChangedStatus>クラスです。CountChangedStatusは、以下のような値をもったenum型です。

publicenum CountChangedStatus
{
        Increment = 0,
        Decrement = 1,
        Empty = 2,
        Max = 3,
}

CountNotifierクラスは、インスタンス作成時に最大値を指定(指定しない場合はint.MaxValueになります)してIncrementメソッドとDecrementメソッドを使って値を加算・減算していきます。IncrementやDecrementが呼び出されたタイミングでCountChangedStatusを発行します。IncrementやDecrementメソッドの戻り値はIDisposable型でDisposeを呼び出すと、IncrementやDecrementで行った計算の逆の計算を行います。

var n = new CountNotifier(10);
n.Subscribe(x => Console.WriteLine(x));

var d = n.Increment();
Console.WriteLine(n.Count); // 1
d.Dispose();
Console.WriteLine(n.Count); // 0

n.Increment(10);
Console.WriteLine(n.Count); // 10
n.Decrement(5);
Console.WriteLine(n.Count); // 5

実行すると以下のような結果になります。

Increment
1
Decrement
Empty
0
Increment
Max
10
Decrement
5

ScheduledNotifierクラスは、指定した時間に値を発行する機能を持ったクラスです。以下のような動きをします。

var n = new ScheduledNotifier<int>();
n.Subscribe(x => Console.WriteLine(DateTime.Now + " > " + x));
n.Report(10);
n.Report(100, TimeSpan.FromSeconds(10));

実行結果は以下の通りです。

2015/02/22 21:11:23 > 10
2015/02/22 21:11:33 > 100

ReactiveTimer

ReactiveTimerは停止と再開が可能なTimerクラスです。StartメソッドとStopメソッドで開始、停止を行い、タイマーの発火はSubscribeで受信できます。Subscribeには何回目のタイマーの発火かを表す数字がわたってきます。

以下のようなコードで動作を確認できます。

var timer = new ReactiveTimer(TimeSpan.FromSeconds(1)); // 1秒スパン
timer.Subscribe(x => Console.WriteLine(x));

timer.Start();

Console.ReadKey();

timer.Stop();

Console.ReadKey();

timer.Start();

Console.ReadKey();

IObservableの拡張メソッド

全ての最後の値がTrueの時にTrueを流すCombineLatestValuesAreAllTrueメソッドと、その逆のCombineLatestValuesAreAllFalseメソッドがあります。このメソッドは、複数のバリデーションの結果がすべてTrueだった場合に有効にするCommandを作ったりする際にとても有用です。

ToUnitメソッドは、全てのIObservableをIObservable<Unit>に変換します。

ObserveOnUIDispatcherメソッドは処理をUIスレッド上で行うようにします。

Pairwiseメソッドは、Zip(Skip(1))のショートカットになります。

ReactivePropertyのシリアライズ

Json.NETを使ってJSON形式でシリアライズ、デシリアライズ可能です。ViewModelの値を一時的に保管するケースではJson.NETを使ってください。

まとめ

ReactivePropertyは、IObservable<T>からReactiveProperty>T<を作成するという非常にシンプルな機能をコアに様々なものをIObservableとして捉えたライブラリです。全てのものがIObservableで繋がることで、以下の図のようにMVVMの全てのレイヤがシームレスに繋がります。

f:id:okazuki:20150222212604p:plain

また、IObservableとして扱えるため当然ですがReactive Extensionsの強力な各種メソッドを使うことが出来ます。Reactive Extensionsについては以下のSlideShareから、PDFをダウンロードしてメソッド等を確認してみてください。

今更ですがRx再入門の記事をPDF化したやつをSlideShareに入れました - かずきのBlog@hatena

以上、簡単にですがReactivePropertyのv2系の紹介でした。

ストアアプリでListViewに左右のスワイプ処理を追加するという記事が面白かったのでReactivePropertyを使って書いてみた

$
0
0

元ネタ

めとべや東京 #7で登壇してきましたその1 ストアアプリでListViewに左右のスワイプ処理を追加する - shinji-japanのブログ

面白い内容です。読んでて記事内容で疑問点がいくつか出てきました。

  • なぜItemTemplate内のBorderの横幅と縦幅を明示してるのか
  • スワイプされた要素はどうやって特定するのか
  • MVVMちっくに書きたい

なぜItemTemplate内のBorderの横幅と縦幅を明示してるのか

これはおそらくイベントを拾うため。ItemContainerStyleでHorizontalContentAlignmentとVerticalContentAlignmentをStretchにしてやることで、ItemTemplateの中身がいっぱいにひろがって明示的にサイズを指定しなくても、イベントが拾えるようになりました。

具体的にはItemContainerStyleプロパティのコピーを以下のように編集します。

<Style x:Key="ListViewItemStyle1"TargetType="ListViewItem"><Setter Property="FontFamily"Value="{ThemeResource ContentControlThemeFontFamily}"/><Setter Property="FontSize"Value="{ThemeResource ControlContentThemeFontSize}"/><Setter Property="Background"Value="Transparent"/><Setter Property="TabNavigation"Value="Local"/><Setter Property="IsHoldingEnabled"Value="True"/><Setter Property="Margin"Value="0,0,18,2"/><Setter Property="HorizontalContentAlignment"Value="Left"/><Setter Property="VerticalContentAlignment"Value="Top"/><!-- この2個を追加 --><Setter Property="HorizontalContentAlignment"Value="Stretch" /><Setter Property="VerticalContentAlignment"Value="Stretch" /><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="ListViewItem"><-- 省略 --></ControlTemplate></Setter.Value></Setter></Style>

スワイプされた要素はどうやって特定するのか

イベント引数のOriginalSourceのDataContextからかっさらってやればよかったです。

MVVMチックに書きたい

ReactivePropertyのEventToReactiveCommandを使ってみました。

こんなConverterを作って、スワイプされた要素とスワイプの方向のTupleを返すようにしました。

publicclass ManipulationDeltaRoutedEventArgsToStringConverter : ReactiveConverter<ManipulationDeltaRoutedEventArgs, Tuple<string, string>>
{
    protectedoverride IObservable<Tuple<string, string>> OnConvert(IObservable<ManipulationDeltaRoutedEventArgs> source)
    {
        var ox = source
            .Select(x => Tuple.Create(x.OriginalSource as FrameworkElement, x.Delta.Translation.X));
        return Observable.Merge(
            ox
                .Where(x => x.Item2 > 10)
                .Throttle(TimeSpan.FromSeconds(0.5))
                .ObserveOnUIDispatcher()
                .Select(x => Tuple.Create(x.Item1.DataContext asstring, "Right")),
            ox
                .Where(x => x.Item2 < -10)
                .Throttle(TimeSpan.FromSeconds(0.5))
                .ObserveOnUIDispatcher()
                .Select(x => Tuple.Create(x.Item1.DataContext asstring, "Left")));
    }
}

Throttleを入れてるのはログを吐くようにしてみたら10以上の移動が連続して発生していたので、フィルタリングするためです。これで無駄なイベントの発生を排除しています。

これをViewModelのCommandにBindingします。

<Pagexmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:App3"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"xmlns:Core="using:Microsoft.Xaml.Interactions.Core"xmlns:Interactivity1="using:Reactive.Bindings.Interactivity"x:Class="App3.MainPage"mc:Ignorable="d"><Page.Resources><!-- ITemTemplate内からViewModelに触るためにリソースにVMを登録しておく --><local:MainPageViewModel x:Key="viewModel"/><DataTemplate x:Key="DataTemplate1"><Grid ManipulationMode="TranslateX,System"Background="Pink"><Interactivity:Interaction.Behaviors><!-- ManipulationDeltaのイベント引数を変換してコマンドに渡す --><Core:EventTriggerBehavior EventName="ManipulationDelta"><Interactivity1:EventToReactiveCommand Command="{Binding SwipeCommand, Mode=OneWay, Source={StaticResource viewModel}}"><local:ManipulationDeltaRoutedEventArgsToStringConverter /></Interactivity1:EventToReactiveCommand></Core:EventTriggerBehavior></Interactivity:Interaction.Behaviors><TextBlock Text="{Binding}"Style="{ThemeResource BodyTextBlockStyle}" /></Grid></DataTemplate><Style x:Key="ListViewItemStyle1"TargetType="ListViewItem"><!-- さっきの内容なので省略 --></Style></Page.Resources><Page.DataContext><StaticResource ResourceKey="viewModel"/></Page.DataContext><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition/></Grid.RowDefinitions><TextBlock TextWrapping="Wrap"Text="{Binding SwipeMessage.Value}"Style="{StaticResource BodyTextBlockStyle}"HorizontalAlignment="Center"/><ListView Grid.Row="1"ItemsSource="{Binding Items}"ItemTemplate="{StaticResource DataTemplate1}"ItemContainerStyle="{StaticResource ListViewItemStyle1}" /></Grid></Page>

ViewModelはこんな感じ。

using Reactive.Bindings;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;

namespace App3
{
    publicclass MainPageViewModel
    {
        public ReadOnlyReactiveCollection<string> Items { get; private set; }
        public ReactiveCommand<Tuple<string, string>> SwipeCommand { get; private set; }

        public ReactiveProperty<string> SwipeMessage { get; private set; }

        public MainPageViewModel()
        {
            var source = new ObservableCollection<string>(Enumerable.Range(1, 1000).Select(x => "item " + x));
            this.Items = source.ToReadOnlyReactiveCollection(x => x);

            this.SwipeCommand = new ReactiveCommand<Tuple<string, string>>();
            this.SwipeMessage = this.SwipeCommand
                .Select(x => x + " swiped.")
                .ToReactiveProperty();
        }
    }
}

実行してスワイプしてみた。ちゃんととれてる。

f:id:okazuki:20150222234807p:plain

ReactiveProperty v2.0をリリースしました

$
0
0

最近ちまちまといじってた2.0をリリース前パッケージではなく正式版としてリリースしました。

ReactiveProperty

NuGet Gallery | ReactiveProperty 2.0.0

リリースノート

v2.0

破壊的変更

  • 名前空間が Codeplex.Reactive から Reactive.Bindings に変わりました。
  • ReactiveProperty#ObserveHasError メソッドをObserveHasErrorsに変更してください。

非推奨

  • EventToReactiveを非推奨にしました。EventToReactivePropertyかEventToReactiveCommandを使ってください。

変更点

  • ReadOnlyReactivePropertyが要素を削除するときにDisposeメソッドを呼ぶようになりました。
  • CountNotifierクラスにINotifyPropertyChangedを実装しました。

追加

  • Xamarin.Android用の機能を追加しました
    • ViewクラスにSetBinding拡張メソッドを追加しました。
    • IObservableにSetCommand拡張メソッドを追加しました。

ReactiveProperty v2.1をリリースしました。

$
0
0

BlogのコメントでReadOnlyReactiveCollectionをReadOnlyObservableCollectionから生成できないのは何故?という意見が来てたので、確かに、と思って追加しました。

ReactiveProperty

NuGet Gallery | ReactiveProperty 2.1.0

v2.1.0

追加

  • ReadOnlyReactiveCollectionをReadOnlyObservableCollectionから作成できるようにしました
    • readOnlyObservableCollectionInstance.ToReadOnlyReactiveCollection(x => CreateViewModel(x))

変更

  • ReadOnlyReactiveCollectionのDisposeメソッドを呼び出したときに、コレクション内部のインスタンスに対してもDisposeを呼ぶようにしました

CoreTweet + Prism.StoreApps + ReactivePropertyで簡単なTwitterクライアントのサンプル

Rx2系の差分を知りたいときにみるサイト

インターバルと試行回数を指定できるRetryの作り方

$
0
0

Reactive Extensionsには回数指定のリトライか、無限リトライしか用意されてません。世の中には、一定間隔をあけてリトライ処理をしたいという需要もあります。ということで、さくっと試してみました・

staticclass Ex
{
    publicstatic IObservable<T> RetryEx<T>(this IObservable<T> self, int retryCount, TimeSpan retrySpan)
    {
        int count = 0;
        IObservable<T> retry = null;
        retry = self.Catch((Exception ex) =>
            {
                if (count++ < retryCount)
                {
                    // retryreturn retry.DelaySubscription(retrySpan);
                }

                return Observable.Throw<T>(ex);
            });
        return retry;
    }
}

リトライ回数の以下ならDelaySubscriptionで、Subscribeを遅延させて、リトライの回数を超えていたら例外を投げています。

因みに、ReactivePropertyには、これをもっとしっかり作ったOnErrorRetryというメソッドが用意されてるので、お勧めしておきます。

ASP.NET WebAPIで循環参照を持ったオブジェクトを返したい

$
0
0

ということで、元ネタはこちらです。

Serializing Circular References with JSON.Net and Entity Framework

Serializing Circular References with JSON.Net and Entity Framework

基本的に参照先のでいいのですが、ちょっとクラス名が変わってました。

var jsonSerializerSettings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects
};
config.Formatters.Clear();
// 参照先ではJsonFormatterというクラスになってた
config.Formatters.Add(new JsonMediaTypeFormatter { SerializerSettings = jsonSerializerSettings });

数時間悩んだよ。

ReactiveProperty v2.1.2をリリースしました

$
0
0

ReactiveProperty

NuGet Gallery | ReactiveProperty 2.1.2

v2.1.2

追加

  • ObservableCollection と ReadOnlyObservableCollection の要素のプロパティ変更を監視する ObserveElementProperty 拡張メソッドを実装しました。
  • INotifyCollectionChanged に対して ObserveXxxChanged 拡張メソッドを追加しました。

変更

  • ToReadOnlyReactiveCollection 拡張メソッドから参照型制約を除去しました。

ちょっと解説

ObserveElementProperty拡張メソッドは、以下のようなクラスがあるときに

publicclass Person : BindableBase
{
  privatebool isDelete;
  publicbool IsDelete
  {
    get { returnthis.isDelete; }
    set { SetProperty(ref isDelete, name); }
  }
}

コレクションの中の要素に対してプロパティの変更の監視が出来ます。

var c = new ObservableCollection<Person>();
c.Add(new Person());
c.Add(new Person());
c.Add(new Person());

c.ObserveElementProperty(x => x.IdDelete).Subscribe(x => ...);

c[0].IsDelete = true; // subscribeに通知がいく

ReactiveProperty v2.1.3をリリースしました

$
0
0

ReactiveProperty

v2.1.3

破壊的変更

  • ObserveElementProperty で値に変更があったインスタンスも通知できるようにしました。

解説

2.1.2で追加したObserveElementPropertyをちょこっと変えました。ごめんなさい。互換性壊しました!

そのかわり便利になってます。例えば、ObservableCollectionの要素から特定のプロパティを監視して、リアルタイムに条件を満たす要素だけを集めておくといったことが簡単?に出来るようになっています。

例えば以下のようなクラスがあるとします。

class Person : BindableBase
{
    privatestring name;

    publicstring Name
    {
        get { returnthis.name; }
        set { this.SetProperty(refthis.name, value); }
    }

    privatebool isRemoved;

    publicbool IsRemoved
    {
        get { returnthis.isRemoved; }
        set { this.SetProperty(refthis.isRemoved, value); }
    }

}

IsRemovedがTrueの要素だけをリアルタイムに集めるにはこんな感じにします。

// ソース
var col = new ObservableCollection<Person>(
    Enumerable.Range(0, 10).Select(x => new Person { Name = "tanaka" + x, IsRemoved = x % 4 == 0 }));

// IsRemovedがtrueの要素を集める
var removed = new ObservableCollection<Person>();
col.ObserveElementProperty(x => x.IsRemoved)
    .Subscribe(x =>
    {
        if (x.Value)
        {
            removed.Add(x.Instance);
        }
        else
        {
            removed.Remove(x.Instance);
        }
    });

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka0 tanaka4 tanaka8

col[1].IsRemoved = true;

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka0 tanaka4 tanaka8 tanaka1

col[0].IsRemoved = false;

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka4 tanaka8 tanaka1

ObserveElementPropertyにわたってくる引数のValueでプロパティ値が取れて、Instanceでプロパティを持ってるクラスのインスタンスが取れます。上記の例では使ってませんが、PropertyでPropertyInfoも取得できます。

仕上げ

上記の例では、コレクションに要素が削除されたときに対応できません。ということで、本当にリアルタイムにIsRemovedがTrueの要素を集めるコードはコレクションの削除の処理も追加して以下のような感じになります。

// ソース
var col = new ObservableCollection<Person>(
    Enumerable.Range(0, 10).Select(x => new Person { Name = "tanaka" + x, IsRemoved = x % 4 == 0 }));

// IsRemovedがtrueの要素を集める
var removed = new ObservableCollection<Person>();
col.ObserveElementProperty(x => x.IsRemoved)
    .Subscribe(x =>
    {
        if (x.Value)
        {
            removed.Add(x.Instance);
        }
        else
        {
            removed.Remove(x.Instance);
        }
    });
// 要素が削除されたときの対応
col.ObserveRemoveChangedItems()
    .Merge(col.ObserveReplaceChangedItems().Select(x => x.OldItem))
    .Subscribe(xs =>
    {
        foreach (var x in xs)
        {
            if (x.IsRemoved) { removed.Remove(x); }
        }
    });
// 何か大きな変更があったときの対応
col.ObserveResetChanged()
    .Subscribe(_ =>
    {
        removed.Clear();
        foreach (var x in col.Where(x => x.IsRemoved))
        {
            removed.Add(x);
        }
    });

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka0 tanaka4 tanaka8

col[1].IsRemoved = true;

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka0 tanaka4 tanaka8 tanaka1

col[0].IsRemoved = false;

Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka4 tanaka8 tanaka1// 追加OK
col.Add(new Person { Name = "okazuki", IsRemoved = true });
Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka4 tanaka8 tanaka1 okazuki// 削除OK
col.Remove(removed[0]);
Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // tanaka8 tanaka1 okazuki// クリアOK
col.Clear();
Console.WriteLine(removed.Aggregate("", (x, y) => x + " " + y.Name)); // なし

ReactiveProperty v2.1.5をリリースしました

$
0
0

ReactiveProperty

NuGet Gallery | ReactiveProperty 2.1.5

2.1.4はちょんぼをしてしまったので欠番です。はい。

v2.1.5

追加

  • IEnumerableからReadOnlyReactiveCollectionを作成する拡張メソッドを追加しました。

解説

今までReadOnlyReactiveCollectionはObservableCollectionかReadOnlyObservableCollectionからしか作成できませんでした。ということで、Livetみたいに自前でコレクションを実装しちゃってるようなライブラリのコレクションからReadOnlyReactiveCollectionを作るのは不可能(自分でやれば出来るけど…)でした。

ということで、今回はIEnumerable + CollectionChangedの組合わせで、ReadOnlyReactiveCollectionを作れるようにしました。

using Livet;
using Reactive.Extensions;



var source = new ObservableSynchronizedCollection<string>();
source.Add("aaa");
source.Add("bbb");

var coll = source.ToReadOnlyReactiveCollection(
    source.ToCollectionChanged<string>(),
    x => x + "!!",
    Scheduler.CurrentThread);

Console.WriteLine("{0} {1}", coll[0], coll[1]); // aaa!! bbb!!

source.Move(1, 0);

Console.WriteLine("{0} {1}", coll[0], coll[1]); // bbb!! aaa!!

ReactiveProperty v2.1.6をリリースしました

$
0
0

ReactiveProperty

NuGet Gallery | ReactiveProperty 2.1.6

v2.1.6

追加

  • ObservableCollection と ReadOnlyObservableCollection の要素の ReactiveProperty の変更を監視する ObserveElementReactiveProperty 拡張メソッドを実装しました。
  • ObservableCollection と ReadOnlyObservableCollection の要素の PropertyChanged イベントを監視する ObserveElementPropertyChanged 拡張メソッドを実装しました。
  • 読み取り専用の ReactiveProperty である ReadOnlyReactivePropertyを実装しました。IObservableから ToReadOnlyReactiveProperty 拡張メソッドで生成できます。

破壊的変更

  • INotifyCollectionChanged の拡張メソッドとして提供されていた ObserveElementProperty メソッドのアクセシビリティを internal にしました。

解説

ObserveElementXXXXメソッドシリーズ

コレクション内の要素を監視する系メソッドになります。コレクション内の要素のCollectionChangedや、PropertyChangedや、ReactivePropertyの変更を監視できます。

ReadOnlyReactiveProperty

今までのReactivePropertyは、読み取り専用に使うときでも必ず読み書き出来る状態でした。今回は、ReadOnlyな場合は今までのToReactivePropertyからToReadOnlyReactivePropertyに変えることで書き込みが出来ないReadOnlyReactivePropertyを使うことが出来ます。

ReactiveProperty v2.1.8をリリースしました

$
0
0

最近BlogがReactivePropertyのリリース日記になってる感じがありますが…。v2.1.8をリリースしました。

ReactivePropertywww.nuget.org

v2.1.8

追加

  • Helpers名前空間にリアルタイムにコレクションの要素をフィルタリングするコレクション FilteredReadOnlyObservableCollectionを追加しました。

解説

ObserveElementXXXXメソッドを追加したときに、コレクションの増減に対応するコードまで入れるとリアルタイムにコレクションから条件を満たす要素を選択できるという例を示しました。

ReactiveProperty v2.1.3をリリースしました - かずきのBlog@hatenaokazuki.hatenablog.com

このコードに順序の管理を加えて、リアルタイムフィルタリングをするコレクションを作成しました。

テストコードからの抜粋になりますが、以下のように使います。

var source = new ObservableCollection<Person>(new[]
{
    new Person { Name = "tanaka1", IsRemoved = true },
    new Person { Name = "tanaka2", IsRemoved = false },
    new Person { Name = "tanaka3", IsRemoved = false },
});

// 初期状態ではtanaka1のみが対象
var filtered = source.ToFilteredReadOnlyObservableCollection(x => x.IsRemoved);
filtered.Select(x => x.Name).Is("tanaka1");
filtered.Count.Is(1);

// 無関係の要素を追加しても変わらない
source.Add(new Person { Name = "tanaka4", IsRemoved = false });
filtered.Select(x => x.Name).Is("tanaka1");
filtered.Count.Is(1);

// プロパティを変更してフィルターの対象になった場合は、その要素が追加される
source[3].IsRemoved = true;
filtered.Select(x => x.Name).Is("tanaka1", "tanaka4");
filtered.Count.Is(2);

source.Clear();
filtered.Count.Is(0);

DataGridComboBoxColumnのItemsSourceのバインド方法

$
0
0

久しぶりのWPFネタです。小ネタ。

DataGridComboBoxColumnクラスを使うと、簡単にDataGridにComboBoxを設定できます。しかし、DataGridComboBoxColumnクラスのItemsSourceプロパティをBindingしようとすると、BindingのSourceがWindowのDataContextではなく、DataGridのItemsSourceに設定されたコレクションの行に該当するオブジェクトがSourceとして使用されます。そのため、WindowのDataContextが持っているコレクションを表示しようとするだけでも、Bindingが多少複雑になります。

AncestorTypeなどを使って親要素を辿ったりということを考えがちですが、もっと簡単なやり方があります。WindowのResourcesにCollectionViewSourceを使って、DataContextのコレクションをStaticResourceで参照出来るようにして、DataGridComboBoxColumnのItemsSourceにバインドする方法です。コード例を以下に示します。

まずDataGridの行に該当するクラスとして、以下のPersonクラスを定義します。このPersonクラスのParentIdをComboBoxから選択するという機能を実装します。

publicclass Person : INotifyPropertyChanged
{
    publicevent PropertyChangedEventHandler PropertyChanged;
    privatevoid SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
    {
        field = value;
        var h = this.PropertyChanged;
        if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); }
    }

    privatestring id;

    publicstring Id
    {
        get { returnthis.id; }
        set { this.SetProperty(refthis.id, value); }
    }

    privatestring name;

    publicstring Name
    {
        get { returnthis.name; }
        set { this.SetProperty(refthis.name, value); }
    }

    privatestring parentId;

    publicstring ParentId
    {
        get { returnthis.parentId; }
        set { this.SetProperty(refthis.parentId, value); }
    }

    public Person()
    {
        this.Id = Guid.NewGuid().ToString();
    }
}

続けて、MainWindowのViewModelを作成します。今回は以下のようなPersonクラスのコレクションを持っただけのシンプルなものになります。

publicclass MainWindowViewModel
{
    public ObservableCollection<Person> People { get; private set; }

    public MainWindowViewModel()
    {
        this.People = new ObservableCollection<Person>(
            Enumerable.Range(1, 100).Select(x => new Person { Name = "okazuki" + x }));
    }
}

このクラスをMainWindowのDataContextに設定して、DataGridに表示します。そして、ParentIdプロパティをPeopleコレクションの中から選択出来るようにします。XAMLは以下のようになります。

<Window x:Class="MVVMDataGridComboboxColumnSample.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:l="clr-namespace:MVVMDataGridComboboxColumnSample"Title="MainWindow"Height="350"Width="525"><Window.DataContext><l:MainWindowViewModel /></Window.DataContext><Window.Resources><!-- CollectionViewSourceで参照出来るようにしておいて --><CollectionViewSourcex:Key="PeopleSource"Source="{Binding People}" /></Window.Resources><Grid><DataGridAutoGenerateColumns="False"ItemsSource="{Binding People}"><DataGrid.Columns><DataGridTextColumnHeader="名前"Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /><!-- DataGridComboBoxColumnのItemsSourceで使用する --><DataGridComboBoxColumnHeader="親"SelectedValuePath="Id"DisplayMemberPath="Name"ItemsSource="{Binding Source={StaticResource PeopleSource}}"SelectedValueBinding="{Binding ParentId}"/></DataGrid.Columns></DataGrid></Grid></Window>

WindowのResourcesにPeopleをCollectionViewSourceとして登録しているため、簡単にDataGridComboBoxColumnのItemsSourceが設定できています。実行結果を以下に示します。

f:id:okazuki:20150319203213p:plain

歌舞伎座.tech#7 でお話してきます

Reactive Extensionsのはじめかた

$
0
0

最近Reactive Extensionsがバズってきてて、2年以上前に公開したReactive Extensionsのv1のメソッドを大体網羅したPDFが割と参照されてます。

www.slideshare.net

個人的な入門は、そこに全部書いたので今更感もありますが、Reactive Extensionsを今から入門する人向けに色々書いてみたいと思います。

Reactive Extensionsとは

IObservable<T>とIObserver<T>がコアのインターフェースになります。名前からわかるようにこれは、GoFのデザインパターンの1つ、オブザーバーパターンを表したインターフェースになります。

Observerパターンを簡単に説明すると、IObservableの状態に何かしら変化があったら、IObserverに変更通知を発行します。IObserverは、変更を受け取り何らかの処理を行います。

o: 状態変化
x: 処理
|: データの流れ
/: 終端
Observable ----o------o------o------o-------o----/
               |      |      |      |       |
Observer   ----x------x------x------x-------x----/

コードで表すと以下のようになります。

// Observable監視される側class Observable : IObservable<string>
{
  private List<IObserver<string>> obs = new List<IObserver<string>>();

  public IDisposable Subscribe(IObserver<string> o)
  {
    this.obs.Add(0);
    returnnull; // 本当は購読解除するDisposableを返す
  }

  // 変更があったことを通知するメソッドpublicvoid Execute(string x)
  {
    foreach (var o inthis.obs)
    {
      o.OnNext(x);
    }
  }
}
// Observer監視する側class Observer : IObserver<string>
{
  // 変更通知があったときに呼ばれるメソッドpublicvoid OnNext(string x)
  {
    // とりあえず標準出力に出力するだけ
    Console.WriteLine(x);
  }

  publicvoid OnCompleted() {} // no usepublicvoid OnError(Exception ex) {} // no use
}

この2つを繋げます。

var observable = new Observable();
observable.Subscribe(new Observer()); // 監視する人を一人追加する
observable.Execute("Hello"); // Hello と出力される

observable.Subscribe(new Observer()); // 監視する人をもう一人追加する
observable.Execute("World"); // Worldが2回出力される

図にすると大体以下のような感じです。

observable -----o(Hello)----------------------o(World)----------/
                |                             |\
observer1  -----x-----------------------------x-|---------------/
                                               /
observer2                             --------x-----------------/

これまで書いてきた図について

Marble Diagramsという図を崩して書いたものになります。(AAだから許して!) この図は、右に行くと時間軸。縦方向が上がInputで下がOutputという非常に単純な図です。oとかでデータの発生を表します。

何故ObserverパターンをMarble Diagramsで描いてきたかというと、この図をイメージ出来るようになることが大事だからです。Reactive Extensionsの処理は、往々にして、このMarble Diagramsで描かれます。つまり時系列に応じて発生する値を入力として受け取って、何らかの処理を行い出力するのがReactive Extensionsでやっていることになります。

ObserverパターンとPush型のコレクション

ここまでReactive Extensionsの記事なのにObserverパターンについて延々と語ってきました。ObserverパターンのObservableは、時間の経過に応じてデータを出力するものとみなすことができます。時間と入出力の関係を図示するMarble DiagramsでObserverパターンの処理の流れを描くことが出来たのはそのためです。 このように時間に応じてデータを発行するものをPush型のデータ、複数発行する場合はPush型のコレクションと言ったりします。

Push型のコレクション操作

時系列に沿って何かしらの状態変化を起こして値を発行するものを、コレクションと見ることが出来たら後は、コレクション操作をするだけです。幸いにもC#にはLINQという強力なコレクション操作ライブラリがあります。このLINQをIObservableにまで拡張+αしたものがReactive Extensionsになります。(他言語にも大体コレクション操作ライブラリがあって、それに合うような形でObservableに対してコレクション操作を行うノリでReactive Extensionsがポーティングされています。)

Where + Select

一番よく使うLINQのメソッドのWhereやSelectを使ってReactive Extensionsのコード例を示します。1~10の値から、偶数のみを抽出して2倍した結果を出力してみます。Marble Diagramsで描くと以下のような処理です。

source  --(1)--------(2)-------(3)-----(4)------(5)-----...
                      |                 |
Where   -------------(2)---------------(4)--------------...
                      |                 |
Select  -------------(4)---------------(16)-------------...
                      |                 |
処理     ------------(出力)-------------(出力)-----------...
var source = Observable.Range(1, 10);

source
    .Where(x => x % 2 == 0)
    .Select(x => x * x)
    .Subscribe(x => Console.WriteLine("value = {0}", x));

実行すると以下のような結果になります。

value = 4
value = 16
value = 36
value = 64
value = 100

通常のLINQと違うのは、LINQがコレクションに入っている値を処理するのに対して、Reactive Extensionsは、sourceから発行された値を処理している点です。微妙ですが大事な点です。

Reactive Extensionsのソースになるもの

これまでの例では、単純なコレクションの処理を示してきました。しかし、Reactive Extensionsは、時間の経過に伴って値を発行するものなら何でも処理できる点が強みです。このような特徴を持つものの代表例を以下に示します。

  • タイマー
  • イベント
  • 普通のコレクション(0秒で一気に値を発行する)
  • 非同期処理(要素が1つのコレクション)

この中でもイベントを扱えるのは非常に強力で、イベントに対してフィルタリング、バッファリング、変換を行って処理をするといったことが簡単にできるようになります。

応用的な内容

上記が個人的に思ってる基本的な内容です。応用的な内容としては以下のようなものがあります。簡単に触りだけ。

合成

色々なものがReactive Extensionsで値を発行するソースになることを簡単に紹介しました。これらのデータは、Reactive Extensionsで提供されている様々な合成系のメソッドを使って合体させることが出来ます。

時間の操作

Reactive Extensionsで扱うデータは時間軸に応じて、値を発行するものだということは何度も言ってきました。その特徴から、Reactive Extensionsでは、時間に関する操作を行う処理もたくさん提供されています。

参考情報

基本を押さえたうえで読むととても理解が捗ると思われる資料

大作。利用シーンが書かれてて素敵。

www.slideshare.net

安定のぐらばくさん。マーブルダイアグラムとコードがならべて書いてあってとてもわかりやすい。

Unity のための C# 勉強会 セッション資料grabacr.net

あと、上記資料からリンクされてるところも超参考になります。(リンク集め手抜き)

以上!良いRxライフを!

Viewing all 1388 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>