3

ユーザーが入力をやめた後、またはSearchを押したときに自動的に検索する単純な検索フィールドがあります。最初の部分は、次の方法で簡単に実現できます

var inputs = Observable.FromEventPattern<SomeEventArgs>(searchBar, "TextChanged")
                       .Select(pattern => pattern.EventArgs.SearchText)
                       .Throttle(TimeSpan.FromMilliseconds(500));

このように実際の検索を行うためにそれをチェーンすることもできます

var results = from query in inputs
              from results in Observable.FromAsync<Something>(() => Search(query))
              select results;

しかし問題は、ユーザーが検索ボタンを押したときに先にスキップする方法がないことです。私のRxの理解から、コードはおそらく次のようなものになるはずです:

// inputs = the same event stream without Throttle
// buttonClicks = construct event stream for search button clicks

// ??? somehow make a third stream which lets a value through
// ??? only after a delay and when the value hasn't changed,
// ??? OR when the second stream yields a value

// async search

Stopwatchのようなものを使用して、ユーザーの入力に応じてリセットすることで、これを命令的に記述する方法を確認できます。クリックが発生した場合は、スキップすることができます。しかし、Rxの世界では、おそらく次のようになります(疑似linqコードを許してください)

from query in inputs
where (query.isLast() and query.timestamp > 500.ms.ago) or buttonClicked
...

2 番目のイベント ソースが値を生成する場合は、最後のクエリ入力をすぐに取得できる必要があります。値がない場合は、Throttle が使用されているかのように、指定された遅延だけ待機します

4

1 に答える 1

4

まず、典型的な検索 Rx は次のようになります。

var searchResults = Observable.FromEventPattern<SomeEventArgs>(searchBar, "TextChanged")
                              .Select(pattern => pattern.EventArgs.SearchText)
                              .Throttle(TimeSpan.FromMilliseconds(500))
                              .DistinctUntilChanged()
                              .Select(text => Observable.Start(() => Search(text)))
                              .Switch()

Select は結果ストリームのストリームを提供し、Switch は最後に作成されたストリームを返します。DistinctUntilChanged を追加して、クエリが重複して送信されないようにしました。

あなたが説明したことの1つの戦略は、スロットル期間の後、またはボタンがクリックされた場合に放出するスロットル期間セレクターをスロットルに提供することです。これがどのように行われるかを示すために、Rx 2.1 以外のライブラリを使用しないサンプル ViewModel をまとめました。これが ViewModel 全体です。View と Repository はご想像にお任せしますが、それらが何をするかは明らかです。

最後の警告 - このサンプルは簡潔にし、理解を曇らせる可能性のある不必要な詳細を省略しようとしたため、これは本番環境の準備ができていません。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using StackOverflow.Rx.Annotations;
using StackOverflow.Rx.Model;

namespace StackOverflow.Rx.ProductSearch
{
    public class ClassicProductSearchViewModel : INotifyPropertyChanged
    {
        private string _query;
        private IProductRepository _productRepository;
        private IList<Product> _productSearchResults;

        public ClassicProductSearchViewModel(IProductRepository productRepository)
        {
            _productRepository = productRepository;
            // Wire up a Button from the view to this command with a binding like
            // <Button Content="Search" Command="{Binding ImmediateSearch}"/>
            ImmediateSearch = new ReactiveCommand();

            // Wire up the Query text from the view with
            // a binding like <TextBox MinWidth="100" Text="{Binding Query, UpdateSourceTrigger=PropertyChanged}"/>
            var newQueryText = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
                h => PropertyChanged += h,
                h => PropertyChanged -= h)
                .Where(@event => @event.EventArgs.PropertyName == "Query")
                .Select(_ => Query);

            // This duration selector will emit EITHER after the delay OR when the command executes
            var throttleDurationSelector = Observable.Return(Unit.Default)
                                                     .Delay(TimeSpan.FromSeconds(2))
                                                     .Merge(ImmediateSearch.Select(x => Unit.Default));


            newQueryText
                .Throttle(x => throttleDurationSelector)
                .DistinctUntilChanged()
                /* Your search query here */
                .Select(
                    text =>
                        Observable.StartAsync(
                            () => _productRepository.FindProducts(new ProductNameStartsWithSpecification(text))))
                .Switch()
                .ObserveOnDispatcher()
                .Subscribe(products => ProductSearchResults = new List<Product>(products));
        }

        public IList<Product> ProductSearchResults
        {
            get { return _productSearchResults; }
            set
            {
                if (Equals(value, _productSearchResults)) return;
                _productSearchResults = value;
                OnPropertyChanged();
            }
        }

        public ReactiveCommand ImmediateSearch { get; set; }

        public string Query
        {
            get { return _query; }
            set
            {
                if (value == _query) return;
                _query = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    // A command that is also an IObservable!
    public class ReactiveCommand : ICommand, IObservable<object>
    {
        private bool _canExecute = true;
        private readonly Subject<object> _execute = new Subject<object>();

        public ReactiveCommand(IObservable<bool> canExecute = null)
        {
            if (canExecute != null)
            {
                canExecute.Subscribe(x => _canExecute = x);
            }
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute;
        }

        public void Execute(object parameter)
        {
            _execute.OnNext(parameter);
        }

        public event EventHandler CanExecuteChanged;

        public IDisposable Subscribe(IObserver<object> observer)
        {
            return _execute.Subscribe(observer);
        }
    }
}

Rxx や ReactiveUI など、このコードを簡単にするライブラリがあります。ここではそれらを使用していないため、最小限の「魔法」が行われています。

この例の ReactiveCommand は、ReactiveUI に含まれているものの単純な実装です。これは、同時にコマンドと IObservable のように見えます。実行されるたびに、コマンド パラメーターがストリーミングされます。

以下は、著者のブログの ReactiveUI を使用した例です: http://blog.paulbetts.org/index.php/2010/06/22/reactivexaml-series-reactivecommand/

別の回答では、可変スロットリング機能を個別に見ていきます。

于 2013-10-24T14:30:04.310 に答える