Use Reactive Extensions to throttle TextChanged events and subscribe to search results

Download the demo project RxSearch

rx

Are you new to Reactive Extensions and looking for a quick tutorial on how to perform a few common tasks in a Windows Store App? In this tutorial you will learn how to throttle TextBox.TextChanged events and use them to search in a collection. You will see how you subscribe to the search results and inform your UI of changes.

Why am I writing about Rx?

wunderAt 6Wunderkinder we make Wunderlist, a multi-platform, multi-user, online/offline, asynchronous application that allows you to organize your life and business.

The multi-user/asynchronous nature of the application presents us with a lot of challenges. We need a local repository (model) that holds the data we need when we create ViewModels that support the Views. But what happens if another user edits data you are looking at right now? A clever sync mechanism changes the model and these changes need to be broadcast to the viewmodels. For this we use Reactive Extensions.

When my colleagues Chris and Martin showed me Rx for the first time, I had no idea what I was looking at. So we started with a very simple example that uses Rx to listen to TextChanged events, throttles them and uses them to perform a search. Then, instead of having a function returning a list of search results, the search function returns an IObservable to which the caller can subscribe. The UI looks like this:

rx

How would you do this without Rx?

  • Add a KeyDown event handler to the TextBox
  • Use a DispatcherTimer to collect a few keystrokes before searching
  • Call a search function and return the result
  • Update the ListBox

Rx allows you to do this in 1 stream of data/events. Let’s start with a fake search function to return some data.

Use Rx to perform the search

  • Start a blank Windows 8.1 app
  • Use Nuget to add Reactive Extensions – Main Library
  • Use Nuget to add Reactive Extensions – XAML Support Library
  • Create class Api.cs and add the following code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;

namespace RxSearch
{
    public class Api
    {
        public IObservable<List<string>> Search(string filter)
        {
            var list = new List<string> { "Vera", "Chuck", "Dave", "John", "Paul", "Ringo", "George" };
            var filteredList = list.Where(x => x.ToLower().Contains(filter.ToLower()));
            return Observable.Return(filteredList.ToList());
        }
    }
}

The Search function does not return a List<string> but an IObservable<List<string>>. This changes a pull-based result to a push-based result. To return a value, we use Observable.Return(list) which returns an IObservable<List<string>> and completes the stream.

Creat the UI in MainPage.xaml:

<Grid Background="#FF96B209">
  <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
  <TextBox x:Name="textbox" Width="400" Margin="0,0,0,20" FontSize="20"/>
  <ListBox x:Name="listbox" Width="400" Height="400" FontSize="20" />
  </StackPanel>
</Grid>

To use the search you edit MainPage.xaml.cs:

using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using Windows.UI.Xaml.Controls;

namespace RxSearch
{
    public sealed partial class MainPage
    {
        public MainPage()
        {
            InitializeComponent();

            var api = new Api();

            var textchanges = Observable.FromEventPattern<TextChangedEventHandler, TextChangedEventArgs>(
                h => textbox.TextChanged += h,
                h => textbox.TextChanged -= h
                ).Select(x => ((TextBox)x.Sender).Text);

            textchanges
                .Throttle(TimeSpan.FromMilliseconds(300)) // result on threadpool 
                .Select(api.Search)
                .Switch()
                .ObserveOnDispatcher() // send back to dispatcher
                .Subscribe(OnSearchResult);

            api.Search("").Subscribe(OnSearchResult);
        }

        private void OnSearchResult(List<string> list)
        {
            listbox.ItemsSource = list;
        }
    }
}

I split the stream into two parts. The event from the TextBox and the data from the Search result.

TextChanges:

var textchanges = Observable.FromEventPattern<TextChangedEventHandler, TextChangedEventArgs>(
                h => textbox.TextChanged += h,
                h => textbox.TextChanged -= h
                ).Select(x => ((TextBox)x.Sender).Text);

textchanges is now an IObservable<string> that is…well…observable. To observe it, we subscribe to it.

Subscribe to IObservable

Update!

In my initial post I used SelectMany to get to the actual data. Then I got this feedback from  the Rx creators themselves.

