Posts mit dem Label HowTo werden angezeigt. Alle Posts anzeigen
Posts mit dem Label HowTo werden angezeigt. Alle Posts anzeigen

Freitag, 31. Juli 2009

Anwendung nicht im Alt + Tab - Fenster anzeigen

Manches mal hat man die Anforderung, dass eine Applikation nicht in dem Fenster angezeigt werden soll, das aktiv wird, wenn man die Tastenkombination Alt + Tab verwendet, um zwischen den laufenden Applikationen umzuschalten.

Eine kurze, prägnante und gute Anleitung dazu ist mir unter http://www.csharp411.com/hide-form-from-alttab/ über den Weg gelaufen. Das soll hier einfach mal für den Fall vermerkt werden, dass ich mal in die Verlegenheit kommen sollte. Zudem hilft es ja vielleicht auch dem ein oder anderen weiter.

Montag, 19. Januar 2009

Versionskontrolle mit VisualSVN & AnkhSVN

Versionskontrolle ist sicherlich für jeden Entwickler ein Thema, vor allem natürlich im beruflichen Umfeld. Doch auch für die private Entwicklung ist sie ein Thema, denn auch hier möchte der Entwickler nicht auf die Sicherheit verzichten, die eine Versionskontrolle bietet. Nicht umsonst ist der Einsatz eines Versionskontrollsystems bereits für den ersten, den Roten Grad im Wertesystem der Clean Code Developer vorgesehen.

Aus genannten Gründen habe ich mich mit eben diesen Systemen auseinandergesetzt und mich letztlich für eine Kombination aus VisualSVN als Server und AnkhSVN als Visual Studio-AddIn entschieden. Dass beide kostenlos verwendet werden können, hat dabei natürlich auch eine Rolle gespielt.

Obwohl sich Installation, Einrichtung und Verwendung als denkbar einfach erweisen, möchte ich hier eine kleine Hilfe für den Einstieg bieten, die eventuell auch für Unentschlossene als kleine Entscheidungshilfe dienen kann.

Installation

Nachdem die beiden Installationsdateien für VisualSVN und AnkhSVN, die jeweils nur etwa 3 MB klein sind, heruntergeladen wurden, kann mit der Installation des Subversion-Servers begonnen werden. Man startet also zunächst die Installation von VisualSVN.

Die Installation gestaltet sich dabei als einfach. Lediglich in einem Fenster müssen Entscheidungen getroffen werden, die der Konfiguration des Servers dienen.

Konfiguration VisualSVN

Neben dem Installationsverzeichnis kann hier das Verzeichnis gewählt werden, in dem später die Repositories des Servers abgelegt werden.
Auch kann man den zu verwendenden Port einstellen und wählen, ob eine sichere Verbindung via https:// verwendet werden soll, um eine Verbindung mit dem Server herzustellen.
Schlussendlich bleibt noch die Wahl, ob man für den Zugriff separate Subversion-Benutzer verwenden möchte oder lieber die bereits existierenden Windows-Benutzer.

Der nebenstehende Screenshot zeigt die Standardeinstellungen, die ich für meine Installation auch so belassen werde.

Damit ist die Installation des Servers auch bereits abgeschlossen und er ist damit funktionstüchtig. Bevor wir uns jedoch genauer damit auseinandersetzen, installieren wir zunächst noch das Visual Studio-AddIn AnkhSVN, das uns als Client dienen wird. Diese beschränkt sich auf das Starten der Installation plus mehrmaliges “Weiter”-Klicken, daher gehe ich darauf nicht näher ein.

 

Konfiguration & Einrichtung

Nach erfolgreicher Installation starten wir zunächst den VisualSVN Server Explorer, den im Startmenü gefunden werden kann. Dieser dient der Verwaltung des Subversion-Servers. Hier werden beispielsweise Benutzer und Repositories angelegt, mit denen Subversion arbeitet. Auch kann hier der Server gestoppt und gestartet werden.SVN_002

Diese grundlegenden Einstellungen kann man direkt vom Startbildschirm aus treffen.
Wurde als Authentifizierungsmethode die Subversion-Authentifizierung gewählt, sollte zunächst ein Benutzer erstellt werden.
Anschließend wird noch ein Repository angelegt und schon ist die grundlegende Einrichtung erledigt und der Server einsatzbereit.

Bevor wir jetzt allerdings unser erstes Projekt unter Versionskontrolle stellen, möchte ich noch zeigen, wie man die Verwendung von Keywords bzw. Properties aktivieren kann.
Diese werden zur sogenannten Keyword Substitution benötigt. Dabei handelt es sich um die dynamische Ersetzung bestimmter Schlüsselwörter durch deren Werte innerhalb der Quelldatei. So kann man beispielsweise im Header der Quelldatei eine Übersicht realisieren, welcher Benutzer eine Datei zuletzt eingecheckt hat und wann dies geschehen ist.

Um Keywords zu aktivieren, navigieren wir zunächst zum Konfigurations-Verzeichnis des Subversion-Servers, der unter Windows XP unter “C:\Dokumente und Einstellungen\<Benutzername>\Anwendungsdaten\Subversion” zu finden ist.  Dort öffnen wir die Datei “config” mit einem beliebigen Editor.
In der Sektion [miscellany] muss zunächst die Zeile enable-auto-props = yes einkommentiert werden. Hierzu wird einfach die vorangestellte Raute entfernt.
Anschließend muss in der Sektion [auto-props] noch ein Eintrag für jede Datei-Art eingefügt werden, für die Keywords aktiviert werden sollen. Für C#-Quelldateien sieht der Eintrag folgendermaßen aus:

*.cs = svn:keywords=HeadURL LastChangedBy LastChangedRevision LastChangedDate Id

Wie hier zu sehen gibt es fünf Keywords, die aktiviert werden können. Diese haben teilweise noch Alias-Namen, die stattdessen verwendet werden können.
Um mehr über die Keywords und die Keyword Substitution an sich zu erfahren, kann unter http://svnbook.red-bean.com/en/1.4/svn.advanced.props.special.keywords.html nachgelesen werden.

Wichtig:
Soll Keyword Substitution verwendet werden, muss das beschriebene durchgeführt werden, bevor das Projekt , in dem sie verwendet werden soll, unter Source-Control gestellt wird.

 

Verwendung

Nun, da alles eingerichtet ist, können wir endlich dazu übergehen, unsere Subversion-Installation zu verwenden.
SVN_003

Wird jetzt in Visual Studio ein neues Projekt angelegt, zeigt sich eine Checkbox, die zuvor nicht verfügbar war. Diese erlaubt es, das zu erstellende Projekt direkt unter Source-Control zu stellen.

 

 

 

 

SVN_004

Wird diese Option ausgewählt, erscheint nach dem erfolgreichen Anlegen des Projekts ein Subversion-Dialog, in dem bestimmt werden kann, in welchem Repository es verwaltet werden soll.

Hier muss lediglich die URL des zu verwendenden Repositories eingefügt werden. Diese kann ganz einfach aus dem VisualSVN Server Manager kopiert werden.

Bei aktivierter Subversion-Authentifizierung kommt, muss sich der Benutzer nun zunächst anmelden.

Nach einem Klick auf OK steht das neu erstellte Projekt jetzt offiziell unter Versionskontrolle.

In Visual Studio wird man feststellen, dass einige neue Fenster hinzugekommen sind, ebenso wie einige Einträge ins Kontextmenü des Projektmappen-Explorers. Von entscheidender Bedeutung ist hierbei sicherlich das Fenster Pending Changes, dass anzeigt, welche Änderungen seit dem letzten Auschecken aufgetreten sind. Hier können diese Dateien auch wieder eingecheckt werden, was die Änderungen in das Versionskontrollsystem übernimmt.

