FC2ブログ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Rxでイベントを扱う

 RxではイベントもLINQとして扱えます。
 ただし、Rxでイベントを扱うためにはイベントをIObservable<T>に変換する必要があります。イベントをIObservable<T>に変換するたには、FromEventメソッドかFromEventPatternメソッドのどちらかを使用します。
 どちらを使用しても良いのですが、この2つのイベントは名前も使い方も似ていて非常に紛らわしいので、どちらか片方だけを覚えておけば良いと思います。
 私はFromEventメソッドの方で覚えているので、この記事ではFromEventメソッドを使用してイベントをIObservable<T>に変換します。
 これから紹介するコードでは、Modelクラスに作成したイベントをViewModelでObservable化して扱います。
using System;
using Livet;

namespace RxEvent.Models
{
    // TextChangedイベントのイベント変数
    public class TextChangedEventArgs : EventArgs
    {
        public TextChangedEventArgs(string text)
        {
            Text = text;
        }

        public string Text { get; private set; }
    }

    public class Item : NotificationObject
    {
        // 変更されたことだけを通知するイベント
        public event EventHandler Changed;

        // Textが変更された際に起きるイベント。
        public event EventHandler<TextChangedEventArgs> TextChanged;

        #region Text変更通知プロパティ
        private string _Text;

        public string Text
        {
            get
            { return _Text; }
            set
            { 
                if (_Text == value)
                    return;
                _Text = value;

                RaiseChanged();
                RaiseTextChanged(value);
                RaisePropertyChanged();
            }
        }
        #endregion

        // Changedイベントを発生させる。
        protected virtual void RaiseChanged()
        {
            var h = this.Changed;
            if(h != null)
            {
                h(this, new EventArgs());
            }
        }

        // TextChangedイベントを発生させる。
        protected virtual void RaiseTextChanged(string text)
        {
            var h = this.TextChanged;
            if(h != null)
            {
                h(this, new TextChangedEventArgs(text));
            }
        }
    }
}
 Modelでは適当なプロパティを作成し、その値が変更された際に各種イベントを発生させているだけです。これらのイベントをViewModelでObservable化して、Rxで使用します。
 イベントのObservable化を1回しかやらないのであれば、使用するときにObservable化すれば良いのですが、複数個所で使用する可能性があるのであれば拡張メソッドとして定義することをお勧めします。
using System;
using System.Reactive.Linq;
using System.ComponentModel;

namespace RxEvent.Models
{
    public static class ItemExtensions
    {
        // Changed(EventHandler)イベントのObservable化する
        public static IObservable<EventArgs> ChangedAsObservable(this Item source)
        {
            return Observable.FromEvent<EventHandler, EventArgs>(
                h => (sender, e) => h(e),
                h => source.Changed += h,
                h => source.Changed -= h);
        }

        // TextChanged(EventHandler<T>)イベントのObservable化する
        public static IObservable<TextChangedEventArgs> TextChangedAsObservable(this Item source)
        {
            return Observable.FromEvent<EventHandler<TextChangedEventArgs>, TextChangedEventArgs>(
                h => (sender, e) => h(e),
                h => source.TextChanged += h,
                h => source.TextChanged -= h);
        }

        // WPFでよく使うPropertyChangedをObservable化する
        public static IObservable<PropertyChangedEventArgs> PropertyChangedAsObservable(this INotifyPropertyChanged source)
        {
            return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
                h => (sender, e) => h(e),
                h => source.PropertyChanged += h,
                h => source.PropertyChanged -= h);
        }
    }
}
 ItemクラスのChangedイベント(EventHandler)とTextChangedイベント(EventHandler<T>)のObservable化とPropertyChangedイベントのObservable化を行っています。
 FromEventメソッドでObservable化する際には、FromEventメソッドにイベントハンドラーの型とイベント変数の型を指定する必要があります。
 FromEventメソッドの第1引数で変換を行い、第2、第3引数で目的のイベントへの追加と削除を行っています。
 見ての通り、イベントハンドラーやイベント変数の型が違っても、FromEventメソッドの引数は同じような形になっています。なので、この使い方を1つ覚えるだけで様々なイベントをObservable化することができます。

 次にViewModel側でこれらのイベントをRxで利用します。
using System;
using Livet;
using Livet.EventListeners;
using System.Reactive.Linq;
using RxEvent.Models;

namespace RxEvent.ViewModels
{
    public class MainWindowViewModel : ViewModel
    {
        private Item _Item;

