Archiv für Desktop Development

.Net RowFilter Multi-Wildcard Filter Expressions

In .Net existiert an der Klasse System.Data.DataView ein Property namens RowFilter, das einen Ausdruck in SQL-Syntax erwartet, was wiederum ein Filtern der zugrunde liegenden Daten nach sich zieht. Der Aufbau und die Möglichkeiten des Filterausdrucks werden in in dem Property Expression der Klasse DataColumn beschrieben. Als Wildcards wird der „*“ akzeptiert und ist zugelassen.

Man hat ja von einer freien Filtereingabe die Vorstellung, dass man beliebig Wildcards platzieren kann und der Ausdruck nahezu beliebig komplex werden darf. Doch weit gefehlt: „[…] Wildcards are not allowed in the middle of a string. For example, 'te*xt' is not allowed. […]“ (Quelle: MSDN, ZEICHENFOLGENOPERATOREN).

Was tut man also in einem solchen Fall ? Nun, wenn man sich den Ausdruck 'te*xt' ansieht, so kann man diesen zerlegen in 'te* AND *xt'. Daraus ergibt sich eine Ergebnismenge, die alle Daten beinhaltet, die mit „te“ beginnen und mit „xt“ enden.

Das bedeutet natürlich im Gegenzug, dass man die Erzeugung des Filterausdrucks komplett selbst machen muss. Dafür habe ich mir nun folgendes (und erprobtes) Klassengerüst ausgedacht. Es wird hierbei auf das Factory-Pattern zurückgegriffen (klicken zum Vergrößern):

Filter Klassendiagramm

Hierbei wird auch auf die verschiedenen Editoren und Datentypen Rücksicht genommen. Die Entscheidung, welche Klasse für die Erzeugung des Teilfilterausdrucks (der Ausdruck wird partiell pro Datenspalte erzeugt) genommen wird, erfolgt so:

?View Code CSHARP
this.typesHashtable = new Hashtable();
 
this.typesHashtable.Add(typeof(bool), typeof(BoolGridFilter));
 
this.typesHashtable.Add(typeof(byte), typeof(NumericGridFilter));
this.typesHashtable.Add(typeof(short), typeof(NumericGridFilter));
this.typesHashtable.Add(typeof(int), typeof(NumericGridFilter));
this.typesHashtable.Add(typeof(long), typeof(NumericGridFilter));
this.typesHashtable.Add(typeof(float), typeof(NumericGridFilter));
this.typesHashtable.Add(typeof(double), typeof(NumericGridFilter));
this.typesHashtable.Add(typeof(decimal), typeof(NumericGridFilter));
this.typesHashtable.Add(typeof(UInt16), typeof(NumericGridFilter));
this.typesHashtable.Add(typeof(UInt64), typeof(NumericGridFilter));
 
this.typesHashtable.Add(typeof(DateTime), typeof(DateTimeGridFilter));
 
this.defaultGridFilterType = typeof(TextGridFilter);

Später wird dann mittels Reflection entschieden, welche Klasse genommen werden soll. Dies geschieht über den der DataColumn zugrunde liegenden DataType:

?View Code CSHARP
Type dataType = dc.DataType;
if (dataType != null)
{
  if (this.typesHashtable.ContainsKey(dataType))
  {
    Type gridFilterType = this.typesHashtable[dataType] as Type;
    dcFilter = gridFilterType.Assembly.CreateInstance(gridFilterType.FullName, false,
                       BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance,
                       null, new object[] { dc.DataField, PrepareFilterExpression(dc.FilterText) }, null, new object[] { }) as IDataColumnFilter;
  }
  else
  {
     dcFilter = this.defaultGridFilterType.Assembly.CreateInstance(this.defaultGridFilterType.FullName, false,
                       BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance,
                       null, new object[] { dc.DataField, PrepareFilterExpression(dc.FilterText) }, null, new object[] { }) as IDataColumnFilter;
  }
}

Die hier beschriebenen Code-Fragmente wurden in Verbindung mit dem TrueDBGrid von ComponentOne implementiert, die Architektur ist aber recht lose über Interfaces handhabbar, so dass man die Technik ganz leicht zur Zusammenarbeit mit anderen DataGrids überreden können sollte. Den kompletten Quelltext werde ich hier nicht veröffentlichen, ich hoffe aber, dass dieser Beitrag als Ideengeber dienen kann 😉