Da wir uns so viel Mühe gegeben haben, die Keyword Substituation zu aktivieren, zeige ich jetzt noch, wie sie auch tatsächlich verwendet werden kann. Soll beispielsweise nach jedem Einchecken angezeigt werden, wann dies passiert ist und wer dafür verantwortlich zeichnet, fügt man in die Datei einfach einen Header ein, der wie folgt aussehen könnte.

   1:  // $LastChangedDate$
   2:  // $LastChangedBy$


Die Keywords, die enthalten sind, werden nun bei jedem Einchecken ersetzt und überschrieben, so dass der Header immer aktuell ist.

 

Jetzt wünsche ich viel Vergnügen beim Coden und Herumprobieren mit Subversion.
Sollten bei der Verwendung Fragen oder Probleme auftreten, kann das kostenlose Online-Buch Version Control with Subversion sicherlich wertvolle Dienste leisten.

Dienstag, 13. Januar 2009

DataBinding mit einer List<string>

Neulich stieß ich bei dem Versuch eine List<string> als Datenquelle für ein DataGridView zu verwenden auf ein interessantes Verhalten, das ich mir jedoch zunächst nicht erklären konnte.

Nehmen wir beispielsweise folgenden Code:

   1:  public FormMain()
   2:  {
   3:      this.InitializeComponent();
   4:   
   5:      List<string> tvStations = new List<string>();
   6:      tvStations.Add("Pro7");
   7:      tvStations.Add("RTL");
   8:      tvStations.Add("Sat1");
   9:      tvStations.Add("VOX");
  10:      tvStations.Add("Arte");
  11:      tvStations.Add("N24");
  12:      tvStations.Add("Phoenix");
  13:      tvStations.Add("RTL II");
  14:   
  15:      this.dgvTvStations.DataSource = tvStations;
  16:  }


Zu erwarten wäre, dass beim Start der Applikation unser DataGridView die in der Liste enthaltenen Fernsehsender anzeigt. Doch weit gefehlt, denn das Ergebnis sieht in Wirklichkeit so aus:

callingResult

Angezeigt werden uns also keineswegs die Fernsehsender sondern lediglich die Länge der Strings, die in der Liste enthalten sind.
Das liegt daran, dass die Datenquelle untersucht wird, wobei natürlich festgestellt wird, dass es sich um eine Liste von Strings handelt. Versucht man hier via Index auf einen bestimmten Wert zuzugreifen, wird man feststellen, dass die einzige verfügbare Eigenschaft dieses Wertes Length ist. Und genau diese wird dann vom DataGridView verwendet.

Um die Daten nun korrekt anzuzeigen muss ein Umweg in Kauf genommen werden.
Wir erstellen hierfür zunächst eine Klasse, die die Daten entgegennimmt, die wir anzeigen wollen. Im vorliegenden Fall könnte diese beispielsweise so aussehen:

   1:  public class TvStation
   2:  {
   3:      private string name;
   4:   
   5:      public string Name
   6:      {
   7:          get
   8:          {
   9:              return this.name;
  10:          }
  11:      }
  12:   
  13:      public TvStation(string name)
  14:      {
  15:          this.name = name;
  16:      }
  17:  }

 

Nun verwenden wir im Formular, das die Daten anzeigen soll, statt einer List<string> eine List<TvStation> und binden diese an den DataGridView.

   1:  public FormMain()
   2:  {
   3:      this.InitializeComponent();
   4:   
   5:      List<TvStation> tvStations = new List<TvStation>();
   6:      tvStations.Add(new TvStation("Pro7"));
   7:      tvStations.Add(new TvStation("RTL"));
   8:      tvStations.Add(new TvStation("Sat1"));
   9:      tvStations.Add(new TvStation("VOX"));
  10:      tvStations.Add(new TvStation("Arte"));
  11:      tvStations.Add(new TvStation("N24"));
  12:      tvStations.Add(new TvStation("Phoenix"));
  13:      tvStations.Add(new TvStation("RTL II"));
  14:      
  15:      this.dgvTvStations.DataSource = tvStations;
  16:  }

 

Wenn jetzt die Anwendung gestartet wird, zeigt der DataGridView tatsächlich die erwarteten Daten.

correctResult

Schade, dass in diesem Fall ein solcher Umweg nötig ist, aber so funktioniert DataBinding nun einfach mal.

Montag, 10. November 2008

Konsole in Konsolenanwendung verbergen

Gerade hatte ich mal wieder eine "wie Schuppen von den Augen"-Situation.

Zielsetzung war, eine Konsolenanwendung zu erstellen, bei der die Konsole nicht sichtbar sein sollte.
Zuerst habe ich an etwas gedacth wie Console.Hide(), doch eine solche Methode existiert nicht.
Dann habe ich Google bemüht und seltsame Verrenkungen gefunden, die einen DllImport benötigten. Auch nicht gerade die feine Art, wie ich fand.

Und dann schoss es mir in den Kopf: Einfach den Ausgabetyp auf Windows-Anwendung setzen und glücklich werden!
Schon erstaunlich, wie einfach es manchmal sein kann und wie weit man dennoch daneben liegt...

P.S.: Gerade habe ich noch gesehen, dass in den Kommentaren des obigen Blog-Eintrags die von mir verwendete Möglichkeit tatsächlich auch von jemandem erwähnt wurde.

Mittwoch, 29. Oktober 2008

Hexadezimaldarstellung eines Strings nach Int32 umrechnen - bbo-Style

Wie tropensturm Hex-Strings nach Int32 konvertiert, haben wir heute schon gelernt. Natürlich hat er gleich wieder Forderungen gestellt, also kommt hier meine Version.

Zunächst muss ich anmerken, dass ich wesentlich pragmatischer an die Sache herangegangen bin. Ich habe Dinge wie Encoding und Little- / Big-Endian vernachlässigt, denn ich brauchte schnell eine funktionierende Lösung für einen Test.

Folgender Code ist bei mir dabei heraus gekommen:

   1:  public static int ConvertHexStringToInt(string hexString)         
   2:  {
   3:      string hex = "0x";
   4:   
   5:      for (int i = 0; i < hexString.Length; i++)
   6:      {
   7:          hex += ((int)hexString[i]).ToString("X");
   8:      }
   9:   
  10:      return Convert.ToInt32(hex, 16);
  11:  }

Im Prinzip ist das, was ich hier tue, eigentlich recht einfach.
Zunächst lege ich mir eine String-Variable an, die die hexadezimale Darstellung des übergebenen Strings aufnehmen soll. In der for-Schleife sorge ich entsprechend dafür, dass dies auch so ist. In Zeile 7 wandle ich zunächst jeden Buchstaben des Übergabe-Strings in seine ASCII-Int32-Repräsentation, die anschließend nach Hexadezimal konvertiert und dem finalen String angehängt wird.
Ist die Schleife für alle Zeichen des Strings durchlaufen worden, haben wir einen String im Format 0xA9AE.
Diesen String konvertiere ich dann in Zeile 10 noch nach Int32, wobei ich eine Basis von 16 (also hexadezimal) angebe und gebe das Ergebnis zurück.

So weit, so einfach...
... leider aber auch so "beschränkt".
Durch das Konvertieren jedes Buchstabens in seine Int32-Repräsentation in Zeile 7 wird diese Lösung auf Zeichen beschränkt, die im ASCII-Zeichensatz vorhanden sind. Werden Zeichen verwendet, die in diesem nicht auftauchen, führt das zu falschen Ergebnissen.

FAZIT:
Für meine Zwecke ist diese Implementierung absolut ausreichend, da ich mir sicher sein kann, dass mein Übergabe-String nur Zeichen enthält, die auch im ASCII-Zeichensatz auftauchen. Für einen produktiven Einsatz ist aber auf jeden Fall tropensturms Lösung vorzuziehen, da diese eben auch mit nicht ASCII-konformen Zeichen umgehen kann.

