C# 组合反应式框架(Rx)查询以提供正确的UI行为时出现问题
我试图从Silverlight应用程序中删除更传统的事件处理程序,以支持使用大量Rx查询来提供更好、更易于管理的行为抽象 我需要解决的问题是让搜索屏幕的行为正常工作,但我不能完全按照我想要的方式解决它。这是相当标准的东西。这就是它的行为方式:C# 组合反应式框架(Rx)查询以提供正确的UI行为时出现问题,c#,.net,silverlight,system.reactive,C#,.net,Silverlight,System.reactive,我试图从Silverlight应用程序中删除更传统的事件处理程序,以支持使用大量Rx查询来提供更好、更易于管理的行为抽象 我需要解决的问题是让搜索屏幕的行为正常工作,但我不能完全按照我想要的方式解决它。这是相当标准的东西。这就是它的行为方式: 我有一个文本框,用户可以在其中输入搜索文本 如果没有文本(或仅空白)则搜索按钮被禁用 如果存在非空白文本,则搜索按钮处于启用状态 当用户单击搜索时,文本框和搜索按钮都被禁用 当结果返回时文本框和搜索按钮均已启用 我有这些观测值(通过标准事件上的一些扩展
- 我有一个文本框,用户可以在其中输入搜索文本
- 如果没有文本(或仅空白)则搜索按钮被禁用
- 如果存在非空白文本,则搜索按钮处于启用状态
- 当用户单击搜索时,文本框和搜索按钮都被禁用
- 当结果返回时文本框和搜索按钮均已启用
IObservable<IEvent<TextChangedEventArgs>> textBox.TextChangedObservable()
IObservable<IEvent<RoutedEventArgs>> button.ClickObservable()
IObservable<IEvent<LoadingDataEventArgs>> dataSource.LoadingDataObservable()
IObservable<IEvent<LoadedDataEventArgs>> dataSource.LoadedDataObservable()
(我确实保留了对Subscribe
方法返回的一次性文件的引用。我已将.ObserveOnDispatcher()
添加到每个可观察文件中,以确保代码在UI线程上运行。)
现在,虽然这样做有效,但有一件事让我很烦。searchTextReady
中的select语句调用textBox.Text.Trim()
以获取当前修剪的搜索文本,但我已经在textBoxText
中完成了此操作。我真的不想重复我自己的话,所以我想创建一个结合了这些观察值的查询,这就是我失败的地方
当我尝试以下查询时,会收到重入调用以执行搜索:
IObservable<string> searchTextReady =
from text in textBoxText
from x in button.ClickObservable()
select text;
IObservable searchTextReady=
从textBoxText中的文本
从x进入按钮。单击可观察()
选择文本;
以下查询似乎适用于第一个查询,但每次我更改文本框中的文本后,搜索将自动执行,而无需单击搜索按钮:
IObservable<string> searchTextReady =
from text in button.ClickObservable()
.CombineLatest(textBoxText, (c, t) => t)
select text;
IObservable searchTextReady=
从按钮中的文本。单击可观察()
.CombineTest(textBoxText,(c,t)=>t)
选择文本;
以下查询需要在单击搜索按钮后进行进一步的文本更改,然后再次运行失败:
IObservable<string> searchTextReady =
from text in textBoxText
.SkipUntil(button.ClickObservable())
.TakeUntil(dataSource.LoadingDataObservable())
select text;
IObservable searchTextReady=
从textBoxText中的文本
.SkipUntil(button.ClickObservable())
.TakeUntil(dataSource.LoadingDataObservable())
选择文本;
有什么办法可以让这一切顺利进行吗?你试过了吗
比如:
IObservable<string> searchTextReady = Observable.ForkJoin(textBoxText, button.ClickObservable());
searchTextReady.Subscribe( ....
IObservable searchtext ready=Observable.ForkJoin(textBoxText,button.ClickObservable());
searchTextReady.Subscribe(。。。。
这类事情本身就很棘手,因此我最终写了一些文章来帮助我解决问题——事实证明,有了这个库,这项任务非常简单;下面是全部代码,详细解释了这些类的工作原理:
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; }
}
}
公共类TextSearchViewModel
{
公共文本搜索视图模型
{
//如果没有文本(或仅空白),则禁用搜索按钮。
var isSearchEnabled=this.ObservableForProperty(x=>x.SearchText)
.Select(x=>!String.IsNullOrWhitespace(x.Value));
//创建表示搜索按钮的ICommand
//每次设置1将确保搜索按钮在搜索运行时禁用
DoSearch=新的ReactiveAsyncCommand(isSearchEnabled,一次1/*);
//当用户单击“搜索”时,文本框和“搜索”按钮均被禁用。
var textBoxEnabled=DoSearch.ItemsInflight
。选择(x=>x==0);
//始终使用最新的TextboxEnabled IObservable更新“TextboxEnabled”属性
_TextboxEnabled=this.ObservableToProperty(TextboxEnabled,
x=>x.TextboxEnabled,true);
//注册我们的搜索功能在后台线程中运行-每次点击搜索
//按钮,searchResults IObservable将生成一个新的OnNext项
IObservable searchResults=DoSearch.RegisterAsyncFunction(textboxText=>{
var client=new MySearchClient();
返回client.DoSearch((string)textboxText);
});
//始终使用可观察的SearchResults中的最新项更新SearchResults属性
_SearchResults=this.ObservableToProperty(SearchResults,x=>x.SearchResults);
}
//创建标准INotifyPropertyChanged属性
字符串搜索文本;
公共字符串搜索文本{
获取{return\u SearchText;}
设置{this.RaiseAndSetIfChanged(x=>x.SearchText,value);}
}
//这是一个属性,将使用可观察到的结果进行更新
ObservablePropertyHelper\u TextboxEnabled;
公共bool TextboxEnabled{
获取{return\u TextboxEnabled.Value;}
}
//这是一个ICommand,用于在后台执行任务
public ReactiveAsyncCommand DoSearch{get;protected set;}
//这是一个属性,将使用可观察到的结果进行更新
ObservablePropertyHelper\u搜索结果;
公共IEnumerable搜索结果{
获取{return\u SearchResults.Value;}
}
}
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));
//低级可观测值
var数据源加载=…/“加载”可观察
var dataSourceLoaded=…/“loaded”可观察
var textChange=Observable.FromEvent(MyTextBox,“TextChanged”);
var click=Observable.FromEvent(MyButton,“click”);
//高级可观测
var text=textChange.Select(=>MyTextBox.text.Trim());
var emptyText
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; }
}
}
// 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));
Func<string, bool> textIsValid = t => !String.IsNullOrEmpty(t);
IObservable<string> searchTextReady =
(from text in textBoxText
select (from x in button.ClickObservable().TakeUntil(textBoxText)
where textIsValid(text)
select text)
).Switch();
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();