.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 😉

Compact Framework Design Time Attribute

Wenn man unter dem .Net Framework entwickelt, kann man Properties in visuellen Klassen (UserControls, Forms, Controls, …) mit Attributen versehen, so dass diese gewisse Eigenschaften haben. Zum Beispiel ist es so möglich ein Property „AlternateText“ in ein eigenes Control einzufügen. Dieses Property ist im Designer auch sichtbar, nachdem man es auf die gerade zu designende Oberfläche per Drag & Drop gezogen hat. Standardmäßig taucht dieses Property in der Kategorie „Misc“ auf. Man kann nun beeinflussen, dass das Property im Properties-Browser im Designer nicht sichtbar ist ([Browsable(false)]), man kann einen Default-Wert festlegen ([DefaulValue(„Not set“)]) oder auch eine eigene Kategorie definieren ([Category(„Advins – Control Visual Properties“)]).

Leider hat man genau diese Annehmlichkeiten nicht im Compact Framework zur Verfügung. Man kann sich aber behelfen, indem man eine XMTA Datei dem Projekt hinzufügt und darin diese Eigenschaften in XML-Form festlegt. Eine solche XML-Datei (DesignTimeAttributes.xmta) kann wie folgt aussehen:

<?xml version="1.0" encoding="utf-16"?>
<Classes xmlns="http://schemas.microsoft.com/VisualStudio/2004/03/SmartDevices/XMTA.xsd">
 <Class Name="AdvControls.ImageButton">
  <Property Name="ButtonText">
   <Browsable>true</Browsable>
   <Category>AdvControls - Properties</Category>
   <Description>Set the text to be displayed in the button here.</Description>
  </Property>
  <Property Name="Image">
   <Browsable>true</Browsable>
   <Category>AdvControls - Properties</Category>
   <Description>Set the image to be displayed in the button here.</Description>
  </Property>
 </Class>
</Classes>

Der Compiler erkennt die Datei und erzeugt daraus eine AdvControls.WindowsCE.asmmeta.dll. So kann man seine Control-Properties doch wie gewohnt (leider nur etwas umständlicher) mit Attributen versehen.

Entwickelt man Code sowohl für den Desktop als auch ein Mobile Device kann man seine Attribute mittels

#if Desktop

#endif

über das Property schreiben ohne Compile-Fehler zu bekommen.

Wiedergabe von Sounds mit dem Compact Framework

Vor einiger Zeit schon stieß ich auf das Problem unter Windows Mobile Sounds wiedergeben zu wollen. Nahezu ohne Weiteres unterstützt wird das Abspielen von WAV-Sounds, aber wie sieht es mit MP3 und/oder WMA aus ? Nun dazu später mehr, hier erst einmal die Lösung von Microsoft, wie man WAV-Sounds unter Windows Mobile abspielen kann:

public class Sound {
private byte[] m_soundBytes;
private string m_fileName;
private enum Flags
{
SND_SYNC = 0x0000, /* play synchronously (default) */
SND_ASYNC = 0x0001, /* play asynchronously */
SND_NODEFAULT = 0x0002, /* silence (!default) if sound not found */
SND_MEMORY = 0x0004, /* pszSound points to a memory file */
SND_LOOP = 0x0008, /* loop the sound until next sndPlaySound */
SND_NOSTOP = 0x0010, /* don’t stop any currently playing sound */
SND_NOWAIT = 0x00002000, /* don’t wait if the driver is busy */SND_ALIAS = 0x00010000, /* name is a registry alias */
SND_ALIAS_ID = 0x00110000, /* alias is a predefined ID */
SND_FILENAME = 0x00020000, /* name is file name */
SND_RESOURCE = 0x00040004 /* name is resource name or atom */
}

[DllImport(„CoreDll.DLL“, EntryPoint=“PlaySound“, SetLastError=true)]
private extern static int WCE_PlaySound(string szSound, IntPtr hMod, int flags);
[DllImport(„CoreDll.DLL“, EntryPoint=“PlaySound“, SetLastError=true)]
private extern static int WCE_PlaySoundBytes (byte[] szSound, IntPtr hMod, int flags);

/// <summary>
/// Construct the Sound object to play sound data from the specified file.
/// </summary>
public Sound (string fileName)
{
m_fileName = fileName;
}

/// <summary>
/// Construct the Sound object to play sound data from the specified stream.
/// </summary>
public Sound(Stream stream)
{
// read the data from the stream
m_soundBytes = new byte [stream.Length];
stream.Read(m_soundBytes, 0,(int)stream.Length);
}

/// <summary>
/// Play the sound
/// </summary>
public void Play ()
{
// if a file name has been registered, call WCE_PlaySound,
// otherwise call WCE_PlaySoundBytes
if (m_fileName != null)
WCE_PlaySound(m_fileName, IntPtr.Zero, (int) (Flags.SND_ASYNC | Flags.SND_FILENAME));
else
WCE_PlaySoundBytes (m_soundBytes, IntPtr.Zero, (int) (Flags.SND_ASYNC | Flags.SND_MEMORY));
}
}