bbo-Style: 0
tropensturm-Style: 1

Donnerstag, 9. Oktober 2008

Strings verschlüsseln mit C# - Part 2

Vor einiger Zeit hat chico beschrieben, wie man einen String mit C# verschlüsseln kann. Leider hatte der Beitrag den kleinen Schönheitsfehler, dass die Methode zur Verschlüsselung ein Byte-Array zurückgeliefert hat. Für manche Situationen mag das durchaus ausreichend sein, doch wenn man den verschlüsselten String beispielsweise in einer Datenbank ablegen möchte, wird man mit dem Byte-Array vermutlich nicht weit kommen. Daher möchte ich heute, aufbauend auf dem ersten Artikel zur Thematik, erläutern, wie man statt dem Byte-Array einen String zurückliefern kann.

Auch hier müssen zuerst wieder ein Schlüssel sowie ein Initialisierungsvektor angelegt werden.

   1:  private readonly byte[] key = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 
   2:      13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
   3:  private readonly byte[] iv = new byte[] { 65, 110, 68, 26, 69, 178, 200, 219 };

Die Methode zur Verschlüselung sieht sich ebenfalls noch ziemlich ähnlich. Es wurde lediglich eine Zeile geändert, eine neue kam hinzu.

   1:  /// <summary>
   2:  /// Verschlüsselt einen Eingabestring.
   3:  /// </summary>
   4:  /// <param name="input">Der zu verschlüsselnde String.</param>
   5:  /// <returns>Byte-Array mit dem verschlüsselten String.</returns>
   6:  public string StringVerschluesseln(string input)
   7:  {
   8:      try
   9:      {
  10:          // MemoryStream Objekt erzeugen
  11:          MemoryStream memoryStream = new MemoryStream();
  12:   
  13:          // CryptoStream Objekt erzeugen und den Initialisierungs-Vektor
  14:          // sowie den Schlüssel übergeben.
  15:          CryptoStream cryptoStream = new CryptoStream(
  16:          memoryStream, 
  17:          new TripleDESCryptoServiceProvider().CreateEncryptor(this.key, this.iv),
  18:          CryptoStreamMode.Write);
  19:   
  20:          // Eingabestring in ein Byte-Array konvertieren
  21:          byte[] toEncrypt = new ASCIIEncoding().GetBytes(input);
  22:   
  23:          // Byte-Array in den Stream schreiben und flushen.
  24:          cryptoStream.Write(toEncrypt, 0, toEncrypt.Length);
  25:          cryptoStream.FlushFinalBlock();
  26:   
  27:          // Ein Byte-Array aus dem Memory-Stream auslesen
  28:          byte[] encrypted = memoryStream.ToArray();
  29:   
  30:          // Stream schließen.
  31:          cryptoStream.Close();
  32:          memoryStream.Close();
  33:   
  34:          // Konvertierung in Base64-String
  35:          string encryptedString = Convert.ToBase64String(encrypted);
  36:   
  37:          // Rückgabewert.
  38:          return encryptedString;
  39:      }
  40:      catch (CryptographicException e)
  41:      {
  42:          Console.WriteLine(string.Format(
  43:              CultureInfo.CurrentCulture,
  44:              "Fehler beim Verschlüsseln: {0}", 
  45:              e.Message));
  46:   
  47:          return null;
  48:      }
  49:  }

Die Änderung finden wir in Zeile 21, statt ASCIIEncoding wird jetzt Encoding.UTF8 verwendet. Hinzugekommen ist Zeile 35. Hier wird das ermittelte Byte-Array in einen Base64String umgewandelt, der dann auch zurückgegeben wird.
Genau wie in der Verschlüsselungs-Methode ändert sich auch in ihrem Pendant wenig.

   1:  /// <summary>
   2:  /// Entschlüsselt einen String aus einem Byte-Array.
   3:  /// </summary>
   4:  /// <param name="data">Das verscghlüsselte Byte-Array.</param>
   5:  /// <returns>Entschlüsselter String.</returns>
   6:  public string StringEntschluesseln(string data)
   7:  {
   8:      try
   9:      {
  10:          // Verschluesselten Base64String in Byte-Array konvertieren
  11:          byte[] encrypted = Convert.FromBase64String(data);
  12:   
  13:          // Ein MemoryStream Objekt erzeugen und das Byte-Array
  14:          // mit den verschlüsselten Daten zuweisen.
  15:          MemoryStream memoryStream = new MemoryStream(encrypted);
  16:   
  17:          // Ein CryptoStream Objekt erzeugen und den MemoryStream hinzufügen.
  18:          // Den Schlüssel und Initialisierungsvektor zum entschlüsseln verwenden.
  19:          CryptoStream cryptoStream = new CryptoStream(
  20:          memoryStream,
  21:          new TripleDESCryptoServiceProvider().CreateDecryptor(this.key, this.iv), 
  22:          CryptoStreamMode.Read);
  23:   
  24:          // Buffer erstellen um die entschlüsselten Daten zuzuweisen.
  25:          byte[] fromEncrypt = new byte[data.Length];
  26:   
  27:          // Read the decrypted data out of the crypto stream
  28:          // and place it into the temporary buffer.
  29:          // Die entschlüsselten Daten aus dem CryptoStream lesen
  30:          // und im temporären Puffer ablegen.
  31:          cryptoStream.Read(fromEncrypt, 0, fromEncrypt.Length);
  32:   
  33:          // Den Puffer in einen String konvertieren und zurückgeben.
  34:          return Encoding.UTF8.GetString(fromEncrypt).Replace("\0", string.Empty);
  35:      }
  36:      catch (CryptographicException e)
  37:      {
  38:          Console.WriteLine(String.Format(
  39:              CultureInfo.CurrentCulture,
  40:              "Fehler beim Entschlüsseln: {0}",
  41:              e.Message));
  42:   
  43:          return null;
  44:      }
  45:  }

Die erste Änderung findet sich - natürlich - direkt in der Signatur der Methode (Zeile 6). Entgegengenommen wird jetzt natürlich kein Byte-Array mehr, sondern ein String. Entsprechend haben wir in Zeile 11 dann die Umwandlung des Base64Strings in ein Byte-Array, das dann weiter verarbeitet wird.  Schlussendlich haben wir dann in Zeile 34 die finale Konvertierung in den Rückgabe-String. Auch hier wird statt ASCIIEncoding wieder UTF8 verwendet, schließlich ist das ja jetzt die Basis. Eine Besonerheit ist hierbei, dass am fertigen String einige "\0"s hängen. Diese müssen noch entfernt werden, was hier durch den Aufruf von Replace geschieht.

Der jetzt zurückgegebene String entspricht wieder genau dem, der ursprünglich verschlüsselt wurde. Alles andere wäre natürlich auch inakzeptabel.

Bleiben jetzt nur die Fragen, warum der Umstieg von ASCIIEncoding auf UTF8 sein musste und warum das Byte-Array nicht direkt in einen normalen String, sondern in einen Base64String konvertiert wird. Beides hängt damit zusammen, dass ein Aufruf von ToString() zu falschen Ergebnissen bzw. sogar zu einer Exception in Zeile 29 (beim Entschlüsseln) führt. Dies hängt offenbar damit zusammen, dass der String im Normalfall Steuerzeichen (Stichwort Backslash) enthält. Durch die gemachten Änderungen wird dies umgangen.

Für die endgültige Lösung möchte ich chico danken, der maßgeblich an der Entwicklung beteiligt war.

Donnerstag, 4. September 2008

Strings verschlüsseln mit C#

