代码之家  ›  专栏  ›  技术社区  ›  Enigmativity

组合反应式框架(Rx)查询以提供正确的UI行为的问题

  •  0
  • Enigmativity  · 技术社区  · 15 年前

    我需要解决的问题是,让搜索屏幕的行为正常工作,但不能完全按照我想要的方式解决它。这是很标准的东西。这就是它的行为方式:

    • 我有一个 文本框 用户可以在其中输入 搜索文本 .
    • 无文本(或仅空白) 搜索按钮 残疾人 .
    • 如果有的话 非空白文本 搜索按钮 启用 .
    • 点击搜索 这个 文本框 以及 搜索按钮 两者都是 残疾人 .
    • 结果回来了 这个 文本框 以及 搜索按钮 两者都是 .

    我有这些可观察到的(通过标准事件的一些扩展方法创建)可用于:

    IObservable<IEvent<TextChangedEventArgs>> textBox.TextChangedObservable()
    IObservable<IEvent<RoutedEventArgs>> button.ClickObservable()
    IObservable<IEvent<LoadingDataEventArgs>> dataSource.LoadingDataObservable()
    IObservable<IEvent<LoadedDataEventArgs>> dataSource.LoadedDataObservable()
    

    我现在有这样的疑问:

    IObservable<bool> dataSourceIsBusy =
        dataSource.LoadingDataObservable().Select(x => true)
        .Merge(dataSource.LoadedDataObservable().Select(x => false));
    
    IObservable<string> textBoxText =
        from x in textBox.TextChangedObservable()
        select textBox.Text.Trim();
    
    IObservable<bool> textBoxTextIsValid =
        from text in textBoxText
        let isValid = !String.IsNullOrEmpty(text)
        select isValid;
    
    IObservable<string> searchTextReady =
        from x in button.ClickObservable()
        select textBox.Text.Trim();
    

    buttonIsEnabled.Subscribe(x => button.IsEnabled = x);
    dataSourceIsBusy.Subscribe(x => textBox.IsEnabled = !x);
    searchTextReady.Subscribe(x => this.ExecuteSearch(x));
    

    (我确实提到了 Subscribe 方法。我有 .ObserveOnDispatcher() 添加到每个observate以确保代码在UI线程上运行。)

    现在,当这个工作时,有一件事让我感到不安。中的select语句 searchTextReady textBox.Text.Trim() 获取当前修剪的搜索文本,但我已经在 textBoxText

    当我尝试以下查询时,会收到重新进入的调用以执行搜索:

    IObservable<string> searchTextReady =
        from text in textBoxText
        from x in button.ClickObservable()
        select text;
    

    以下查询似乎适用于第一个查询,但每次更改文本框中的文本后,搜索将自动执行,而无需单击“搜索”按钮:

    IObservable<string> searchTextReady =
        from text in button.ClickObservable()
            .CombineLatest(textBoxText, (c, t) => t)
        select text;
    

    以下查询要求在单击“搜索”按钮后进行进一步的文本更改,然后无法再次运行:

    IObservable<string> searchTextReady =
        from text in textBoxText
          .SkipUntil(button.ClickObservable())
          .TakeUntil(dataSource.LoadingDataObservable())
        select text;
    

    4 回复  |  直到 15 年前
        1
  •  5
  •   Ana Betts    15 年前

    这些事情本身就很棘手,所以我最后写了 an M-V-VM + Rx library 为了帮助我-事实证明,使用这个库,这个任务非常简单;下面是整个代码, my blog 解释有关这些类如何工作的更多信息:

    public class TextSearchViewModel
    {
        public TextSearchViewModel
        {
            // If there is no text (or whitespace only) then the search button is disabled.
            var isSearchEnabled = this.ObservableForProperty(x => x.SearchText)
                .Select(x => !String.IsNullOrWhitespace(x.Value));
    
            // Create an ICommand that represents the Search button
            // Setting 1 at a time will make sure the Search button disables while search is running
            DoSearch = new ReactiveAsyncCommand(isSearchEnabled, 1/*at a time*/);
    
            // When the user clicks search the text box and the search button are both disabled.
            var textBoxEnabled = DoSearch.ItemsInflight
                .Select(x => x == 0);
    
            // Always update the "TextboxEnabled" property with the latest textBoxEnabled IObservable
            _TextboxEnabled = this.ObservableToProperty(textBoxEnabled, 
                x => x.TextboxEnabled, true);
    
            // Register our search function to run in a background thread - for each click of the Search
            // button, the searchResults IObservable will produce a new OnNext item
            IObservable<IEnumerable<MyResult>> searchResults = DoSearch.RegisterAsyncFunction(textboxText => {
                var client = new MySearchClient();
                return client.DoSearch((string)textboxText);
            });
    
            // Always update the SearchResults property with the latest item from the searchResults observable
            _SearchResults = this.ObservableToProperty(searchResults, x => x.SearchResults);
        }
    
        // Create a standard INotifyPropertyChanged property
        string _SearchText;
        public string SearchText {
            get { return _SearchText; }
            set { this.RaiseAndSetIfChanged(x => x.SearchText, value); }
        }
    
        // This is a property who will be updated with the results of an observable
        ObservableAsPropertyHelper<bool> _TextboxEnabled;
        public bool TextboxEnabled {
            get { return _TextboxEnabled.Value; }
        }
    
        // This is an ICommand built to do tasks in the background
        public ReactiveAsyncCommand DoSearch { get; protected set; }
    
        // This is a property who will be updated with the results of an observable
        ObservableAsPropertyHelper<IEnumerable<MyResult>> _SearchResults;
        public IEnumerable<MyResult> SearchResults {
            get { return _SearchResults.Value; }
        }
    } 
    
        2
  •  1
  •   Enigmativity    15 年前

    谢谢大家在这个问题上的帮助。我无法给任何人答案,因为我发现解决方法是使用 .Switch() 接线员没人回答。

    我最初的问题之一是我不想重复代码。所以我介绍了 Func<string, bool> textIsValid 可以在我的 IObservable<bool> textBoxTextIsValid IObservable<string> searchTextReady .

    Func<string, bool> textIsValid = t => !String.IsNullOrEmpty(t);
    

    现在 searchTextReady

    IObservable<string> searchTextReady =
        (from text in textBoxText
         select (from x in button.ClickObservable().TakeUntil(textBoxText)
                 where textIsValid(text)
                 select text)
        ).Switch();
    

    所以 .开关() IObservable<IObservable<string>> 把它压平成一个 IObservable<string> .

    创建一个 IObservable<IObservable<字符串>> -只需针对一个可观察对象编写一个查询,然后选择其他一些可观察对象。

    例如:

    IObservable<Unit> xs= ...;
    IObservable<string> ys= ...;
    
    IObservable<IObservable<string>> zss = xs.Select(x => ys);
    
    IObservable<string> zs = zss.Switch();
    

    如果更改为:

    IObservable<Unit> clicks = ...;
    IObservable<string> texts = ...;
    Func<string, bool> isValid = ...;
    
    IObservable<IObservable<string>> zss =
        texts.Select(t =>
            clicks
                .Take(1)
                .TakeUntil(texts)
                .Where(x => isValid(t))
                .Select(x => t));
    
    IObservable<string> zs = zss.Switch();
    

    最终观测结果可以描述为:

    “每当文本更改时 不会再次更改,因此文本 是有效的,然后选择值

    我希望这个答案能帮助别人。

        3
  •  0
  •   ozczecho    15 年前

    ForkJoin

    类似于:

        IObservable<string> searchTextReady = Observable.ForkJoin(textBoxText, button.ClickObservable());
        searchTextReady.Subscribe( ....
    
        4
  •  0
  •   PL.    15 年前

    DistinctUntilChanged 是你要找的。

    // low level observables
    var dataSourceLoading = ... // "loading" observable
    var dataSourceLoaded = ... // "loaded" observable
    var textChange = Observable.FromEvent<TextChangedEventArgs>(MyTextBox, "TextChanged");
    var click = Observable.FromEvent<RoutedEventArgs>(MyButton, "Click");
    
    // higher level observables
    var text = textChange.Select(_ => MyTextBox.Text.Trim());
    var emptyText = text.Select(String.IsNullOrWhiteSpace);
    var searchInProgress = dataSourceLoading.Select(_ => true).Merge(dataSourceLoaded.Select(_ => false));
    
    // enable/disable controls
    searchInProgress.Merge(emptyText)
        .ObserveOnDispatcher()
        .Subscribe(v => MyButton.IsEnabled = !v);
    searchInProgress
        .ObserveOnDispatcher()
        .Subscribe(v => MyTextBox.IsEnabled = !v);
    
    // load data 
    click
        .CombineLatest(text, (c,t) => new {c,t})
        .DistinctUntilChanged(ct => ct.c)
        .Subscribe(ct => LoadData(ct.t));
    
    推荐文章