Biggle's Blog

Web- und Software Development

by Mario Priebe

OR verknüpftes Filtering in einem DataGrid

Im folgenden Artikel beschreibe ich, wie man in einem DataGrid OR verknüpftes Filtering mit Daten aus einem RIA Sevice nach MVVM implementiert. Da sich keine OR Verknüpfung an einem “normalen” WPF DataGrid “so auf die Schnelle” umsetzen lässt, setze ich ein Telerik DatatGrid ein.

Um in einem DataGrid die Daten aus einem RIA Service-Kontext über der Eingabe in einer TextBox zu filtern, kann man normalerweise den FilterDescriptor einsetzen:

<riaControls:DomainDataSource.FilterDescriptors>     <riaControls:FilterDescriptor PropertyPath="Ort" Operator="Contains" Value="{Binding ElementName=txtOrt, Path=Text}" /> </riaControls:DomainDataSource.FilterDescriptors>

Das funktioniert soweit auch ganz gut, jedoch kann es dazu kommen, dass der Kunde über diese TextBox in mehr als einer Spalte im DataGrid die Daten filtern möchte.

Beispiel: Ich habe eine Spalte "Ort" und eine Spalte "Halle". Nun möchte ich über meiner TextBox entweder in Ort oder Halle filtern und das Ergebnis im Grid anzeigen.

Der o.g. Code zeigt, wie man einen Filter der Liste von FilterDescriptors hinzufügt. Natürlich heißt das auch, dass ich hier mehr als diesen verwenden kann. Nur das Problem ist, das alle Filter innerhalb dieser Liste, lediglich AND verknüpft werden. Das Verwenden eines OR Operators scheint leider (“auf die Schnelle”) nicht möglich.

Wir haben das Glück Komponenten von Telerik einzusetzen. Auch hier ist es möglich am DataGrid (RadGridView) Filter zu verwenden. Hier hat man die Collection "FilterDescriptors" zur Verfügung, in der ich mehrere "CompositeFilterDescriptor" definieren kann. Das Gute daran, hier kann ich über die Property LogicalOperator bestimmen, wie die Filter zueinander stehen.

Das Ganze sieht dann beispielsweise wie folgt aus:

<telerik:RadGridView.FilterDescriptors>     <telerik:CompositeFilterDescriptor LogicalOperator="Or">         <telerik:CompositeFilterDescriptor.FilterDescriptors>             <telerik:FilterDescriptor Member="Ort"                     Operator="Contains"                     Value="hier der suchstring"                     IsCaseSensitive="False" />             <telerik:FilterDescriptor Member="Halle"                     Operator="Contains"                     Value="hier ein weiterer suchstring"                     IsCaseSensitive="False" />         </telerik:CompositeFilterDescriptor.FilterDescriptors>     </telerik:CompositeFilterDescriptor> </telerik:RadGridView.FilterDescriptors>

Nun möchte ich gerne den Wert aus der "Filter"-TextBox in den Filtern verwenden. In etwa so:

<telerik:FilterDescriptor Member="Ort"         Operator="Contains"         Value="{Binding ElementName=txtOrt, Path=Text}"         IsCaseSensitive="False" />

 

Leider funktioniert das so nicht. Telerik sagt hier :

"Binding with the ElementName in the Silverlight GridView is not the best way to go. ElementName uses the logical tree to find the element in the element name binding. Parts of the telerik grid (and the sl data grid), do not participate in this part of the tree, or they have not been created yet at the time of binding (since most of the grid is dynamically generated)"

Jedoch ist es mir möglich über ein ViewModel-Property dieses Problem zu umgehen.

Ich erstelle mir dazu eine ViewModel-Klasse (wenn noch nicht vorhanden) und definiere in dieser die Property "SearchString". (Das ViewModel muss das Interface "INotifyPropertyChanged" implementieren)

private string searchString; public string SearchString {     get     {         return searchString;     }     set     {         searchString = value;         this.OnPropertyChanged("SearchString");     } }

Anschließend referenzieren wir das ViewModel in unserer View:

xmlns:vm="clr-namespace:Namespace.zum.ViewModel"

und in den Page oder Grid Resources instanziieren wir dieses:

<vm:MyViewModel x:Key="MyViewModel" />

Jetzt binden wir TextBox-Text an die Property "SearchString"

<TextBox Text="{Binding Source={StaticResource MyViewModel}, Path=SearchString, Mode=TwoWay}" Name="txtOrt" />

Und dasselbe machen wir in den FilterDescriptoren:

<telerik:FilterDescriptor Member="Ort"     Operator="Contains"     Value="{Binding Source={StaticResource MyViewModel}, Path=SearchString}"     IsCaseSensitive="False" /> <telerik:FilterDescriptor Member="Halle"     Operator="Contains"     Value="{Binding Source={StaticResource MyViewModel}, Path=SearchString}"     IsCaseSensitive="False" />

Funktioniert :-)

Aber Achtung: Wenn man nun in der TextBox Werte die in der Spalte "Ort" und "Halle" vorkommen einträgt, muss man zunächst den Focus der TextBox verlassen, um das Ergebnis zu sehen. Ich möchte aber gerne, das ich das Ergebnis direkt bei der Eingabe bekomme.

In WPF ist es möglich den UpdatSourceTrigger auf PropertyChanged zu setzen. In Silverlight finde ich lediglich default und explizit. Hmmm, was nun. Google angeschmissen und erfahren, das gibt es so nicht.

Auf der Suche nach einer Lösung sogleich beim .NET Rocker gelandet. Thomas stellt eine Klasse bereit, welche das PropertyChanged in Silverlight bereit stellt.

/// <summary> /// Supports a PropertyChanged-Trigger for DataBindings /// in Silverlight. Works just for TextBoxes /// (C) Thomas Claudius Huber 2009 /// http://www.thomasclaudiushuber.com /// </summary> public class BindingHelper {     public static bool GetUpdateSourceOnChange(DependencyObject obj)     {         return (bool)obj.GetValue(UpdateSourceOnChangeProperty);     }     public static void SetUpdateSourceOnChange         (DependencyObject obj, bool value)     {         obj.SetValue(UpdateSourceOnChangeProperty, value);     }     // Using a DependencyProperty as the backing store for …     public static readonly DependencyProperty         UpdateSourceOnChangeProperty = DependencyProperty.RegisterAttached("UpdateSourceOnChange",         typeof(bool), typeof(BindingHelper), new PropertyMetadata(false, OnPropertyChanged));     private static void OnPropertyChanged         (DependencyObject obj,         DependencyPropertyChangedEventArgs e)     {         var txt = obj as TextBox;         if (txt == null)             return;         if ((bool)e.NewValue)         {             txt.TextChanged += OnTextChanged;         }         else         {             txt.TextChanged -= OnTextChanged;         }     }     static void OnTextChanged(object sender,         TextChangedEventArgs e)     {         var txt = sender as TextBox;         if (txt == null)             return;         var be = txt.GetBindingExpression(TextBox.TextProperty);         if (be != null)         {             be.UpdateSource();         }     } }

Wenn wir diese verwenden, so wird nun beim TextChanged-Event wie erwartet das Grid aktualisiert.

<TextBox Text="{Binding Source={StaticResource MyViewModel}, Path=SearchString, Mode=TwoWay}" Name="txtOrt" />

Jetzt passt das.

Viel Spaß beim entwickeln : )