Samstag, 11. Februar 2012


Artikel

Oktober 2006 | Artikel

Visual Basic international

(Link zum Artikel: http://www.entwickler.de/dotnet//000987)

Dank der ResourceManager-Klasse lassen sich die Inhalte von Ressourcendateien einfach in eigene Programme integrieren

Text: von Peter Monadjemi
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
Es gehört nicht nur zum guten Stil bei einer modernen Anwendung, oft ist es auch sehr praktisch, Bitmaps, Icons, Zeichenketten, ganze Text-Files, aber auch serialisierte Objekte, mit denen eine Anwendung arbeiten soll, in einer Ressourcendatei zusammenzufassen, die die Programmdatei begleitet. Praktische Voraussetzung sind Ressourcendateien, wenn es darum geht, eine Anwendung mit möglichst wenig Aufwand und Abhängigkeiten mehrsprachig zu gestalten.

Ressourcen ist ein anderes Wort für Bitmaps, Icons, Zeichenketten und alles das, was von einem Programm angesprochen wird, aber nicht im Quelltext definiert werden soll. Ressourcen gibt es bei Windows bereits „ewig“ – das PE-Format (Portable Excetuable) einer Windows-Programmdatei sah schon von Anfang vor, Ressourcen direkt in der Programmdatei unterzubringen. Auch wenn Assemblies ebenfalls im PE-Format vorliegen, diese Ressourcen haben überhaupt gar nichts mit den Ressourcen unter .NET zu tun. Wenn Sie mit einem Ressourcen-Editor wie Resource Tuner eine Assembly-Datei laden, wird der Ressourcenblock zwar konkret angezeigt, nur die enthaltenen Ressourcen sehen sie nicht. Diese Tools können Sie zum Nachbearbeiten einer Assembly daher vergessen. Das ist jedoch kein Nachteil, da die Möglichkeiten von Visual Studio mehr als ausreichend sind. Mit dem kleinen Freeware-Tool Resource Hacker lassen sich Win32-Ressourcen in einer Assembly unterbringen, was immer in dem Fall sehr praktisch ist, wenn diese Ressourcen über das /res-Protokoll des Internet Explorer angesprochen werden sollen (siehe www.codeproject.com).

Dass sich Ressourcen auch bei .NET direkt in die Programmdatei einbetten lassen, ist ein Umstand, den nicht viele Programmierer kennen – ein kleines „Experiment“ soll dies veranschaulichen:

  1. Starten Sie Visual Basic 2005 und legen Sie ein Windows-Projekt an.
  2. Ordnen Sie auf dem Formular einen Button und eine PictureBox an.
  3. Fügen Sie zum Projekt ein beliebiges Bitmap hinzu, etwa Sonnenuntergang.jpg (es könnte natürlich auch eine animierte Gif-Datei sein).
  4. Selektieren Sie das Bitmap im Projekt-Explorer, drücken Sie [F4], um die Eigenschaften anzuzeigen, und wählen Sie anschließend bei BUILDVORGANG die Einstellung Eingebettete Ressource – dadurch wird die Datei ein Teil der Assembly-Datei.
  5. Fügen Sie in die Click-Prozedur des Buttons die Befehle aus Listing 1 ein. Diese greifen über die GetManifestResourceStream-Methode direkt auf das in der Assembly eingebettete Bitmap als Stream zu. Sehr praktisch ist dabei, dass die Image-Klasse eine (freigegebene) FromStream-Methode besitzt, sodass der Byte-Stream nicht über ein Array oder ein MemoryStream-Objekt eingelesen werden muss. Der Name der Ressource setzt sich aus dem Projektnamen und dem Dateinamen zusammen und es muss Groß- und Kleinschreibung unterschieden werden.
  1. Listing 1
  2. ---------------------------------------------------------------------------------------------
  3. Diese Befehle lesen das eingebettete Bitmap in der Assemblydatei
  4. Dim Ass As Assembly = Assembly.GetExecutingAssembly
  5. Dim ResName As String = "VB_ResDirekt.Sonnenuntergang.jpg"
  6. Dim St As System.IO.Stream = Ass.GetManifestResourceStream(ResName)
  7. PictureBox1.SizeMode = PictureBoxSizeMode.StretchImage
  8. PictureBox1.Image = Image.FromStream(St)
Ressourcen in Assemblies auslagern
So praktisch es in manchen Fällen sein mag, alles gesammelt in einer Datei zu haben (auch die auf diese Weise eingebetteten Ressourcen lassen sich nicht per Win32-API und mit den üblichen Ressourcen-Editoren auslesen), in den meisten Fällen werden Ressourcen bei .NET in Ressourcendateien ausgelagert. Eine Ressourcendatei ist eine Datei, die nicht ausführbare Daten enthält und die die Anwendung begleitet, also mit ihr ausgeliefert wird. Definiert wird eine Ressourcendatei durch eine spezielle Definitionsdatei im XML-Format, die die Erweiterung .Resx trägt. Visual Basic 2005 bietet dafür nicht nur eine entsprechende Vorlage an (allerdings nicht bei der Express-Version von Visual Basic 2005 – hier müssen Sie eine Textdatei hinzufügen und ihr die Erweiterung .Resx geben. Sobald der erste Eintrag hinzugefügt wurde, wird der benötigte XML-Rahmen angelegt), sondern bietet auch einen einfachen Editor, sodass Sie die XML-Datei nicht direkt editieren müssen. Theoretisch genügt für die Definition einer Ressourcendatei sogar eine einfache Textdatei, die aus Namen-Wert-Paaren besteht:

  1. Begruessung1=Good Morning
  2. Abschied1= Bye, bye
  3. Fehler1000=You Dummy, you
  4. usw.

Über das Tool Resgen.exe aus dem .NET Framework SDK lässt sich diese Datei in eine .Resx-Datei oder in ihre binäre Entsprechung (eine Datei mit der Erweiterung .Resources) umwandeln. Handelt es sich, wie im erwähnten „Beispiel“, um eine Datei, die die landessprachlichen Zeichenketten enthält, kommt es bei der resultierenden .Resx-Datei auf den Dateinamen an. Dieser muss dem Projektnamen mit dem Anhang des ISO-639-Kürzels für das Land entsprechen (z.B. „en-GB“ für England), beispielsweise VB_RessourceBsp.en-GB.resx. Die resultierende .Resx-Datei kann direkt in ein Visual-Basic-Projekt eingefügt werden, wobei die IDE beim Kompilieren dafür sorgt, dass daraus eine Satelliten-Assembly wird (mehr dazu in Kürze), die in das dafür erforderliche „Sprachenverzeichnis“ kopiert wird – für den Fall, dass sie automatisch ausgewählt wird, wenn die CultureInfo der Anwendung auf die entsprechende Kultur gesetzt wird. Bevor es aber um das Thema „Mehrsprachigkeit mit Satelliten-Assemblies“ geht, soll gezeigt werden, dass sich in Ressourcendateien beliebige „Dinge“ ablegen lassen und dass der Zugriff auf diese Ressourcen bei Visual Basic 2005 dank des neuen Ressourceneditors ein Kinderspiel ist.

Ressourcendateien als externe Ablage
Ressourcendateien haben nicht automatisch etwas mit Mehrsprachigkeit zu tun, sie lassen sich als Ablage für alles das verwenden, was nicht in der Programm-Assembly enthalten sein soll. Auch dazu ein kleines „Experiment“:

  1. Starten Sie Visual Basic 2005 und legen Sie eine Windows-Anwendung an. Geben Sie Ihr den Namen VB_ResSimpel und speichern Sie das Projekt ab.
  2. Ordnen Sie auf dem Formular ein Label an.
  3. Machen Sie im Projekt-Explorer alle Dateien sichtbar – sie werden feststellen, dass es unter My Project eine Datei mit dem Namen Resources.resx gibt, die von einer Quellcodedatei mit dem Namen Resources.designer.vb begleitet wird. Jedes Projekt besitzt also von Anfang an eine Ressourcendatei.
  4. Sie könnten die Ressourendatei zwar doppelt anklicken, doch es gibt auch einen „offiziellen“ Weg, um sie zu editieren. Wechseln Sie in die Projekteigenschaften und dort in das Register RESSOURCEN. Der Inhalt der Ressourcendatei wird im neuen Ressourceneditor von Visual Basic 2005 angezeigt (den es auch bei der Express-Edition gibt) und Sie können ihren Inhalt (relativ) bequem editieren und zum Beispiel neue Elemente hinzufügen.
  5. Tragen Sie ein neues String-Element ein, geben Sie ihm den Namen Welcome und tragen Sie als Wert einen beliebigen Text ein.
  6. Wechseln Sie in die Form_Load-Ereignisprozedur und geben Sie dort dann den Befehl Label1.Text = My.Resources.Welcome ein.

Bereits nach Eingabe von My.Resources werden Sie feststellen, dass Welcome in der Auswahlliste angeboten wird. Das ist keiner dieser „Hinter-den-Kulissen-Tricks“ à la Visual Basic, sondern beruht auf dem Umstand, dass für die Ressource eine CodeBehind-Datei mit einer Klasse angelegt wird, welche den Eintrag typisiert (also als String) zur Verfügung stellt. In Wirklichkeit wird der folgende Befehl ausgeführt:

  1. ResourceManager.GetString("Welcome", resourceCulture)

Der Zugriff über My.Resources erspart daher den Zugriff über die ResourceManager-Klasse, die Sie im nächsten Abschnitt kennen lernen werden.

Eine Anwendung wird mehrsprachig
Um eine Anwendung mehrsprachig zu gestalten, muss für jede Landessprache eine eigene Ressourcendatei hinzugefügt werden. Wichtig hierbei ist, dass sie den passenden Namen erhält, in der die ISO-Kulturbezeichnung enthalten ist. Es gibt (mindestens) zwei Varianten für die richtige Namensgebung. Entweder besteht dieser aus dem Projektnamen und dem Kulturkürzel. Oder die Ressourcendatei trägt den allgemeinen Namen Resources ergänzt um das Kürzel (beispielsweise Resources.it-IT.resx). Der Zugriff auf die Ressourcen geschieht über die RessourceManager-Klasse, die neben einer GetString- auch eine allgemeine GetObject-Methode anbietet. Beide Methoden sind überladen, sodass neben dem Namen der Ressource auch ein CultureInfo-Objekt übergeben werden kann, um gezielt auf eine bestimmte Ressourcendatei zuzugreifen. Im Allgemeinen ist dies aber nicht erforderlich, da automatisch jene Ressourcendatei angesprochen wird, die zu der Kultur gehört, die aktuell über die CurrentUICulture-Eigenschaft des Haupt-Threads der Anwendung ausgewählt wurde. Im einfachsten Fall lädt das Programm, etwa im Konstruktor des Formulars im Anschluss an den Aufruf von InitializeComponent, die Ressourcenelemente und weist sie den einzelnen Steuerelementen zu (Listing 2).

  1. Listing 2
  2. --------------------------------------------------------------------------------------
  3. Private Ass As [Assembly] = Assembly.GetExecutingAssembly
  4. ' Dim Res As New Resources.ResourceManager("VB_RessourceBsp.VB_RessourceBsp", Ass)
  5. DateiToolStripMenuItem.Image = Res.GetObject("Flagge")
  6. ÖffnenToolStripMenuItem.Text = Res.GetString("OeffnenMenu")

Ist eine angesprochene Ressource nicht vorhanden, ist eine MissingManifestResourceException-Ausnahme die Folge. Es versteht sich von selbst, dass diese Befehlsfolgen nur einmal im Programm enthalten sind, da sie ja die Auswahl anhand der aktuellen eingestellten Kultur treffen und damit selber vollkommen unabhängig von einer Ressourcendatei sind. Sie müssen lediglich dafür sorgen, dass die Namen in jeder Ressourcendatei identisch sind. Es ist übrigens kein Problem, auch komplette Textdateien in eine Ressourcendatei unterzubringen, der Zugriff auf den Textinhalt erfolgt ebenfalls über die GetObject-Funktion. Wurde eine Sound-Datei eingefügt, wird diese über GetStream eingelesen:

  1. My.Computer.Audio.Play(Res.GetStream("Hymne"), AudioPlayMode.Background)
Ein Blick hinter die Kulissen
Es ist faszinierend zu beobachten, wie unauffällig die IDE dem Entwickler eine Menge Formalitäten abnimmt. Ohne die IDE müsste jede Ressourcendatei per Resgen.exe in eine .Ressources-Datei übersetzt und per Assembly-Linker Al.exe in eine Satelliten-Assembly kompiliert werden (der Name rührt von dem Umstand, dass sie die Haupt-Assembly wie ein Satellit begleitet). Die IDE sorgt dafür, dass dies automatisch geschieht. Wenn Sie im Projekt-Explorer (dazu müssen alle Dateien sichtbar sein) einen Blick in die Verzeichnisstruktur des bin-Verzeichnis werfen, erkennen Sie, dass für jede Ressourcendatei ein Unterverzeichnis mit dem ISO-Kürzelnamen als Name angelegt wird, in dem sich die Satelliten-Assembly befindet. Voraussetzung ist lediglich, dass die .Resx-Datei den richtigen Namen enthält. Wird das Programm auf einen anderen Rechner übertragen, muss diese Verzeichnisstruktur im Assembly-Verzeichnis enthalten sein, was z.B. durch ein Setup-Projekt erledigt wird. Hier empfiehlt es sich den Setup-Wizard zu benutzen und im zweiten Schritt die Option Lokalisierte Ressourcen ... anzukreuzen. Damit müssen die Ressourcendateien nicht einzeln angelegt werden.

„Vorsicht“ vor der Localizable-Eigenschaft
Microsoft meinte es offenbar gut mit den Entwicklern und wollte das Mehrsprachigmachen einer Anwendung möglichst einfach gestalten, allerdings mit zweifelhaftem Resultat. Die Rede ist von den Eigenschaften Localizable und Language der Form-Klasse. Die Idee ist, dass Sie die Localizable-Eigenschaft eines Formulars auf True setzen, über Language eine neue Sprache auswählen und dann in jedem Steuerelement etwa die Text-Eigenschaft entsprechend der Landessprache ändern. Visual Studio legt dadurch nicht nur eine Ressourcendatei für jede ausgewählte Sprache an (auch für jene, die Sie vielleicht nur versehentlich ausgewählt haben), sondern sorgt auch dafür, dass die Eigenschaften nach dem Programmstart ihre Werte aus der zuständigen Ressourcendatei erhalten.

Die Idee ist gut, weniger gut ist die Umsetzung gelungen, denn der Quellcode wird durch einen „Wust“ von Befehlen aufgebläht, die auch jenen Eigenschaften ihre Werte zuweisen, die gar nicht mehrsprachig gehalten werden sollen. Hier nachträglich Änderungen vorzunehmen ist relativ aussichtslos. Auch wenn es verlockend erscheinen mag, der direkte Weg über das Anlegen von Ressourcendateien ist der einzige vernünftige Weg.

Zu einer echten Mehrsprachigkeit einer Anwendung gehört natürlich sehr viel mehr als verschiedene Ressourcendateien, die zur Laufzeit ausgewählt werden. Hier steht das Erstellen und Bearbeiten der Texte durch externe Mitarbeiter, das Einpassen in die Benutzeroberfläche (ein Ausgabetext wird in unterschiedlichen Sprachen im Allgemeinen auch unterschiedlich lang, sodass beispielsweise die Breite eines Labels passend gewählt werden muss) und vieles mehr im Vordergrund. Das kann Visual Studio nicht mehr leisten und man ist daher auf die Mitwirkung spezieller Helfer wie etwa Catalyst angewiesen, die zum Beispiel in der Lage sind, sämtliche Menü- und Labeltexte eines Programms in eine Ressourcendatei zu extrahieren.

Kommentare

Gravatar Robert 27.08.2011
um 20:09 Uhr
sehr guter Artikel, hab Wochenlang nach einem vernünftigen Resource Beispiel für VB.NET gesucht. Dieser hier funktioniert nun endlich. Vielen Dank an it-republik! :) #zitieren