switch

So I tried Select().Switch() and it works beautifully!

textchanges
 .Throttle(TimeSpan.FromMilliseconds(300)) // result on threadpool 
 .Select(api.Search)
 .Switch()
 .ObserveOnDispatcher() // send back to dispatcher
 .Subscribe(OnSearchResult);

And that’s really it. OnSearchResult is just a handler to actually fill the ListBox:

 private void OnSearchResult(List<string> list)
        {
            listbox.ItemsSource = list;
        }

Download the demo project RxSearch

Change XAML designer background color in Visual Studio

If you use the Visual Studio 2013 XAML designer with the light theme enabled and you are designing user controls or pages with transparent backgrounds, it might be hard to read the text:

vsbefore

Solution

One quick solution is to change the XAML UI Designer colors.

  • Click TOOLS > OPTIONS… > Environment > Fonts and Colors
  • Under Show settings for choose XAML UI Designer
  • Change Item foreground and Item background to the same color to prevent a checkerboard pattern

 

vscolor

Now you can read your text again:

vsres

Cross platform development with C#

phoneAre you writing apps for multiple platforms? Do you wonder what you can and cannot share when writing C# for Windows Phone, Windows 8 and WPF?

Me and Christian Lang tell you all about it! We’ve prepared a great talk where you learn how to write an application and convert it to a multi-targeted app. The goal is to write native apps that respect the UI paradigms and capabilities of the platform, while sharing as much of the code as possible in the Business and Data layer:

share

We talked & coded at the .NET user group here in Berlin:

cross

And we will do it again at Spartakiade (March 23, 2014) and at Developer Week 2014 (July 14-17, 2014) in Nürnberg.

Interested?

Come see us talk about Cross Platform development with C# at Spartakiade in Berlin or Developer Week in Nürnberg. Can’t make it? Send me an email (loek@6wunderkinder.com) to discuss the possibilities to talk at your company!

First aid for detecting memory leaks in a Windows Store App

fix-water-leak-reno-lg1Before submitting your Windows 8 app to the Windows Store, it never hurts checking for memory problems. This article is about detecting these problems in an early stage of development. I’ll show you a super easy trick to do this without the need for memory profilers. The solution is to put a TextBlock on each page that shows the memory currently in use. The good thing is that after a while you get an understanding about how much memory your app “should use”. If this changes suddenly, you are in a good position to guess why it happened and the solution is only one Git-action away.

Showing memory usage on your page

  • Put this TextBlock in your MainPage.xaml
<TextBlock TextWrapping="Wrap" x:Name="mem" FontSize="21.333"/>
  • And add the following code to your OnNavigatedTo event handler in MainPage.xaml.cs:
mem.Text = "Memory in use: " + Math.Round(GC.GetTotalMemory(true) / 1000000.0, 2) + "mb";

Now see what happens if I navigate between 2 pages in my app. Watch the MEMORY USE text in the left upper corner:

mem

Clearly my app has a major memory leak. If your memory increases with 1 MB each time you navigate, older pages are probably not garbage collected.

Meten is weten!

Indicating memory leaks is as simple as adding 2 lines of code to each page. It will help you to get a good feeling of memory usage regardless of problems.

Choosing a specific master page for each Windows Store App page

At 6Wunderkinder we are creating the Windows Store Version (WinRT) of Wunderlist. Our app basically has two kinds of pages. One is where you are logged in and one is where you are not logged in. We use a master page to take care of backgrounds and other stuff that is shared across pages. When all pages are the same, you just replace Window.Current.Content as Frame (in App.xaml.cs) with your master page (as described by Fons Sonnemans) and load all other pages within that masterpage. But what if you want to support multiple masterpages?

master

Well, it turns out it is very easy to do this and you can even use the default Frame from App.xaml.cs.  Download a demo project or follow the next steps to create Application Navigation where pages can use a specific masterpage or no masterpage at all!

Step 1: Create a Masterpage

  • Create a new blank page (MasterPage.xaml) that acts as a masterpage. You can include some app-wide xaml like this:
