I’ve been soaking up all the new Rx stuff coming out of Microsoft recently. Basically it takes the concept of IEnumerable and flips it around into a type IObservable (gotta love duality…).
One of the areas in myOSity I’ve not been entirely satisfied with is the real-time search capability in the File System dialogs. The feature allows you to type keywords into a text box, and the file list updates below in real-time. However, this was previously entirely synchronously, such that long searches would hang the UI thread and cause unseemly lag in the responsiveness of the keyboard input.
The general format of this old method was like so:
- _app.tbautoSearchByTags.TextChanged += (sender, _) =>
- {
- var tb = (TextBox)sender;
- FileSystemService.Current.VM.SearchCriteria = tb.Text;
- FileSystemService.Current.VM.FilterBySearch();
- };
Pretty standard stuff: I subscribe to the event handler of the textbox’s TextChanged event, and then run my search query, feeding in the the .Text property of the TextBox. This however, yields the laggy responsiveness that is so undesirable.
What I want to do is to only call my search function when the user has paused their typing for some period of time (.5 seconds, for example). I could write my own timer logic, but as you will see, Rx handles this much more elegantly and in a much more composable way…
- var textObserver = (from text in Observable.FromEvent<TextChangedEventArgs>(_app.tbautoSearchByTags, "TextChanged")
- select text).Throttle(TimeSpan.FromSeconds(.5));
-
- _searchObserver = textObserver.Subscribe(textChangedEvent =>
- {
- var tb = (TextBox)textChangedEvent.Sender;
- FileSystemService.Current.VM.SearchCriteria = tb.Text;
- FileSystemService.Current.VM.FilterBySearch();
- });
There are two main pieces to the above code. First I am defining an IObservable (textObserver), which is a lazy Linq query. You’ll also notice the extension method .Throttle, which prevents the observable from firing any data to the observer unless the given time has passed without any events occurring. Said another way, the event stream from the TextChanged event must be quiet for .5 seconds before any observers will receive data. Neat!
The second piece instantiates the observer via the .Subscribe extension. Here I’m using a lamba to create an anonymous observer.
Now with the Rx magic in place, my UI is not making wasteful calls to the search function, but only when the user is “finished” providing input. Improvements are still possible. For example, I could make the search function asynchronous, and implement IObservable<T> on it. This will help free up the UI thread even further while the search function does it’s work, and only return the result when complete.