Eine Suchfunktion kann auf verschiedene Weise in eine App integriert werden. In den MSDN Beispielen wird oft ein SearchBox-Element verwendet, das beim Absenden der Suchanfrage auf eine separate Ergebnisseite weiterleitet. Auf dieser erfolgt dann der eigentliche Suchvorgang und die Darstellung der Ergebnisse. Während die Suchbegriffe eingetippt werden, können unterhalb des Suchfeldes hier auch bereits Suchvorschläge angezeigt werden, um dem Benutzer direkt mögliche Ergebnisse anzubieten.

Soll eine Suchfunktion auf einer Seite genutzt werden, die eine Auflistung von Elementen enthält, bietet sich noch ein alternatives Vorgehen für den Suchvorgang an. Hierbei kann die Suche beispielsweise in Echtzeit während des Tippens auf die angezeigten Elemente angewendet werden. Dadurch ist die Anzeige von Suchvorschlägen nicht mehr notwendig und es wird keine separate Ergebnisseite benötigt. Wie eine solche Suchfunktion unter Verwendung von MVVM-Prinzipien entwickelt werden kann, wird in diesem Artikel vorgestellt.

Als Ausgangsszenario dient uns eine einfache App mit einer einzelnen Seite, die eine Liste von Filmen enthält, für die nun eine Suchfunktion hinzugefügt werden soll. Zur Anzeige der Filmliste wird ein GridView-Element verwendet, das die Daten über eine Datenbindung aus einem ViewModel bezieht, wie es bei MVVM üblich ist.

Beispielanwendung für die Suchfunktion Beispielanwendung für die Suchfunktion

Für das Beispiel wird eine Liste von Filmobjekten verwendet. Die Filme haben das folgende Format:

public class Movie
{
    public string Title { get; set; }
    public string Subtitle { get; set; }
    public Uri ImagePath { get; set; }
}

1. SearchBox-Steuerelement hinzufügen

Als erstes fügen wir der aktuellen Seite ein SearchBox-Element hinzu. Die Eigenschaft „FocusOnKeyboardInput“ sollte direkt auf „True“ festgelegt werden. Das ermöglicht dem Nutzer, die Suchfunktion direkt beim Tippen auf der Tastatur verwenden zu können, auch wenn der Fokus zuvor nicht auf das Suchfeld gesetzt worden ist.

1
<SearchBox x:Name="SearchBox" FocusOnKeyboardInput="True"></SearchBox>

Für den eigentlichen Suchvorgang sind die folgenden zwei Ereignisse interessant: QueryChanged und QuerySubmitted.

  • QueryChanged wird ausgelöst, wenn sich der eingegebene Suchbegriff ändert
  • QuerySubmitted wird ausgelöst, wenn die Eingabetaste im Suchfeld gedrückt oder das Suchen-Symbol betätigt wird

Da wir eine Sofortsuche implementieren wollen, werden wir das QueryChanged-Ereignis verwenden, um direkt bei Änderung des Suchbegriffes die aktuellen angezeigten Elemente filtern zu können.

2. Behandeln des QueryChanged-Ereignisses im ViewModel

Da wir uns in der MVVM-Welt bewegen wollen, sollte möglichst keine Logik in der Code-Behind-Datei der Seite enthalten sein. Die Suchlogik findet einen besseren Platz im ViewModel, das auch bereits Zugriff auf die Daten hat und diese direkt filtern kann. Damit das QueryChanged-Ereignis im ViewModel behandelt werden kann, wird es mithilfe des Behaviors SDKs in ein ICommand umgeleitet.

2.1 Behaviors SDK hinzufügen

Das Behavior SDK ist seit Visual Studio 2013 bereits vorinstalliert und muss dem Projekt lediglich als Verweis hinzugefügt werden. Im Verweis-Manager ist es unter „Windows 8.1 / Erweiterungen / Behaviors SDK (XAML)“ zu finden.

Es wird eine Referenz zu dem Behaviors SDK hinzugefügt Es wird eine Referenz zu dem Behaviors SDK hinzugefügt

