Question

I have a working implementation of a ReactiveUI asynchronous search routine within an WPF MVVM application based off of the following (legacy) example:

http://blog.paulbetts.org/index.php/2010/07/05/reactivexaml-series-implementing-search-with-observableaspropertyhelper/

public class TheViewModel : ReactiveObject
{
    private string query;

    private readonly ObservableAsPropertyHelper<List<string>> matches;

    public TheViewModel()
    {
        var searchEngine = this.ObservableForProperty(input => input.Query)
                .Value()
                .DistinctUntilChanged()
                .Throttle(TimeSpan.FromMilliseconds(800))
                .Where(query => !string.IsNullOrWhiteSpace(query) && query.Length > 1);

        var search = searchEngine.SelectMany(TheSearchService.DoSearchAsync);

        var latestResults =
            searchEngine.CombineLatest(search, (latestQuery, latestSearch) => latestSearch.Query != latestQuery ? null : latestSearch.Matches)
                .Where(result => result != null);

        matches = latestResults.ToProperty(this, result => result.Matches);
    }

    public string Query
    {
        get
        {
            return query;
        }
        set
        {
            this.RaiseAndSetIfChanged(ref query, value);
        }
    }

    public List<string> Matches
    {
        get
        {
            return matches.Value;
        }
    }
} 

The ReactiveXAML is working as expected and I can easily bind to the Matches property like so

<ListBox Grid.Row="1" Margin="6" ItemsSource="{Binding Matches}" />

However I would like to refactor TheSearchService.DoSearchAsync() to return a more complex result structure sort of like this:

public class SearchResult
{
    public string Query { get; set; }
    public List<string> Matches { get; set; }
    public int MatchCount { get; set; }
    public double SearchTime { get; set; }
}

The matches would still be represented as a List<string> which would be bound to the same ListBox but I'd like also like to bind to a string metadata property on each search that returns the match count and search time in some sort of format such as:

 string.Format("Found {0} matches in {1}s", x.MatchCount, x.SearchTime)

How could I change the ViewModel implementation to allow multiple bindings for each search?

Working implementation based on accepted answer

public class TheViewModel : ReactiveObject
{
    ...

    private readonly ObservableAsPropertyHelper<SearchResult> results;

    public TheViewModel()
    {
        ...

        var latestResults = searchEngine.CombineLatest(search, (latestQuery, latestSearch) => latestSearch.Query != latestQuery ? null : latestSearch)
                .Where(result => result != null);

        results = latestResults.ToProperty(this, result => result.Result);
    }

    ...

    public SearchResult Result
    {
        get
        {
            return results.Value;
        }
    }
}

Here is the view

<StackPanel>
    <TextBox Text="{Binding Query, UpdateSourceTrigger=PropertyChanged}"
             Margin="6"
             FontSize="26" />
    <TextBlock>
        <TextBlock.Text>
            <MultiBinding StringFormat="Found {0} matches in {1}s">
                <Binding Path="Result.MatchCount" />
                <Binding Path="Result.SearchTime" />
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>
</StackPanel>
<ListBox Grid.Row="1"
     Margin="6"
     ItemsSource="{Binding Result.Matches}" />
Was it helpful?

Solution

In your viewmodel, instead of having instance of Matches create property of type SearchResult say MySearchResult. Also implement INotifyPropertyChanged on SearchResult. Update the SearchResult after every search operation

Now your listbox bindings will be like <ListBox Grid.Row="1" Margin="6" ItemsSource="{Binding MySearchResult.Matches}" />

To show search result you can have textblock like below:

 <TextBlock>
    <TextBlock.Text>
        <MultiBinding StringFormat="Found {0} matches in {1}s">
            <Binding Path="MySearchResult.MatchCount"/>
             <Binding Path="MySearchResult.SearchTime"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top