Strings mit .NET verschlüsseln ist gar nicht schwer. Durch Zuhilfenahme der TripleDESCryptoServiceProvider-Klasse kann ein String ganz einfach ver- und entschlüsselt werden. Hier ein kleines Beispiel: Das erste was wir brauchen ist ein Initialisierungsvektor und ein Schlüssel.

private readonly byte[] key = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
private readonly byte[] iv = new byte[] { 65, 110, 68, 26, 69, 178, 200, 219 };
Der Initialisierungsvektor ist der Eingangsparameter mit dem die Startbedingungen für einen kryptografischen Algorithmus gesetzt werden. Der Schlüssel wird für die Verschlüsselung benötigt.Die beiden Werte werden für das Ver- und Entschlüsseln benötigt. Als nächstes der Code zum Verschlüsseln. Die Kommentare erklären eigentlich den Code.
/// <summary>
/// Verschlüsselt einen Eingabestring.
/// </summary>
/// <param name="input">Der zu verschlüsselnde String.</param>
/// <returns>Byte-Array mit dem verschlüsselten String.</returns>
public byte[] StringVerschluesseln(string input)
{
try
{
// MemoryStream Objekt erzeugen
MemoryStream memoryStream = new MemoryStream();

// CryptoStream Objekt erzeugen und den Initialisierungs-Vektor
// sowie den Schlüssel übergeben.
CryptoStream cryptoStream = new CryptoStream(
memoryStream, new TripleDESCryptoServiceProvider().CreateEncryptor(this.key, this.iv), CryptoStreamMode.Write);

// Eingabestring in ein Byte-Array konvertieren
byte[] toEncrypt = new ASCIIEncoding().GetBytes(input);

// Byte-Array in den Stream schreiben und flushen.
cryptoStream.Write(toEncrypt, 0, toEncrypt.Length);
cryptoStream.FlushFinalBlock();

// Ein Byte-Array aus dem Memory-Stream auslesen
byte[] ret = memoryStream.ToArray();

// Stream schließen.
cryptoStream.Close();
memoryStream.Close();

// Rückgabewert.
return ret;
}
catch (CryptographicException e)
{
Console.WriteLine(String.Format(CultureInfo.CurrentCulture, "Fehler beim Verschlüsseln: {0}", e.Message));
return null;
}
}

Bei DES handelt es sich um einen symmetrischen Algorithmus, das heißt zur Ver- und Entschlüsselung wird derselbe Schlüssel verwendet. TripleDES verschlüsselt die Daten dreimal. Um den String wieder herzustellen, dient folgende Funktion:
/// <summary>
/// Entschlüsselt einen String aus einem Byte-Array.
/// </summary>
/// <param name="data">Das verscghlüsselte Byte-Array.</param>
/// <returns>Entschlüsselter String.</returns>
public string StringEntschluesseln(byte[] data)
{
try
{
// Ein MemoryStream Objekt erzeugen und das Byte-Array
// mit den verschlüsselten Daten zuweisen.
MemoryStream memoryStream = new MemoryStream(data);

// Ein CryptoStream Objekt erzeugen und den MemoryStream hinzufügen.
// Den Schlüssel und Initialisierungsvektor zum entschlüsseln verwenden.
CryptoStream cryptoStream = new CryptoStream(
memoryStream,
new TripleDESCryptoServiceProvider().CreateDecryptor(this.key, this.iv), CryptoStreamMode.Read);
// Buffer erstellen um die entschlüsselten Daten zuzuweisen.
byte[] fromEncrypt = new byte[data.Length];

// Read the decrypted data out of the crypto stream
// and place it into the temporary buffer.
// Die entschlüsselten Daten aus dem CryptoStream lesen
// und im temporären Puffer ablegen.
cryptoStream.Read(fromEncrypt, 0, fromEncrypt.Length);

// Den Puffer in einen String konvertieren und zurückgeben.
return new ASCIIEncoding().GetString(fromEncrypt);
}
catch (CryptographicException e)
{
Console.WriteLine(String.Format(CultureInfo.CurrentCulture, "Fehler beim Entschlüsseln: {0}", e.Message));
return null;
}
}

Mittwoch, 20. August 2008

BizTalk Hotrod Online Magazin

Und ein drittes Mal BizTalk von mir heute... Es gibt ein interessantes freies Online PDF Magazin das sich mit BizTalk beschäftigt und vierteljährlich erscheint: http://biztalkhotrod.com/default.aspx Es ist auf jeden Fall einen Blick wert, da man als BizTalk Entwickler sowieso nicht gerade mit Publikationen erschlagen wird. Die zurückliegenden Ausgaben können unter hier heruntergeladen werden Beim Thema Publikationen sollte ich noch erwähnen dass einzige gute Buch das ich kenne: Professional BizTalk Server 2006 von Wrox (in Englisch versteht sich). Es geht nicht besonders stark in die Tiefe, behandelt eher konstruierte Aufgabenstellungen, aber man findet eigentlich immer zu einem Problem zumindestens einen Ansatz mit dem sich weiterarbeiten lässt. BizTalk Profis werden wohl etwas weniger mit dem Buch anfangen können. Das Buch ist gut strukturiert und liest sich sehr flüssig. Nach Amazon Bewertung würde ich da mal 4 von 5 Punkten für vergeben.

Mittwoch, 23. Juli 2008

HowTo: Erstellen von Snippets

Gestern habe ich über .NET-Snippets.de berichtet, wo man eine Vielzahl von nützlichen Snippets finden kann. Nun kennt sich vielleicht nicht jeder mit der Thematik Snippets aus und weiß, wie man heruntergeladene Snippets in Visual Studio verfügbar macht. Oder das Posting hat den Ehrgeiz geweckt, eigene Snippets zu erstellen, doch es fehlt das Wissen, wie das zu erledigen ist.

Deshalb kam mir der Gedanke, ein HowTo zu schreiben, wie man Snippets erstellen und in VS einbinden kann. Doch da kam mir ein heutiges Posting in den MSDN-News zuvor. Es trägt den Namen "Codierungs-Turbos in Visual Studio 2008" und behandelt einige Interessante Themen, wie man mit Hilfe von Visual Studio Programmierarbeit schneller erledigen kann. Netterweise ist unter anderem ein kompletter Abschnitt über Snippets mit an Bord. Dieser behandelt wirklich alles rund um Snippets. Ganz so, als hätte Microsoft gerochen, was mir gerade so im Kopf herum geht.

Zu erreichen ist der gesamte Beitrag hier: Codierungshilfsmittel Der Abschnitt über Snippets ist hier zu finden: Erstellen und Verwenden von IntelliSense-Codeausschnitten

Dienstag, 22. Juli 2008

OPC: DataChange-Event

Ein OPC-Client hat die Möglichkeit, vom Server Nachrichten über Aktualisierungen von Werten zu empfangen. Das ist natürlich sehr nützlich, wenn man gerne eine bestimmte Aktion ausführen möchte, wenn ein definierter Wert erreicht wird. Kommt ja auch nicht gerade selten vor, diese Anforderung, also hat die OPC Foundation da schon ganz schön schlau definiert.