<Page
    x:Class="MasterPageTest.MasterPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="#FF363333">
        <Grid.RowDefinitions>
            <RowDefinition Height="43*"/>
            <RowDefinition Height="213*"/>
        </Grid.RowDefinitions>
        <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Master!" VerticalAlignment="Center" FontSize="48" Margin="100,0,0,0"/>
        <TextBox HorizontalAlignment="Center" x:Name="search" TextWrapping="Wrap" Text="TextBlock1" VerticalAlignment="Center" FontSize="18.667" Width="200"/>

        <Frame x:Name="ContentFrame" Content="Frame" Grid.Row="1" Margin="100"/>

        <StackPanel Margin="0,0,100,0" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button Content="MainPage" Click="ButtonBase_OnClick1" HorizontalAlignment="Center" VerticalAlignment="Center" Width="150" Height="80" Margin="0"/>
            <Button Content="Second Page" Click="ButtonBase_OnClick2" HorizontalAlignment="Center" VerticalAlignment="Center" Width="150" Height="80" Margin="0"/>
            <Button Content="Logout" Click="ButtonBase_OnClick3" HorizontalAlignment="Center" VerticalAlignment="Center" Width="150" Height="80" Margin="0"/>
        </StackPanel>
    </Grid>
</Page>

Don’t worry about the Button Click Handlers. We will implement them later. Important in the code above is the placeholder for your content:

<Frame x:Name="ContentFrame" Content="Frame" Grid.Row="1" Margin="100"/>

Step 2: Create a navigation helper

  • Add the class NavigateService.cs to your project and enter this code:
    public class NavigateService
    {
        private static readonly Lazy<NavigateService> lazy = new Lazy<NavigateService>(() => new NavigateService());
        public static NavigateService Instance { get { return lazy.Value; } }

        public Frame Frame { get; set; }

        public void Navigate(Type pageType)
        {
            Frame.Navigate(pageType);
        }

        public void Navigate(Type pageType, Type masterPageType)
        {
            var masterPage = Frame.Content as Page;
            if (masterPage != null && masterPage.GetType() != masterPageType)
            {
                Frame.Navigate(masterPageType);
                masterPage = Frame.Content as Page;
            }
            var contentFrame = masterPage.FindName("ContentFrame") as Frame;
            contentFrame.Navigate(pageType);
        }
    }

Step 3: Assign the rootFrame to the NavigationService

  • Open App.xaml.cs and the OnLaunched method with:
protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
            {
                rootFrame = new Frame();
                Window.Current.Content = rootFrame;
            }

            NavigateService.Instance.Frame = rootFrame;

            if (rootFrame.Content == null) {
                rootFrame.Navigate(typeof(LoginPage), e.Arguments);
            }
            Window.Current.Activate();
        }

Step 4: Add click handlers to navigate

  • Open Masterpage.xaml.cs and add the following handlers:
private void ButtonBase_OnClick1(object sender, RoutedEventArgs e)
{
  NavigateService.Instance.Navigate(typeof(MainPage),  typeof(MasterPage));
}
private void ButtonBase_OnClick2(object sender, RoutedEventArgs e)
{
  NavigateService.Instance.Navigate(typeof(SecondPage), typeof(MasterPage));
}
private void ButtonBase_OnClick3(object sender, RoutedEventArgs e)
{
   NavigateService.Instance.Navigate(typeof(LoginPage));
}

You will get some errors because you don’t have a SecondPage.xaml and LoginPage.xaml yet.

  • Create SecondPage.xaml and put a page title on it so you will recognize it once loaded in the masterpage.
  • Open MainPage.xaml and put a title on that also
  • Create Login.xaml and enter the following XAML:
<Grid>
    <Button Content="Login" Click="ButtonBase_OnClick"   HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Height="100"/>
</Grid>
  • Open LoginPage.xaml.cs and add the following handler:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
   NavigateService.Instance.Navigate(typeof(MainPage), typeof(MasterPage));
}

Now run your code and you will see that the Login page uses no masterpage and the other pages share Masterpage.xaml.

This is a very basic masterpage navigation system. For our app we extended it with viewmodel injection. If you need help with that, just email me!