Wie bewerkstelligt man es aber nun, MP3 und WMA-Sounds abzuspielen ? Hierzu fand ich folgende Lösung im Netz:

  • Hinzufügen einer Referenz auf C:\Windows\System32\wmp.dll (man benutzt also den Windows Media Player)
  • Folgenden Code zum Abspielen verwenden:

    WMPLib.WindowsMediaPlayer player = new WMPLib.WindowsMediaPlayer();
    player.URL = „\\Amazing.mp3“;
    player.settings.volume = 100;
    player.controls.next();
    player.controls.play();

Ich greife dieses Thema hier auf, weil mich jüngst Martin per Mail nach einer Lösung fragte und dies vielleicht auch noch für andere interessant sein könnte.

OpenFileDialog im .Net Compact Framework

Am Wochenende bin ich über eine Sache gestolpert, wo ich nicht so recht glauben wollte, dass das wahr sein könnte. Ich habe ja mehrfach berichtet, dass ich mich derzeit daran versuche eine Windows Mobile Applikation zu entwerfen. Mit genau diesem Thema habe ich mich auch am Wochenende befasst, als ich aber an die Stelle kam, an der es darum ging Sounds abzuspielen, bin ich über eine – meines Erachtens nach – Basisfunktionalität gestolpert: System.Windows.Forms.OpenFileDialog.

Angefangen hat die Geschichte mit der Implementierung der Sound-Funktionalität, wobei ich mich hierbei des Windows Media Players (wmp.dll) bediente und diese in den Code einband. Die Klasse für den Sound war an sich schnell umgesetzt – nur wie testen ? Also habe ich auf dem Einstellungen-Tabreiter einen „Sound auswählen“ Button hinzugefügt und wollte dann einen einfachen Dateiauswahl-Dialog öffnen. .Net bietet wie gesagt hier die Klasse OpenFileDialog an. Diese habe ich dann auch genutzt und die Anwendung im Debug gestartet. Im Emulator kam dann die Anwendung nach vorne und ich drückte auf den Button und was erschien ? Dieses „Ding“:

.Net CompactFramework OpenFileDialog

In der Combobox „Folder“ tauchen bspw. völlig unnütze Einträge auf, kurz gesagt ich war nicht in der Lage intuitiv und wie gewohnt durch die Ordnerstruktur zu navigieren und eine einfache Datei auszuwählen. Zuerst dachte ich, ich würde etwas falsch machen und habe mich dann im Internet auf die Suche nach einer Lösung begeben. Das erste worauf ich stieß waren Drittanbieter-Komponenten, die sie sich äußerst fürstlich entlohnen ließen. Langsam stieg in mir ein leiser Verdacht auf: sollte ich hier etwa auf die erste der von einem Kollegen viel beschworenen Lücken im Compact Framework gestoßen sein ??? In der Tat sah es nach einer weiteren viertel Stunde Suche äußerst düster aus. Entweder fand ich nur besagte Dritthersteller-Controls oder Dialoge mit Quelltext, die aber nicht sonderlich ansprechend oder funktional waren. Jeder kocht hier sein eigenes Süppchen und programmiert diese Funktionalität selbst aus. Das ist das Fazit meiner Recherche 🙁