        public  MainWindowViewModel()
        {
            ChangedCount = 0;
            TextChangedCount = 0;
            PropertyChangedCount = 0;
            LatestText = "";

            _Item = new Item();

            // _ItemのイベントをObservable化してRxで使用する
            var changed = _Item.ChangedAsObservable()
                .Subscribe(_ => ChangedCount++);

            var textChanged = _Item.TextChangedAsObservable()
                .Subscribe(e =>
                {
                    TextChangedCount++;
                    LatestText = e.Text;
                });

            var propertyChanged = _Item.PropertyChangedAsObservable()
                .Where(e => e.PropertyName == "Text")   // Textプロパティについてのみ
                .Subscribe(_ => PropertyChangedCount++);

            CompositeDisposable.Add(new PropertyChangedEventListener(_Item)
            {
                (sender, e) => RaisePropertyChanged(e.PropertyName)
            });
            CompositeDisposable.Add(changed);
            CompositeDisposable.Add(textChanged);
            CompositeDisposable.Add(propertyChanged);
        }

        public string Text
        {
            get { return _Item.Text; }
            set { _Item.Text = value; }
        }

        #region ChangedCount変更通知プロパティ
        private int _ChangedCount;
        // Changedイベント発生回数
        public int ChangedCount
        {
            get
            { return _ChangedCount; }
            private set
            { 
                if (_ChangedCount == value)
                    return;
                _ChangedCount = value;
                RaisePropertyChanged();
            }
        }
        #endregion

        #region TextChangedCount変更通知プロパティ
        private int _TextChangedCount;
        // TextChangedイベント発生回数
        public int TextChangedCount
        {
            get
            { return _TextChangedCount; }
            private set
            { 
                if (_TextChangedCount == value)
                    return;
                _TextChangedCount = value;
                RaisePropertyChanged();
            }
        }
        #endregion

        #region LatestText変更通知プロパティ
        private string _LatestText;
        /// <summary>
        /// TextChangedから取得する最新Text
        /// </summary>
        public string LatestText
        {
            get
            { return _LatestText; }
            private set
            { 
                if (_LatestText == value)
                    return;
                _LatestText = value;
                RaisePropertyChanged();
            }
        }
        #endregion

        #region PropertyChangedCount変更通知プロパティ
        private int _PropertyChangedCount;
        // ItemのPropertyChangedイベント発生回数
        public int PropertyChangedCount
        {
            get
            { return _PropertyChangedCount; }
            private set
            { 
                if (_PropertyChangedCount == value)
                    return;
                _PropertyChangedCount = value;
                RaisePropertyChanged();
            }
        }
        #endregion
    }
}
 ModelのイベントをRx化して利用している部分は23~35行目の部分です。拡張メソッドXxxAsObservableを使用して、目的のイベントをObservable化します。あとはRxの使い方と同じで、フィルタリング等の処理を行い最後にSubscribeメソッド使用します。
 Subscribeメソッドの戻り値はIDisposableインスタンスです。このインスタンスのDisposeメソッドを呼ぶことで、イベントの監視を停止させることができます。(ここでは、Livetの機能を使ってViewModelがDisposeされた際に一緒にDisposeさせています。)

 最後にViewを作ります。
<Window x:Class="RxEvent.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:RxEvent.Views"
        xmlns:vm="clr-namespace:RxEvent.ViewModels"
        Title="MainWindow" Height="120" Width="325">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <TextBox Grid.Row="0" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
        
        <TextBlock Grid.Row="1" Text="{Binding ChangedCount, StringFormat=Changed回数:{0}}"/>
        <TextBlock Grid.Row="2" Text="{Binding TextChangedCount, StringFormat=TextChanged回数:{0}}"/>
        <TextBlock Grid.Row="3" Text="{Binding LatestText, StringFormat=最新Text:{0}}" Margin="10,0,0,0"/>
        <TextBlock Grid.Row="4" Text="{Binding PropertyChangedCount, StringFormat=PropertyChanged回数:{0}}"/>
    </Grid>
</Window>
 テキストボックスの値が変更される度に、各イベントのカウントが増加します。
RxEvent.png  
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

カレンダー
09 | 2018/10 | 11
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 - - -
全記事表示リンク

全ての記事を表示する

カテゴリ
タグリスト

月別アーカイブ
11  09  08  07  06  05  04  03  02  01  12  11  10  09  08  07  06  04  03  02  01  12  11  10  09  08  07  06  05  04  03  02  01  12  11  10  09 
最新記事
リンク
最新コメント
検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。