Nicht ganz so schlau ist jedoch die Umsetzung innerhalb der OpcDAAuto.dll gelungen. Ich habe heute etwa einen halben Tag gekämpft, bis ich tatsächlich in der Lage war, den DataChange-Event, der vom Server bei Veränderungen von zuvor dafür angemeldeten Werten gesendet wird, zu empfangen. Ich habe Tutorials gewälzt, das Internet durchforstet und experimentiert - alles ohne Erfolg. Alle Voraussetzungen, die ich finden konnte, schienen mir erfüllt. Nach ewigem Herumprobieren gab es nur noch einen einzigen Unterschied zwischen den Tutorials - die, heruntergeladen und kompiliert im Übrigen einwandfrei funktionierten - und meiner Applikation. Dieser fand sich beim Anlegen von OPC-Items, also jenen Werten, die ich überwachen wollte. Mein Code - im Übrigen eigentlich absolut korrekt - sah folgendermaßen aus:

   1:  OPCGroupClass opcGroupTriggerItems;
   2:   
   3:  // Gruppe anlegen
   4:  opcGroupTriggerItems = ( OPCGroupClass ) opcServer.OPCGroups.Add( "TriggerItems" );
   5:   
   6:  // Items hinzufügen
   7:  opcGroupTriggerItems.OPCItems.AddItem( connection + "/" + alias + "/TriggerItem1", 0 ) );
   8:  opcGroupTriggerItems.OPCItems.AddItem( connection + "/" + alias + "/TriggerItem2", 1 ) );
   9:  opcGroupTriggerItems.OPCItems.AddItem( connection + "/" + alias + "/TriggerItem3", 2 ) );
  10:  opcGroupTriggerItems.OPCItems.AddItem( connection + "/" + alias + "/TriggerItem4", 3 ) );
  11:   
  12:  // Event Handler Methode registrieren
  13:  // WICHTIG: Die Event Handler Methode wird erst nach dem hinzufügen der Items zur Gruppe registriert,
  14:  //          da sonst beim Hinzufügen für jedes Item ein Event ausgelöst wird.
  15:  opcGroupTriggerItems.DataChange += new DIOPCGroupEvent_DataChangeEventHandler( opcGroupTriggerItems_DataChange );

Hier wird zuerst eine Gruppe auf dem OPC-Server angelegt, zu der dann anschließend die Items hinzugefügt werden, die die Werte darstellen, die überwacht werden sollen. Um zu vermeiden, dass für alle Items ein initialer Event ausgelöst wird, der über die Änderung von "nicht vorhanden" nach "vorhanden" und die damit verbundene Wertänderung benachrichtigt, wird die Event-Handler-Methode erst nach dem Anlegen der nötigen Items registriert. So wurden die Items korrekt angelegt, sie konnten ausgelesen werden, aber die Benachrichtigung über Wertänderungen funktionierte, wie bereits erwähnt, überhaupt nicht.

Der Code, der mir von den Tutorials aufgezeigt wurde und der jetzt, nachdem ich ihn so auch in meiner Applikation habe, zu dem gewünschten Ergebnis führt, sieht so aus:

   1:  OPCGroupClass opcGroupTriggerItems;
   2:  string[] itemIds = new string[5];
   3:  int[] clientHandles = new int[5];
   4:  int[] serverHandles;
   5:  int[] errors;
   6:   
   7:  // Gruppe anlegen
   8:  opcGroupTriggerItems = ( OPCGroupClass ) opcServer.OPCGroups.Add( "TriggerItems" );
   9:   
  10:  // Items erstellen
  11:  itemIds.SetValue( connection + "/" + alias + "/TriggerItem1", 1 );
  12:  itemIds.SetValue( connection + "/" + alias + "/TriggerItem2", 2 );
  13:  itemIds.SetValue( connection + "/" + alias + "/TriggerItem3", 3 );
  14:  itemIds.SetValue( connection + "/" + alias + "/TriggerItem4", 4 );
  15:   
  16:  // ClientHandles erstellen
  17:  clientHandles.SetValue( 1, 1 );
  18:  clientHandles.SetValue( 2, 2 );
  19:  clientHandles.SetValue( 3, 3 );
  20:  clientHandles.SetValue( 4, 4 );
  21:   
  22:  // Items der Gruppe hinzufügen
  23:  opcGroupTriggerItems.OPCItems.AddItems( 4, ref itemIds, ref clientHandles, out serverHandles, out errors, null, null );
  24:   
  25:  // Event Handler Methode registrieren
  26:  // WICHTIG: Die Event Handler Methode wird erst nach dem hinzufügen der Items zur Gruppe registriert,
  27:  //          da sonst beim Hinzufügen für jedes Item ein Event ausgelöst wird.
  28:  opcGroupTriggerItems.DataChange += new DIOPCGroupEvent_DataChangeEventHandler( opcGroupTriggerItems_DataChange );

Auch hier wird zunächst eine Gruppe angelegt. Es werden hier jedoch die Items nicht, wie zuvor, einzeln der Gruppe hinzugefügt. Statt dessen werden ein String-Array mit den Werten und ein Integer-Array mit den Client-Handles, unter denen die Items eindeutig zu identifizieren sind, angelegt. Hier ist die Besonderheit zu beachten, dass der erste Wert des Arrays jeweils nicht beachtet wird, das Array wird immer nur von 1 bis max durchlaufen. Das hängt, so weit ich informiert bin, damit zusammen, dass die von mir verwendete OpcDAAuto.dll in VB geschrieben ist. Jedenfalls werden anschließend die Items und ihre Client-Handles gesamt an eine Methode übergeben, die sie der Gruppe hinzufügen. Anschließend wieder die Event-Handler-Methode registriert. Test anwerfen - funzt.  Sehr erstaunlich und für mich nicht wirklich nachvollziehbar, aber im Endeffekt ist es jetzt auch egal, es läuft ja nun :)

Donnerstag, 17. Juli 2008

HowTo: Erstellen eines Windows Service unter .NET

Da ich gerade zum ersten mal in die Verlegenheit komme, einen Windows Service unter .NET zu entwickeln, habe ich mich mal ein wenig im WWW umgeschaut, um ein brauchbares Tutorial zu finden. Fündig wurde ich hierbei bei CodeGuru.

Im Tutorial wird nicht nur sehr schön erklärt, wie ein Windows Service aufgebaut ist, sondern es wird ebenfalls darauf eingegangen, wie er zu installieren ist und wie man den laufenden Service dann debuggen kann. Gerade für Anfänger auf dem Gebiet sicherlich sehr nützliche Informationen. Das Tutorial ist hier zu finden: http://www.codeguru.com/columns/dotnet/article.php/c6027/

Noch ein Zusatz von meiner Seite: Bei der Installation des Windows Service via InstallUtil.exe über die Visual Studio Eingabeaufforderung ist unbedingt die Version des Frameworks zu beachten, mit der der Service erstellt wurde. Wird nämlich die Eingabeaufforderung einer anderen Framework-Version verwendet als jene, mit der der Service erstellt wurde, erhält man beim Aufruf von InstallUtil.exe nur einen Fehler, der einem mitteilt, dass der Service nicht installiert werden konnte.

Alternativ kann man die normale Windows-Eingabeaufforderung verwenden und InstallUtil.exe aus dem Ordner "C:\WINDOWS\Microsoft.NET\Framework\FRAMEWORKVERSION" heraus starten.

Sonntag, 18. Mai 2008

HowTo: DateTimes sortieren im datengebundenen DataGridView

Ich hatte letztens die Anforderung, die Daten eines DataGridView, nach einem Datumswert zu sortieren. Die Daten innerhalb des DataGridView kamen hierbei aus einer Datenbindung an ein DataSet.
Das Sortieren an sich ist hierbei kein Problem, folgender Aufruf ist völlig ausreichend:

   1:  myDataGridView.Sort( myDataGridView.Columns[2], System.ComponentModel.ListSortDirection.Descending);

Bei meinen Datumswerten sah das zuerst auch sehr gut aus, jedenfalls bis zu dem Zeitpunkt, zu dem mir aufgefallen ist, dass der 28.04. weit vor dem 08.05. liegt, obwohl ich ja eigentlich absteigend sortiert habe. Das liegt ganz einfach daran, dass die Daten innerhalb der Spalte als String behandelt und demnach auch als solcher sortiert werden. Hierfür ist das Verhalten absolut korrekt, für meine Zwecke jedoch absolut falsch.

