まず、典型的な検索 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/
別の回答では、可変スロットリング機能を個別に見ていきます。