Daher muss ich nun meinem Projekt „PhoneEventNotifier“ noch eine anderes Projekt vorschalten: „SimpleFileRequesterDialogs“, d.h. ich werde mich nun zuerst hinsetzen und folgende Dinge nachbauen:

  • Verzeichnisauswahldialog
  • Dateiauswahldialog (Öffnen, Speichern, Mehrfach-Selektion, …)

Dies werde ich als DLL erstellen und separat bereitstellen. Im Prinzip sind alle Funktionalitäten im Compact Framework vorhanden, es fehlt nur die Vereinigung dieser in Form eines Dialogs, der sich „wie gewohnt“ bedienen lässt. Danach geht dann die Arbeit am „PhoneEventNotifier“ weiter.

Sachdienliche Hinweise können in Form von Kommentaren hinterlassen werden 😉

Idee zur Windows Mobile Applikation

Es gibt Neuigkeiten an der Entwicklungsfront mit dem Compact Framework 2.0 SP 1 und SharpDevelop. Anfangs sträubte sich SharpDevelop noch etwas das Compact Framework zu erkennen, aus irgendeinem Grund hat es sich dann aber nach diversen Versuchen und Forenbesuchen anders entschieden. Genau sagen, woran es lag, kann ich nicht. Ich denke, dass es an der Installationsreihenfolge liegt.

Man sollte zuerst die CompactFramework Runtimes installieren, danach das Windows Mobile 6 SDK und im Anschluß die gewünschten SDKs für das jeweilige Framework.

Achtung: Wenn man sich auf die Suche nach dem CompactFramework SDK begibt, wird man viele Treffer finden. Es sei hier aber gesagt, dass man das CompactFramework SDK mit den „normalen“ SDKs bekommt. So wird bei der Installation des .Net 2.0 SDK auch das zugehörige CompactFramework SDK auf die Platte kopiert.

Die erste kleine Demo-Applikation (klassisch >Hello World< im Fenster) war in 2 Minuten mit .Net 2.0 geschrieben. Ältere Versionen von Windows Mobile müssen zuerst mit dem .Net Framework in der richtigen Version versorgt werden (Deployment über ActiveSync), Windows Mobile 6 bringt aber bereits alles mit.

Was ist nun die Idee für meine erste Anwendung unter Windows Mobile ? Sie nennt sich „PhoneEventNotifier“ und soll die akustische Signalisierung eines verpassten Ereignisses übernehmen. Der XDA comet signalisiert ein solches Ereignis über eine Status-LED oben am Gerät, diese blinkt dann rot. Dies soll nun noch durch eine Software unterstützt werden, die einen einstellbaren Ton in einem einstellbaren Intervall wiedergibt. So machte das nämlich mein Motorola V3i (habe ich übrigens verkauft mittlerweile) – auch wenn ansonsten recht wenig innovativ war – meiner Meinung nach. An diese Art der Benachrichtigung hatte ich mich im Laufe der zwei Jahre, in denen ich das V3i hatte, gewöhnt und vermisse diese Funktionalität nun etwas bei meinem comet.

Wie soll die Software nun aussehen ? Nun, sie soll im natürlich Hintergrund laufen und mittels eines Timers gesteuert werden. Es soll natürlich immer nur auf ein einziges verpasstes Ereignis reagiert werden, nicht dass sich verpasste Ereignisse kumulieren und das Gerät zig-Mal in verschiedenen Intervallen akustische Signale ausgibt. Ebenso soll eine Zeitspanne einstellbar sein, in der das Gerät keine Signale ausgeben soll (quiet period), bspw. nachts. Die Einstellungen sollen ganz unspektakulär in einem kleinen Fenster (Tab) im Programm vorgenommen werden können.

Ansonsten reift die Idee und ich hoffe, dass ich bald erste Ergebnisse respektive Screenshots liefern kann (Hauptproblem ist wie immer die Zeit für so etwas zu erübrigen 🙁 ). Ich halte die geneigten Leser natürlich auf dem Laufenden 😉