Es muss also irgendwie möglich sein, dem DataGridView mitzuteilen, dass diese Spalte keine Strings enthält, sondern DateTimes. Allerdings ist dies nicht direkt am DataGridView möglich, da zur Design-Zeit für dieses ja noch gar nicht fest steht, welche Daten, geschweige denn in welcher Reihenfolge die Daten vorliegen.

Demnach muss diese Einstellung direkt an der Datenquelle, hier am DataSet, getroffen werden. Auch das ist glücklicherweise mit nur einer Zeile Code zu machen.

   1:  myDataSet.Tables[0].Columns[2].DataType = typeof(DateTime);

Nun liefert die Sortierung der Datumswerte innerhalb des DataGridViews tatsächlich das gewünschte Ergebnis.

Das gleiche Verfahren kann übrigens angewandt werden, wenn innerhalb des DataGridView Bilder angezeigt werden sollen. Normalerweise hätte man nämlich den Namen der verwendeten Klasse - also System.Drawing.Bitmap - in der Spalte stehen, was natürlich nicht wirklich sinnvoll ist. Der folgende Aufruf sorgt dafür, dass das in der Zeile abgelegte Bild korrekt angezeigt wird.

   1:  myDataSet.Tables[0].Columns[0].DataType = typeof(Bitmap);

So klappt es dann auch mit der Anzeige von Bildern.

Montag, 12. Mai 2008

DataGridView - Vorderste Spalte ausblenden

Heute beschäftigen wir uns mal mit etwas, das eigentlich sehr einfach ist, mich allerdings doch etwas aufgehalten hat, weil einfach das Wissen gefehlt hat. Damit dies nicht wieder passiert, wird hier gebloggt! ;)

Die Aufgabenstellung ist eine extrem einfache:
Verwendet man ein DataGridView zur Anzeige von Daten (z.B. via DataBinding auf ein DataSet), wird standardmäßig ganz links eine Spalte angezeigt, die es ermöglicht, die komplette Zeile zu markieren und durch einen kleinen Pfeil anzeigt, dass eine Spalte die aktuelle ist.
Nun ist in meinem Fall der DataGridView so eingestellt, dass immer die ganze Zeile markiert wird, egal, wo sie angeklickt wird. Daraus folgt, dass wir die besagte erste Spalte nicht benötigen.

Naheliegend wäre es nun ja, dass diese Spalte in der Columns-Collection des DataGridView enthalten ist und sie mit dem Aufruf

   1:  myDataGridView.Columns[0].Visible = false;

unsere Anforderung erfüllt.
Startet man jetzt allerdings die Applikation, wird man feststellen, dass statt der nutzlosen Spalte ganz links die erste eigene Spalte ausgeblendet wird, die unsere Daten anzeigen sollte - so geht es also nicht!

Statt dessen bietet das DataGridView eine Eigenschaft, die festlegt, ob besagte Spalte angezeigt wird oder nicht: DataGridView.RowHeadersVisible
Die Spalte, um die es hier geht, wird also offensichtlich als RowHeader bezeichnet. Reichlich interessant, wie ich finde. Zu meiner Schande muss ich jedoch gestehen, dass ich darauf nie im Leben gekommen wäre. Zum Glück gibt es ja das Internet.

Der korrekte Aufruf zum Ausblenden der ersten Spalte sieht dann also letztendlich so aus:

   1:  myDataGridView.RowHeadersVisible = false;

Die Eigenschaft kann aber natürlich auch einfach im Eigenschaften-Fenster des Designers gesetzt werden.

Problem gelöst, ich bin glücklich und meine GUI gefällt mir wieder. Daumen hoch ;)

Mittwoch, 7. Mai 2008

Das Windows Event Log und seine Tücken

Logging ist in der professionellen Softwareentwicklung ein wichtiges Thema. Nicht umsonst gibt es eigens für das Logging eigene Frameworks wie beispielsweise das sehr gute log4net.
Dieses bietet verschiedenste Appender an, die festlegen, wie und wohin geloggt werden soll. So haben wir unter anderem die Auswahl zwischen dem Schreiben von Einträgen in Datenbanken, Textfiles, die Konsole und das Windows Event Log.
Gerade die letzte Möglichkeit bietet sich - aus meiner Sicht - besonders an, denn sie hat einen großen Vorteil: Jeder System-Administrator der Welt ist (sofern er Windows-Systeme administriert) mit dem Windows Event Log vertraut und weiß, wie er an seine Einträge heran kommt. Sicherlich, es ist auch nicht wirklich komplex, Textfiles, die an einer fest definierten Stelle im System abgelegt werden, zu analysieren, doch warum sollte man dem armen Admin zumuten, noch eine zweite Stelle zu prüfen?

Ein weiterer Vorteil des Loggens in das Windows Event Log: .NET bietet uns schon alles, was dazu nötig ist.
Die Klasse  EventLog beinhaltet tatsächlich alles, was wir brauchen und ist zudem sehr einfach zu verwenden. Nachfolgend mal ein kleiner Code-Schnipsel, der zeigt, wie man ein eigenes Log mit eigener Quelle erstellt und anschließend einen Eintrag dort hinterlegt.

   1:  using System;
   2:  using System.Diagnostics;
   3:   
   4:  namespace BBoSoft.EventLogDemo
   5:  {
   6:      public class EventLogDemo
   7:      {
   8:          [STAThread]
   9:          public static void Main()
  10:          {
  11:              // pruefen, ob die Quelle bereits existiert
  12:              if(!EventLog.SourceExists("DemoSource"))
  13:              {
  14:                  // Source anlegen
  15:                  // der erste Parameter ist die Quelle, in die geschrieben werden soll
  16:                  // der zweite Parameter ist das Log, dem die Quelle hinzugefuegt werden soll
  17:                  EventLog.CreateEventSource("DemoSource", "DemoLog");
  18:              }
  19:   
  20:              EventLog.WriteEntry("DemoSource", "DemoMessage", EventLogEntryType.Information, 150, 12);
  21:          }
  22:      }
  23:  }

Wie hier zu sehen ist, ist das Schreiben eines Eintrags in das Event Log absolut einfach zu erledigen.
Beim Aufruf von EventLog.WriteEntry() werden, für diese Überladung, fünf Parameter übergeben. Zuerst die Quelle, dann die Nachricht des Events, der Typ (Information, Warnung, Fehler), eine selbst gewählte EventId sowie eine selbst gewählte Kategorie.

Lässt man den Code so laufen und schaut in das EventLog, müsste das in etwa so aussehen:

EventLog1

Das hat also wunderbar funktioniert. Erfreulich.
Wer jetzt noch den Titel dieses Postings im Kopf hat, wird sich sicherlich fragen, wo denn hier nun die Tücken liegen. Um das zu demonstrieren, ist es nötig, zuerst einmal die erstellte Source oder am Besten gleich das gesamte Log zu entfernen. Hierzu öffnen wir den Registrierungseditor und navigieren zum Punkt HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog und löschen dort dein Eintrag DemoLog.

Zurück zum Code:
Fügen wir doch mal ein beliebiges Sonderzeichen - in meinem Fall war es ein '/' - in den Namen des Logs ein und führen sie das Programm erneut aus. Sieht das EventLog jetzt nicht richtig interessant aus? ;)

Was haben wir jetzt also gelernt?
Richtig! Sonderzeichen im Namen des Logs sind BÖSE! Ich hab es auf die harte Tour gelernt und hoffe, einigen geholfen zu haben, nicht in die gleiche Falle zu tappen.

Noch ein wichtiger Nachtrag:
Zum Anlegen der Source werden Administratorrechte für das System benötigt, da hier in die Registrierung geschrieben wird.