Um das SDK innerhalb der XAML-Datei verwenden zu können, werden zwei Namespaces benötigt: Microsoft.Xaml.Interactivity und Microsoft.Xaml.Interactions.Core. Diese werden dem Kopf der XAML-Seite hinzugefügt:

1
2
3
4
5
6
<Page
    x:Class="MovieSearch.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    xmlns:ic="using:Microsoft.Xaml.Interactions.Core">

Um nun die Verbindung zwischen dem Ereignis der SearchBox und einem Command im ViewModel herzustellen, wird das QueryChanged-Ereignis über ein EventTriggerBehavior aus dem Behaviors SDK behandelt. Über ein InvokeCommandAction kann daraufhin ein Command (hier SearchOnTypingCommand) aus dem ViewModel an das Ereignis gebunden werden. Als Parameter für den Command wird der aktuelle Suchbegriff der SearchBox über eine Datenbindung mitgegeben (QueryText-Parameter), damit das ViewModel auch Zugriff auf den Suchbegriff erhält. Durch diesen deklarativen Ansatz wird nun automatisch bei Änderung des Suchbegriffes der Command im ViewModel aufgerufen. Dazu wird das SearchBox-Element wie folgt angepasst:

1
2
3
4
5
6
7
<SearchBox x:Name="SearchBox" FocusOnKeyboardInput="True">
    <i:Interaction.Behaviors>
        <ic:EventTriggerBehavior EventName="QueryChanged">
            <ic:InvokeCommandAction Command="{Binding SearchOnTypingCommand}" CommandParameter="{Binding QueryText, ElementName=SearchBox}" />
        </ic:EventTriggerBehavior>
    </i:Interaction.Behaviors>
</SearchBox>

2.2 Implementieren der Suchlogik

Im nächsten Schritt muss der Command mit der gewünschten Suchlogik implementiert werden. Für unsere Filmobjekte bauen wir uns eine einfache Suche nach den Filmtitel.

Als erstes erzeugen wir den oben genannten SearchOnTypingCommand im ViewModel. Als Basis für den Command dient die Klasse DelegateCommand (alternativ RelayCommand), die bei den meisten Projektvorlagen für Windows Apps in Visual Studio bereits mitgeliefert wird. Als Parameter wird die Funktion übergeben, die aufgerufen werden soll, wenn der Command ausgeführt wird.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public MainPageViewModel()
{
    this.allMovies = new List<Movie>
    {
        new Movie { Title = "Matrix", Subtitle = "Lorem ipsum...", ImagePath = new Uri("http://lorempixel.com/250/250/nightlife/1") },
        new Movie { Title = "Der Pate", Subtitle = "Lorem ipsum...", ImagePath = new Uri("http://lorempixel.com/250/250/nightlife/2") },
        new Movie { Title = "The Dark Knight", Subtitle = "Lorem ipsum...", ImagePath = new Uri("http://lorempixel.com/250/250/nightlife/3") },
        new Movie { Title = "Pulp Fiction", Subtitle = "Lorem ipsum...", ImagePath = new Uri("http://lorempixel.com/250/250/nightlife/4") },
        new Movie { Title = "Fight Club", Subtitle = "Lorem ipsum...", ImagePath = new Uri("http://lorempixel.com/250/250/nightlife/5") },
    };

    this.Movies = this.allMovies;
    this.SearchOnTypingCommand = new DelegateCommand(this.FilterMoviesByTitle);
}

/// <summary>
/// Ruft den Befehl zum Suchen ab oder legt diesen fest.
/// </summary>
public DelegateCommand SearchOnTypingCommand { get; set; }

