【问题标题】:Reactive Extensions Instant Search for WPF/MVVMWPF/MVVM 的反应式扩展即时搜索
【发布时间】:2014-04-19 09:49:46
【问题描述】:

我想实现一个 TextBox,当您键入时,结果会立即出现在另一个 ListBox 中。我一直在寻找响应式扩展 (Rx) 的示例,我发现的所有示例都使用 Observable.FromEventPattern() 以及 TextBox 的 TextChanged 事件:

我将 WPF 与 MVVM 一起使用,因此我无法直接访问 TextBox 或其事件。

我还偶然发现了 this answer,它展示了如何在 MVVM 设置中使用 Observable.FromEventPattern(),但我希望有比捕获每个 PropertyChanged 事件更好的东西。

除了Observable.FromEventPattern(),有什么好的替代品可以很好地与 WPF/MVVM 配合使用?

【问题讨论】:

    标签: c# wpf mvvm system.reactive


    【解决方案1】:

    ReactiveUI 合作。

    解决方案基于blog postReactiveUI,但那里的代码有点out of date。我在 BitBucket 上托管 solution 以方便访问。它使用 ReactiveUI 5.5.1。

    这是该解决方案中的 ViewModel。 SearchText 绑定到用户键入查询的视图中的 TextBox,而 SearchResults 绑定到显示结果的 ListBox

    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;
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      这是我将在这种情况下使用的方法:

              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));
      

      executeSearch 代码具有以下签名:Func&lt;string, IObservable&lt;string&gt;&gt;

      这个查询的重要部分是最后的Switch 语句。它只返回最新的 observable 的结果,将 IObservable&lt;IObservable&lt;string&gt;&gt; 变成 IObservable&lt;string&gt;

      .ObserveOnDispatcher().ObserveOn(Scheduler.Default) 的调用确保可观察查询的不同部分发生在正确的线程上。

      【讨论】:

      • 感谢您提供有用的代码示例。但是,我的问题专门要求避免 Observable.FromEventPattern() 的 MVVM 友好解决方案。您正在使用它,并结合不适合 MVVM 的事件处理程序。
      • @Gigi - 您可能想使用混合方法,然后给出您接受的答案并不能很好地仅返回最新的查询结果。一对长时间运行的查询可以按任何顺序返回。我使用.Switch() 会阻止这种情况。
      【解决方案3】:

      您可以使用行为来使其正常工作。 Catel 的UpdateBindingOnTextChanged behavior 就是一个很好的例子。可以这样使用:

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

      这将在更改和实际更新之间产生 500 毫秒的延迟。

      【讨论】:

      • 这听起来很有趣,但据我了解,它并不能完全满足要求。即时搜索功能的一部分是,如果在旧搜索的结果尚未到达时发出新搜索,则旧搜索将被取消。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多