I'm trying to remove the more tradition event handlers from a Silverlight application in favour of using a number of Rx queries to provide a better, easier to manage, behavioural abstraction.
The problem that I need to solve, but can't quite crack it the way I want, is getting the behaviour of a search screen working. It's pretty standard stuff. This is how it should behave:
- I have a text box where the user can enter the search text.
- If there is no text (or whitespace only) then the search button is disabled.
- When there is non-whitespace text then the search button is enabled.
- When the user clicks search the text box and the search button are both disabled.
- When the results come back the text box and the search button are both enabled.
I have these observables (created via some extension methods over the standard events) to work with:
IObservable<IEvent<TextChangedEventArgs>> textBox.TextChangedObservable()
IObservable<IEvent<RoutedEventArgs>> button.ClickObservable()
IObservable<IEvent<LoadingDataEventArgs>> dataSource.LoadingDataObservable()
IObservable<IEvent<LoadedDataEventArgs>> dataSource.LoadedDataObservable()
I have these queries that work at the moment:
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();
And then these subscriptions to wire it all up:
buttonIsEnabled.Subscribe(x => button.IsEnabled = x);
dataSourceIsBusy.Subscribe(x => textBox.IsEnabled = !x);
searchTextReady.Subscribe(x => this.ExecuteSearch(x));
(I do keep references to the disposables returned by the Subscribe
methods. I have .ObserveOnDispatcher()
added to each observable to ensure the code runs on the UI thread.)
Now while this works there is one thing that bugs me. The select statement in searchTextReady
calls textBox.Text.Trim()
to obtain the current trimmed search text, but I already have done this in the textBoxText
observable. I really don't want to repeat myself so I would like to create a query that combines these observables and this is where I'm failing.
When I try the following query I get re-entrant calls to execute the search:
IObservable<string> searchTextReady =
from text in textBoxText
from x in button.ClickObservable()
select text;
The following query it seems to work for the first query, but then each time I change the text in the text box thereafter the search is automatically executed without clicking the search button:
IObservable<string> searchTextReady =
from text in button.ClickObservable()
.CombineLatest(textBoxText, (c, t) => t)
select text;
The following query requires a further text change to occur after the search button is clicked and then fails to run again:
IObservable<string> searchTextReady =
from text in textBoxText
.SkipUntil(button.ClickObservable())
.TakeUntil(dataSource.LoadingDataObservable())
select text;
Any ideas how I can make this work?