In diesem Beispiel verwenden wir die IndexOf(String, StringComparison)-Methode zum Durchführen der Suche nach dem Filmtitel. Dadurch wird eine Suche ohne Berücksichtigung der Groß-/ und Kleinschreibung bereitgestellt. Die zurückgegebene Auflistung mit den übereinstimmenden Elementen wird in der Eigenschaft Movies gespeichert, die über eine Datenbindung an das GridView der Seite gebunden ist. Ist der Suchbegriff leer, wird die ursprüngliche Auflistung aller Elemente angezeigt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <summary>
/// Filtert die Filme nach ihrem Namen.
/// </summary>
/// <param name="parameter">Der Suchbegriff</param>
private async void FilterMoviesByTitle(object parameter)
{
    string searchExpression = parameter.ToString();
    if (string.IsNullOrWhiteSpace(searchExpression))
    {
        this.Movies = this.allMovies;
    }
    else
    {
        this.Movies = this.allMovies.Where(x => x.Title.IndexOf(searchExpression, System.StringComparison.CurrentCultureIgnoreCase) > -1).ToList();
    }
}

2.3. Suchverzögerung hinzufügen

Aktuell wird die Suche bei jedem Tastenanschlag auf der Tastatur erneut durchgeführt. Das ist jedoch nicht notwendig, da in der Praxis mehrere Zeichen schnell hintereinander eingetippt werden. Das Suchergebnis wird für den Benutzer erst interessant, wenn er mit der Eingabe fertig ist, beziehungsweise eine gewisse Zeitspanne mit dem Tippen verharrt, um sich die Ergebnisse anzuschauen. Es macht für eine Sofortsuche also durchaus Sinn, eine Verzögerung einzubauen, die eine gewisse Zeit mit dem Suchvorgang wartet, bis der Benutzer beim Tippen eine kurze Pause macht.

Für die Verzögerung verwenden wir die Task.Delay-Methode aus dem System.Threading-Namespace.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/// <summary>
/// Speichert den letzten Suchbegriff.
/// </summary>
private string lastSearch;

/// <summary>
/// Filtert die Filme nach ihrem Namen. Es wird eine kurze Verzögerung hinzugefügt, um den Suchvorgang nicht öfter als notwendig aufzurufen.
/// </summary>
/// <param name="parameter">Der Suchbegriff</param>
private async void FilterMoviesByTitle(object parameter)
{
    string searchExpression = parameter.ToString();
    this.lastSearch = searchExpression;

    // Eine kurze Verzögerung hinzufügen
    await Task.Delay(500);

    // Führt Suche durch, wenn sich der Suchbegriff seit den letzten 500 ms nicht geändert hat
    if (searchExpression == this.lastSearch)
    {
        if (string.IsNullOrWhiteSpace(searchExpression))
        {
            this.Movies = this.allMovies;
        }
        else
        {
            this.Movies = this.allMovies.Where(x => x.Title.IndexOf(searchExpression, System.StringComparison.CurrentCultureIgnoreCase) > -1).ToList();
        }
    }
}

Der eigentliche Suchvorgang in obigen Codeausschnitt wird erst durchgeführt, wenn eine Verzögerung von einer halben Sekunde verstrichen ist und sich der Suchbegriff in dieser Zeit nicht mehr geändert hat. Vergleicht man die Suchfunktion mit und ohne Verzögerung, so stellt man fest, dass die Variante mit Verzögerung die Suche für den Benutzer deutlich angenehmer und natürlicher macht.

3. Zusammenfassung

In diesem Artikel wurde gezeigt, wie sich eine Sofortsuche in einer Windows App umsetzen lässt. Solch eine Variante der Suche macht dann Sinn, wenn die aktuelle Seite eine Liste von Elementen enthält, auf deren Basis die Suche durchgeführt werden soll. Aber auch die Art der Datenquelle und Komplexität der Suche sind Faktoren, die entweder für oder gegen einen solchen Ansatz sprechen. Kommen die Daten aus einem unfangreichen Bestand von einem externen Service und sind gegebenenfalls noch Gruppierung und Sortierung nötig, kann der Ansatz von Microsoft über eine separate Suchseite sinnvoller sein.

Beispielprojekt: Movie Search

Das Beispielprojekt ist hier auf GitHub zu finden.