Dienstag, 22. April 2008

I've joined the Dark Side!

Ja, in der Tat, ich bin heute auf die Dunkle Seite gewechselt, bin jetzt ein Dunkler Entwickler! Aber langsam, von was rede ich da eigentlich? Dunkle Seite von was? Die Dunkle Seite von Visual Studio natürlich!

Wer jetzt keine Ahnung hat, wovon ich rede, hat in letzter Zeit die diversen Blogs bekannter .Net-Entwickler nicht aufmerksam gelesen. Ab in die Ecke dafür! Aber ich will mal nicht so sein und euch verraten, um was es geht. Vor nicht all zu langer Zeit ging es durch die Weiten des Webs, einige Entwickler, die tagtäglich mit Visual Studio arbeiten, wollten sich nicht mehr mit der Standard-Oberfläche abgeben und taten etwas dagegen. Beteiligt waren daran mit Sicherheit Entwickler, die des öfteren in Stunden programmieren, in denen die natürliche Beleuchtung ausgefallen ist und die Ersatzbeleuchtung den Augen viel zu grell. Richtig, ich rede von der Nacht. Das Weiß des standardmäßigen Hintergrunds von Visual Studio hat in eben jenen Stunden den großen Nachteil, sehr grell zu sein und die Augen über kurz oder lang zu ermüden. Daher bestand die Lösung der findigen Entwickler darin, eine Konfiguration für Visual Studio zu erstellen, die die Augen weniger fordert, die Dark Side of Visual Studio war geboren.

Wenn man sich diesen Blog (zumindest im aktuellen Layout, wer weiß, was die Zukunft bringt...) anschaut, fällt einem auf, dass auch hier alles ziemlich dunkel gehalten ist. Da lag es also nahe, dass auch ich mich der Dunklen Seite verschrieb. Die erste Anlaufstelle dafür ist die Website die auffordert: JOIN THE DARK SIDE OF VISUAL STUDIO. Genau hier gibt es die Settings herunter zu laden, die einen auf die dunkle Seite der Programmierung ziehen wollen - allerdings nur für Visual Studio 2008. Die entsprechenden Settings für Visual Studio 2003 (VORSICHT: hier wird ein spezielles Styler-AddIn benötigt!) und 2005 gibt es in diesem Blog zum Download.

Da wahrscheinlich nicht jedem sofort klar ist, wie man diese Settings jetzt in sein Visual Studio bekommt, folgt jetzt eine kleine Anleitung.

Zuerst mal wird das herunter geladene ZIP-File entpackt. Anschließend öffnet man das Studio. Hier geht es weiter mit einem Klick auf Extras --> Einstellungen importieren und exportieren...

1

Im darauf folgenden Assistenten wird der RadioButton vor Ausgewählte Umgebungseinstellungen importieren aktiviert.

2

Ein Klick auf Weiter und es präsentiert sich eine Auswahl, die vorschlägt, die aktuellen Settings vor dem Laden der neuen zu sichern. Das halte ich persönlich für absolut sinnvoll, denn was ist, wenn man nicht für die Dunkle Seite gemacht ist? Ok, es gibt eine Option zum Zurücksetzen der Settings, aber dann ist alles wieder wirklich Standard. An dieser Stelle sollte jeder selbst entscheiden, wie er verfahren möchte. Wie auch immer die Entscheidung ausgefallen ist, nach einem Klick auf Weiter erscheint ein Fenster, in dem einige bereits bekannte Settings zum Import zur Auswahl stehen. Die von uns gewünschten sind hier jedoch nicht vorhanden. Da hilft ein Klick auf den Button Durchsuchen. Hat man dann das Setting gefunden, das man verwenden will, geht es Weiter.

3

Es erscheint nochmal eine Abfrage, die zur Wahl der zu importierenden Einstellungen auffordert. Hier kann man einfach auf Fertig stellen klicken.

4

Anschließend denkt Visual Studio ein wenig darüber nach, was ihm da jetzt angetan wurde, fügt sich jedoch schlussendlich doch folgsam in sein Schicksal und meldet dies auch netterweise mit der Meldung Importvorgang abgeschlossen. Braves kleines Ding.

Wird jetzt ein Projekt geöffnet, müsste das ganze in etwa so aussehen:

5

Welcome to the Dark Side!

Mir persönlich ist die Schrift um Längen zu groß, doch das stellt zum Glück kein großes Problem dar. Will man die Schriftgröße verändern, navigiert man zu Extras --> Optionen..., woraufhin sich der Optionen-Dialog öffnet. Hier wird der Punkt Umgebung erweitert und auf den Unterpunkt Schriftarten und Farben geklickt. Auf der rechten Seite kann jetzt die Schriftgröße reguliert werden, welche nach einem Klick auf OK auch prompt angewendet wird.

Damit sind auch schon alle Arbeiten abgeschlossen. Das Endergebnis kann sich sehen lassen, wie ich denke. Eventuell fällt jetzt etwas Eingewöhnungszeit an, schließlich sind die meisten von uns die Standard-Settings doch sehr gewohnt. Ich selbst habe mich erst heute der Dunklen Seite verschrieben, weshalb ich noch keine wirklichen Erfahrungen damit gemacht habe. Jedoch denke ich, dass mein Studio den Rest seiner Tage auf der Dunklen Seite verbringen wird. Eventuell konnte ich ja den ein oder anderen animieren, sich auch mal auf die Dunkle Seite zu wagen, die Gefahren sind dank Sicherung der vorherigen Settings ja durchaus gering. Falls auch der werte Leser sich in das Dunkel gewagt hat, wünsche ich ihm das, was auch die offizielle Seite des "Projekts" wünscht:

May the dark side of Visual Studio be with you!

Noch ein kleiner Nachtrag: Das hier gezeigte Setting eignet sich auch hervorragend für eine Präsentation, wie ich live miterleben durfte.

Donnerstag, 17. April 2008

Speichern von UserSettings

Das Speichern von Konfigurationseinstellungen ist sicherlich für viele von uns etwas, das einem ständig über den Weg läuft. Leider war es unter der Version 1.1 des .Net-Frameworks nicht möglich, in die Konfigurationsdatei zu schreiben, was bedeutete, dass man eine eigene Lösung, meist basierend auf XML, erstellen musste. Seit der Version 2.0 ist es dem Programmierer jedoch erlaubt, in eben jene Datei zu schreiben. Geradezu paradiesisch, wenn man das nicht gewohnt ist.

Wie genau das zu bewerkstelligen ist, kann man bei MyMSDN nachlesen, und zwar genau hier: Wie speichere ich Konfigurationseinstellungen meiner Anwender?

