Reactive Extensions Instant Search for WPF/MVVM

后端 未结 3 1744
误落风尘
误落风尘 2020-12-19 18:20

I would like to implement a TextBox where, as you type, results appear instantly in another ListBox. I\'ve been looking for examples with Reactive Extensions (Rx), and all o

相关标签:
3条回答
  • 2020-12-19 18:35

    This is the kind of approach that I would use in this instance:

            var query = Observable.FromEventPattern
                <TextChangedEventHandler, TextChangedEventArgs>(
                    h => textBox1.TextChanged += h,
                    h => textBox1.TextChanged -= h)
                .Throttle(TimeSpan.FromMilliseconds(100))
                .ObserveOnDispatcher()
                .Select(x => textBox1.Text)
                .DistinctUntilChanged()
                .Do(x => listBox1.Items.Clear())
                .ObserveOn(Scheduler.Default)
                .Select(x => executeSearch(x))
                .Switch()
                .ObserveOnDispatcher();
    
            query.Subscribe(x => listBox1.Items.Add(x));
    

    The executeSearch code has the following signature: Func<string, IObservable<string>>.

    The important part with this query is the final Switch statement. It turns an IObservable<IObservable<string>> into a IObservable<string> by only returning the results of the latest observable.

    The calls to .ObserveOnDispatcher() & .ObserveOn(Scheduler.Default) ensure that different parts of the observable query happen on the correct threads.

    0 讨论(0)
  • 2020-12-19 18:45

    Got this working with ReactiveUI.

    The solution is based on a blog post at ReactiveUI, but the code there is a little bit out of date. I am hosting the solution on BitBucket for ease of access. It uses ReactiveUI 5.5.1.

    This is the ViewModel from that solution. SearchText is bound to a TextBox in the View where the user types his query, while SearchResults is bound to a ListBox displaying the results.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Collections.ObjectModel;
    using System.Threading.Tasks;
    using System.ComponentModel;
    using System.Reactive.Linq;
    using System.Windows.Input;
    
    using ReactiveUI;
    
    namespace ReactiveExtensionsSearch
    {
        public class MainWindowViewModel : ReactiveObject
        {
            private string[] _repository = new string[]
                { "Mario", "Maria", "Gigi", "Jack", "James", "Jeremy" };
            private ObservableAsPropertyHelper<ObservableCollection<string>> _searchResults;
            private string _searchText;
            private ICommand _executeSearchCommand;
    
            public string SearchText
            {
                get
                {
                    return _searchText;
                }
                set
                {
                    this.RaiseAndSetIfChanged(ref _searchText, value);
                }
            }
    
            public ObservableCollection<string> SearchResults
            {
                get
                {
                    return _searchResults.Value;
                }
            }
    
            public MainWindowViewModel()
            {
                var executeSearchCommand = new ReactiveCommand();
                var results = executeSearchCommand.RegisterAsyncFunction(s => { return ExecuteSearch(s as string); });
                _executeSearchCommand = executeSearchCommand;
    
                this.ObservableForProperty<MainWindowViewModel, string>("SearchText")
                    .Throttle(TimeSpan.FromMilliseconds(800))
                    .Select(x => x.Value)
                    .DistinctUntilChanged()
                    .Where(x => !string.IsNullOrWhiteSpace(x))
                    .Subscribe(_executeSearchCommand.Execute);
    
               _searchResults = new ObservableAsPropertyHelper<ObservableCollection<string>>(results, _ => raisePropertyChanged("SearchResults"));
            }
    
            private ObservableCollection<string> ExecuteSearch(string searchText)
            {
                var q = from s in _repository where s.ToLower().StartsWith(searchText.ToLower()) select s;
                var results = new ObservableCollection<string>(q);
                return results;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-19 18:49

    You can use a behavior to get this working. A great example is the UpdateBindingOnTextChanged behavior of Catel. It can be used like this:

    <TextBox Text="{Binding SearchParam, Mode=TwoWay}">
        <i:Interaction.Behaviors>
            <catel:UpdateBindingOnTextChanged UpdateDelay="500" />
        </i:Interaction.Behaviors>
    </TextBox>
    

    This will create a 500ms delay between the change and the actual update.

    0 讨论(0)
提交回复
热议问题