泥庭

2014年7月27日

ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その4)

Filed under: .NET, LINQ, WPF — タグ: , , , , — yone64 @ 11:28 AM

以前のバージョンは→第1回第2回第3回

今回は、PropertyChanged周りを見てみます。Rx分少なめで…。
ReactivePropertyはMVVM周りで使われるため、INotifyPropertyChangedとは切っても切れない関係があります。

var m = new Model();

var io = Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
    h => (s, e) => h(e),
    h => m.PropertyChanged += h,
    h => m.PropertyChanged -= h);

 this.Name = io.Where(e => e.PropertyName == "Name")
    .Select(e => m.Name)
    .ToReactiveProperty(m.Name);

上記のような、PropertyChangedイベントをIObservableに変換して、ToReactivePropertyするコードは何度も書くことになります。
そのため、Utilityメソッドとして定義しておくとよいでしょう。

public static IObservable<PropertyChangedEventArgs> PropertyChangedAsObservable<T>(this T source) where T : INotifyPropertyChanged
{
    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h);
}

とりあえず、Event→IO<T>変換拡張メソッドは作りますよね。で、次はPropertyNameでフィルタリングするのですが、文字列で指定するのは、いろいろと嫌なのでExpressionを引数にとれるようにします。

public static IObservable<PropertyChangedEventArgs> PropertyChangedAsObservable<TObj, TProp>(this TObj source, Expression<Func<TObj, TProp>> expression)
    where TObj : INotifyPropertyChanged
{
    var name = ((MemberExpression)expression.Body).Member.Name;

    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h)
        .Where(e => e.PropertyName == name);
}

PropertyChangedにおいてはEvetArgsが帰ってきてもうれしいことは少ないので、実際に変更された値を返却するように変更します。引数にデリゲートを追加してもよいのですが、せっかくなので既に引数に追加されているExpressionを利用したいと思います。
Expression<Func<TObj, TProp>>からFunc<TObj,TProp>を作るには、ReflectionとExpression.Compileがありますが、ここは処理速度的に有利なReflectionを使いたいと思います。(dynamicがこの目的では使えないのが残念ですね)

// .net4 まではこちら 
var func = (Func<TObj, TProp>)Delegate.CreateDelegate(typeof(Func<TObj, TProp>), typeof(TObj).GetProperty(name).GetGetMethod());

// PCL だとこっち
var func2 = (Func<TObj, TProp>)typeof(TObj).GetTypeInfo().GetRuntimeProperty(name).GetMethod.CreateDelegate(typeof(Func<TObj, TProp>));

Reflection周りは、ストアアプリ・PCL関連で大きく手が入っているので、.NET4までとPCLとでは書き方が大きく異なります。(.NET4.5だとどっちでもOK)

public static IObservable<TProp> PropertyChangedAsObservable<TObj, TProp>(this TObj source, Expression<Func<TObj, TProp>> expression)
    where TObj : INotifyPropertyChanged
{
    var name = ((MemberExpression)expression.Body).Member.Name;
    var func = (Func<TObj, TProp>)Delegate.CreateDelegate(typeof(Func<TObj, TProp>), typeof(TObj).GetProperty(name).GetGetMethod());

    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h)
        .Where(e => e.PropertyName == name)
        .Select(e => func(source));
}

CreateDelegateはそこそこ重い処理なので、実際に使うときはキャッシュしておきましょう。
あとは、ToReactivePropertyの引数で与えていた初期値をRx側で対応します。

public static IObservable<TProp> PropertyChangedAsObservable<TObj, TProp>(this TObj source, Expression<Func<TObj, TProp>> expression)
    where TObj : INotifyPropertyChanged
{
    var name = ((MemberExpression)expression.Body).Member.Name;
    var func = (Func<TObj, TProp>)Delegate.CreateDelegate(typeof(Func<TObj, TProp>), typeof(TObj).GetProperty(name).GetGetMethod());

    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h)
        .Where(e => e.PropertyName == name)
        .Select(e => func(source))
        .StartWith(func(source));
}

こういうのを一つ作っておくと、ReactivePropertyの活用がはかどりますね。
とここまで書いて、ReactivePropertyのDLLにも同じようなメソッドがあることに気付きました…

INotifyPropertyChangedExtensions.ObserveProperty

Observable.Deferメソッドで囲んであったり少し違いますが、ほぼほぼ同じですよね。(きっと
# この場合、Deferで囲むとどう動作が変わるか分かってない

広告

2014年6月24日

ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その2)

Filed under: .NET, LINQ, WPF — タグ: , , , , — yone64 @ 1:12 AM

第2回があったとは。。。