An und für sich ist die Anleitung dort sehr gut, doch mir persönlich fehlen einige Informationen. Beispielsweise wird nicht darauf eingegangen, wie man auf einfache Art und Weise die Konfigurationsdatei anpassen kann. Aus diesem Grund werde ich das Vorgehen hier noch einmal kurz erläutern, um auch die letzten Fragen aus dem Weg zu räumen.

  1. Erstellen der Settings
    Wurde mit Hilfe von Visual Studio eine Solution angelegt, so findet sich zu Beginn die folgende Ordnerstruktur:

    1

    Erweitert man hier den Ordner Properties, findet man eine Datei Namens Settings.settings. Öffnet man diese, hat man die folgende Übersicht vor sich:

    2

    In diesem Fenster ist es nun möglich, die benötigten Konfigurationsschlüssel, deren Datentypen sowie die Default-Werte einzutragen. Um den Wert des Konfigurationsschlüssels ändern zu können, muss im Feld Bereich unbedingt Benutzer eingetragen sein. Wird statt dessen Anwendung verwendet, ist der Konfigurationsschlüssel aus dem Code heraus nicht veränderbar. Diese sollten für Werte verwendet werden, die immer gleich bleiben, beispielsweise die Adresse eines Webservice.
    Ist die Bearbeitung abgeschlossen, kann die Datei gespeichert und geschlossen werden.

    HINWEIS:
    Wirt man jetzt einen Blick in die Datei app.config, wird man feststellen, dass die soeben in der Datei Settings.settings erstellten Konfigurationsschlüssel hier eingetragen wurden. Das bedeutet, dass man hier auch direkt das XML hätte bearbeiten können, doch ist die Erstellung über die Datei wesentlich einfacher, weshalb diese sicherlich bevorzugt werden wird.

  2. Lesen von Konfigurationsdaten
    Der erste Schritt, um via Code die Konfiguration auszulesen bzw. zu schreiben, ist ein Using-Statement. Es muss der Namespace der eigenen Applikation, gefolgt von einem Properties eingebunden werden. In unserem Beispiel wäre das also:

       1:  using WindowsFormsApplication1.Properties;

    Nun kann eine Instanz der Klasse Settings angelegt werden, die unsere Konfigurationsschlüssel in Form von Properties vorhält. Dem entsprechend einfach ist es dann auch, die Werte auszulesen. Die Instanz sollte sinnvollerweise als privates Feld angelegt werden, damit der Zugriff darauf jederzeit möglich ist. Der ideale Ort zum Auslesen der Konfiguration ist das FormLoad-Event.

       1:  Settings userSettings = new Settings();
       2:   
       3:  private void Form1_Load(object sender, EventArgs e)
       4:  {
       5:      this.textBox1.Text = userSettings.Text;
       6:  }

    Hiermit wird der Schlüssel Text aus der Konfiguration ausgelesen und in die Textbox geschrieben. Wird die Applikation nun gestartet, sehen wir den Erfolg:

    3

    Der in unserer Konfiguration hinterlegte Text wurde tatsächlich in die TextBox geschrieben.

  3. Schreiben von Konfigurationsdaten
    Entsprechend des Vorgehens zum Lesen der Konfiguration funktioniert auch das Schreiben. Wir weisen einfach der zu ändernden Eigenschaft einen neuen Wert zu. Was man an dieser Stelle jedoch unbedingt beachten muss, damit der Wert tatsächlich gespeichert wird, ist der Aufruf der Methode Save der Settings-Instanz.
    Der beste Ort zum Speichern der veränderten Werte, zumindest in unserem Beispiel, ist das FormClosed-Event. In der Regel bietet sich hier allerdings eher das Click-Event eines Speichern-Buttons an. Der Code für unser Beispiel gestaltet sich wie folgt:

       1:  private void Form1_FormClosed(object sender, FormClosedEventArgs e)
       2:  {
       3:      userSettings.Text = this.textBox1.Text;
       4:      userSettings.Save();
       5:  }

    Wenn wir jetzt die Applikation erneut starten, den Text ändern, die Applikation beenden und gleich darauf wieder starten, sollte der geänderte Text in der TextBox sichtbar sein. Und siehe da, es hat - jedenfalls bei mir - wunderbar funktioniert.

    4

  4. Wichtiger Hinweis zum Schluss
    Etwas ganz Wichtiges noch zum Abschluss dieses kleinen Exkurses.
    Schaut man in die *.conifg-Datei, die im Verzeichnis liegt, in das die kompilierte Datei von Visual Studio gelegt wird, stellt man fest, dass hier nach wie vor der Standard-Wert steht. Startet man jedoch die Applikation, ist nicht etwa der Default-Wert zu sehen, sondern der geänderte. Um diesem Rätsel auf die Spur zu gehen, sollte man den Ordner C:\Dokumente und Einstellungen\Windows_Benutzer\Lokale Einstellungen\Anwendungsdaten\ aufsuchen. Dort findet sich ein Ordner, der den Namen der in der AssemblyInfo eingetragenen Firmennamen trägt, darunter einen mit dem Namen der Applikation und hierunter wiederum einen mit der entsprechenden Version. Ist man dort endlich angekommen, findet man eine Datei namens user.config. Schaut man sich nun diese Datei an, wird man den Wert finden, der auch tatsächlich in unserem Test innerhalb der TextBox zu sehen ist. Hier werden also unsere Konfigurationswerte abgelegt. Die *.config-Datei dient demnach nur noch dazu, um zum einen auf die user.config zu verweisen und zum anderen, um die Default-Werte vorzuhalten, die jederzeit mit dem Aufruf

       1:  userSettings = Settings.Default;

    wiederhergestellt werden können.
    Will man seine Applikation wieder restlos aus dem System tilgen, müssen also nicht nur *.exe und *.config vom Ausführungsort entfernt werden, sondern auch die user.config aus dem Anwendungsdaten-Ordner.

  5. Fazit
    Wie hier zu sehen ist, ist mit dem .Net-Framework in der Version 2.0 das Schreiben von Konfigurationsdaten extrem simpel geworden. Einfach grandios, wenn man an die Verrenkungen denkt, die man teilweise unter der Framework-Version 1.1 machen musste, um seine Konfiguration "benutzbar" zu machen.
    Ich hoffe, ich konnte einigen etwas neues zeigen und ihnen so das Leben erleichtern.

Donnerstag, 3. April 2008

Wie beende ich eine Applikation richtig?

Aus aktuellem Anlass hier mal etwas sehr grundlegendes, was einem in manchen Situationen aber absolut nicht einfallen will - eventuell, weil es zu einfach ist? ;)

Problemstellung:
Wir haben innerhalb eines FormLoad-EventHandlers eine Verzweigung, die entweder den EventHandler rekursiv aufruft, oder die Applikation beendet.

Lösungsansatz:
Für den Fall, dass die Applikation beendet werden soll, wird einfach ein Application.Exit() aufgerufen. Klingt ja so weit schön einfach. Wird aber ein folgendes Konstrukt zusammengestöpselt, erleben wir eine Überraschung.

   1:  Form1_Load(object sender, EventArgs e)
   2:  {
   3:      int retry = 0;
   4:   
   5:      try
   6:      {
   7:          string s = IrgendeinAufruf();
   8:      }
   9:      catch(Exception)
  10:      {
  11:          retry++;
  12:   
  13:          if(retry < 4) // 3 Versuche
  14:          {
  15:              Form1_Load(sender, e);
  16:          }
  17:          else
  18:          {
  19:              Application.Exit();
  20:          }
  21:      }
  22:   
  23:      MacheIrgendwasMitEinemString(s);
  24:  }

Hier werden wir eine NullReferenceException erhalten, denn das Application.Exit() wirkt nicht sofort, was dazu führt, dass die Methode 'MacheIrgendwasMitEinemString' auch noch ausgeführt wird, wobei der String natürlich nicht initialisiert wurde.

Die Lösung des Problems ist ein kleines, süßes, nützliches, gewöhnliches 'return', wodurch der korrekt funktionierende Code dann so aussieht:

   1:  Form1_Load(object sender, EventArgs e)
   2:  {
   3:      int retry = 0;
   4:   
   5:      try
   6:      {
   7:          string s = IrgendeinAufruf();
   8:      }
   9:      catch(Exception)
  10:      {
  11:          retry++;
  12:   
  13:          if(retry < 4) // 3 Versuche
  14:          {
  15:              Form1_Load(sender, e);
  16:          }
  17:          else
  18:          {
  19:              Application.Exit();
  20:              return;
  21:          }
  22:      }
  23:   
  24:      MacheIrgendwasMitEinemString(s);
  25:  }

Schlußfolgerung:
Würdigt das RETURN, hegt es, pflegt es und vor allem: nutzt es da, wo es nötig ist! ;)