それはさておき、Rxが便利な分野といえば、イベントと非同期ですね。非同期はTaskとasync/awaitに譲った感もありますが、イベント合成はRxの独壇場です。(たぶん
そして、Rxでイベント合成のサンプルで間違いなく出てくるのが、マウスのDragですね。MouseDown→MouseMove→MouseUpの流れをうまく表現できるのが素晴らしい。

というわけで、いきなりコード

<Window x:Class="ReactiveWPF.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
    <Canvas x:Name="Canvas" Background="Transparent">
        <Polygon Points="{Binding Points.Value}" Stroke="Black" StrokeThickness="2"/>
    </Canvas>
</Window>

XAMLは、何のひねりもないので問題ないと思います。

/// <summary>
/// Window1.xaml の相互作用ロジック
/// </summary>
public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        
        this.Points = Canvas.MouseDownAsObservable().Do(_ => Canvas.CaptureMouse()).Select(e => e.GetPosition(Canvas))
            .SelectMany(_ => Canvas.MouseMoveAsObservable().Select(e => e.GetPosition(Canvas)), (s, e) => new {s, e})
            .Select(a => new PointCollection(new []{a.s, new Point(a.s.X, a.e.Y), a.e, new Point(a.e.X, a.s.Y) }))
            .TakeUntil(Canvas.MouseUpAsObservable().Do(_ => Canvas.ReleaseMouseCapture()))
            .ToReactiveProperty();

        this.Points
            .Subscribe(
            _ => Console.WriteLine("Next:" + _),
            () => Console.WriteLine("Complete"));

        this.DataContext = this;
    }

    public ReactiveProperty<PointCollection> Points { get; private set; }
}

public static class FrameworkElementEx
{
    public static IObservable<MouseButtonEventArgs> MouseDownAsObservable(this FrameworkElement source)
    {
        return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
            h => (s, e) => h(e),
            h => source.MouseDown += h, 
            h => source.MouseDown -= h);
    }

    public static IObservable<MouseEventArgs> MouseMoveAsObservable(this FrameworkElement source)
    {
        return Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
            h => (s, e) => h(e),
            h => source.MouseMove += h,
            h => source.MouseMove -= h);
    }

    public static IObservable<MouseButtonEventArgs> MouseUpAsObservable(this FrameworkElement source)
    {
        return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
            h => (s, e) => h(e),
            h => source.MouseUp += h,
            h => source.MouseUp -= h);
    }
}

C#側もそんなに問題ないですね。詳しくは、@ITの@neueccさんの記事とか@xin9leさんのブログとかを参照してください。ほぼ、そのままですね。
# 手抜きなので、イベントを拾ってプロパティーを変更して、自分自身とBindingしてるという微妙なコードになっていますが、ここではそこは大きな問題ではないです。
これで、Mouseによる範囲選択ができた。と思ったのですが、現実はそんなに甘くありませんでした。

MouseでDrag処理を行った場合、MouseUp時に何かしら確定処理を行うことがほとんどだと思います。
#矩形に含まれるオブジェクトを選択するとか、動的オブジェクトを永続オブジェクトに変更するとか…

しかし、この終了処理を記述する良い場所がないのです。
まず思いつくのが、SubscribeのCompleteだと思うのですが、これはMouseUpイベントが発生し、一連のMouseイベント購読が終わっても、呼び出されることがありません。(Hotだから?
次に、ReleaseMouseCapture()を呼び出している、Doメソッドの内部ですが、ここではObservableなStreamを流れてくるPointCollectionに対してアクセスすることができません。

解決策は、Rxの外部に変数を宣言して、変数のキャプチャを行うことになると思うのですが、これはこれでいまいちですよね。

2014年6月20日

ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その1)

Filed under: .NET, LINQ, WPF — タグ: , , , , — yone64 @ 11:52 PM

タイトル書いてみて、そんなやつおらんやろ。ってなりましたが、まぁ気にせずに。
# そんなことよりRibbonの記事の続きをって人もいないやろうし、

最近、ReactivePropertyを使うようになって、Reactive Extensions(以後、Rx)を知らなさすぎるなぁと、思い知ったのでメモがてら残してきたいと思います。
ところで、ReactivePropertyってなに?って人は、ひとまず
コチラに。超ざっくり言うとMVVMで便利なライブラリです。

public class MainViewModel
{
    public ReactiveCommand Command { get; private set; }

    public MainViewModel()
    {
        this.Command = new ReactiveCommand();
        this.Command.Subscribe(_ => Console.WriteLine("one"));
    }
}

↑こんな感じでVM書いて、CommandをBindingしておけば、クリック時に”one”がコンソールに出力されます。簡単ですね。
そのうえ、イベントベースのRx(Hotっていうやつ?)なので分配もできます。はい。

public class MainViewModel
{
    public ReactiveCommand Command { get; private set; }

    public MainViewModel()
    {
        this.Command = new ReactiveCommand();
        this.Command.Subscribe(_ => Console.WriteLine("one"));
        this.Command.Subscribe(_ => Console.WriteLine("two"));
    }
}

↑のように書くと、”one”も”two”も表示されますね。なるほど。ところで、分配っていつ行われるか気になったので、ちょっと調べてみました。

this.Command = new ReactiveCommand();

var io = this.Command
    .Do(_ => Console.WriteLine("do"))
    .Where(_ => Condition()));

io.Subscribe(_ => Console.WriteLine("one"));
io.Subscribe(_ => Console.WriteLine("two"));

↑ Subscribeまでの間に、共通の条件を突っ込みました。この場合、クリック1回で”do”は何回表示されるでしょうか?
結果は、2回です。ということはCondition()の評価は2回行われてるんですね。これはあまりうれしくない。

1回で済ます方法はないんでしょうか?(教えて偉い人)
# イメージ的にはIE.ToList()なのりで出来そうなのですが…

WordPress.com で無料サイトやブログを作成.