Biggle's Blog

Web- und Software Development

by Mario Binder

Mit LINQ2XML innerhalb einer foreach serialisieren

Ziel dieses HowTo’s ist es, mehrere Daten aus einem Service (hier in dem Beispiel aus einem Mock) via LINQ in eine XML zu schreiben (serialisieren).

Die XML soll folgendermaßen aussehen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Localization>
  <Module id="Customers">
    <CultureCode>de-DE</CultureCode>
    <Key>General</Key>
    <Value>Allgemein</Value>
  </Module>
  <Module id="Customers">
    <CultureCode>en-EN</CultureCode>
    <Key>General</Key>
    <Value>General</Value>
  </Module>
  <Module id="Customers">
    <CultureCode>de-DE</CultureCode>
    <Key>Customer</Key>
    <Value>Kunde</Value>
  </Module>
</Localization>

Dazu inizialisiere ich mir zu erst den Datenpool, in diesem Fall benutze ich eine Klasse die mir ein paar Mock-Objekte bereitstellt.

1
LocalizationServiceMock lm = new LocalizationServiceMock();

Dann erstelle ich mir das erste XML Element “Localization”, welches weitere Elemente beinhalten soll.

1
XElement moduleElement = new XElement("Localization");

Nun iteriere ich durch die Liste von Objekten die ich von meinem Mock bekomme und füge dem oben erstellten “moduleElement” die Daten aus dem Mock, als jeweils neues Element hinzu.

1
2
3
4
5
6
7
8
9
foreach (LocalizationServiceMock.TranslatedString translatedString in lm.GetTranslatedStrings())
{
  moduleElement.Add(new XElement("Module", new XAttribute("id", translatedString.Module),
        new XElement("CultureCode", translatedString.CultureCode),
        new XElement("Key", translatedString.Key),
        new XElement("Value", translatedString.Value)
        )
  );
}

Zum Schluss wird das Ganze noch in ein XDocument geschrieben

1
2
3
4
var localizationService = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
    moduleElement
 );

und gespeichert:

1
localizationService.Save("Services/LocalizationService.xml");

Fertig sieht das folgendermaßen aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void WriteXml()
{
  LocalizationServiceMock lm = new LocalizationServiceMock();
  XElement moduleElement = new XElement("Localization");
 
  foreach (LocalizationServiceMock.TranslatedString translatedString in lm.GetTranslatedStrings())
  {
    moduleElement.Add(new XElement("Module", new XAttribute("id", translatedString.Module),
          new XElement("CultureCode", translatedString.CultureCode),
          new XElement("Key", translatedString.Key),
          new XElement("Value", translatedString.Value)
          )
    );
  }
  var localizationService = new XDocument(
    new XDeclaration("1.0", "UTF-8", "yes"),
      moduleElement
   );
  localizationService.Save("Services/LocalizationService.xml");
}

Viel Spass beim entwickeln : )

by Mario Binder

MenuItems aus XML auslesen und binden – WPF / MVVM

Folgende Aufgabenstellung:

Eine XML soll die Daten für ein Menü bereitstellen. Das ganze ohne CodeBehind und unter MVVM.

Die XML sieht folgendermaßen aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<TopNavigationItems>
  <NavigationItem>
    <MenuImage>new.png</MenuImage>
    <MenuName>Neu</MenuName>
  </NavigationItem>
  <NavigationItem>
    <MenuImage>preferences.png</MenuImage>
    <MenuName>Tools</MenuName>
  </NavigationItem>
  <NavigationItem>
    <MenuImage>search.png</MenuImage>
    <MenuName>Suche</MenuName>
  </NavigationItem>
</TopNavigationItems>

Dazu habe ich als Model eine Klasse TopNavigationMenuItem erstellt mit lediglich zwei Propertys:

1
2
3
4
5
  public class TopNavigationMenuItem
  {
    public String MenuName { get; set; }  
    public String MenuImage { get; set; }  
  }

Anschliessend hab ich eine weitere Klasse in Models erstellt welche eine Methode enthält, die eine Liste von TopNavigationMenuItem zurück gibt. Hier benutze ich LINQ2XML um mir die Knoten heraus zu selektieren:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
 
public class TopNavigationMenuItems
  {
    private static TopNavigationMenuItem navigationItem;
    private static List<TopNavigationMenuItem> topNavigationMenuItemList = new List<TopNavigationMenuItem>();
 
    public static List<TopNavigationMenuItem> getTopNavigationItems()
    {
      var xmlPath = Path.GetDirectoryName(@"\Pfad\zur\TopNavigationItems.xml";
      var NavigationItems = XElement.Load(xmlPath);
 
      foreach (var item in NavigationItems.Elements())
      {
        navigationItem = new TopNavigationMenuItem();
        navigationItem.MenuName = (string)item .Element("MenuName");
        navigationItem.MenuImage = (string)item .Element("MenuImage");
        topNavigationMenuItemList.Add(navigationItem); 
      }
      return topNavigationMenuItemList;
    }
  }

Im ViewModel habe ich dann das ViewModelProperty TopMenuItems erstellt welche die Daten für die View in eine ObservableCollection bereitstellen soll.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    #region ViewModelProperty: TopMenuItems
    private ObservableCollection<TopNavigationMenuItem> _topNavigationMenuItem = new ObservableCollection<TopNavigationMenuItem>();
    public ObservableCollection<TopNavigationMenuItem> TopMenuItems
    {
      get
      {
        return _topNavigationMenuItem;
      }
 
      set
      {
        _topNavigationMenuItem = value;
        OnPropertyChanged("TopMenuItems");
      }
    }
    #endregion

im Konstruktor von dem ViewModel wird dann die Liste aus dem Model der ObservableCollection zugewiesen:

1
2
3
4
    public TopMenuMainViewModel()
    {
      TopNavigationMenuItems.getTopNavigationItems().ForEach(x => TopMenuItems.Add(x));
    }

Und hier kommt der der Lamda Ausdruck zum Einsatz, den wir schon einmal Beachtung geschenkt haben.

Zum Schluss dann in der View noch die Daten an die ObservableCollection binden und in einem DataTemplate aufbereiten:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<UserControl ...
    <UserControl.Resources>
        <DataTemplate x:Key="MenuItemStyle">
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding MenuImage}" Width="16" Height="16" Margin="0 0 5 0"  />
                <TextBlock Text="{Binding MenuName}" />
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>
 
    <Grid>
        <Menu Grid.Column="1"
              VerticalAlignment="Top" 
              HorizontalAlignment="Left" 
              Background="Transparent" ItemsSource="{Binding TopMenuItems}" ItemTemplate="{StaticResource MenuItemStyle}" />
</UserControl>

Fertig : )

by Mario Binder

Mehrsprachigkeit / Lokalisierung in WPF

Folgender Artikel beschreibt die technische Umsetzung  zweier Methoden um WPF Anwendungen mehrsprachig zu gestalten.

Mögliche Lösungsansätze:

  • Ressource-Files
  • Xml-Files

Ressource-Files (.resx) liegen sehr nahe, da der Zugriff auf diese bereits vom Framework bereitgestellt wird, und da Ressource-Files XML-Files sind, ist auch der Umgang mit ihnen kein Problem. Es gibt aber durchaus Situationen, wo “normale” XML-Files die bessere Wahl wären – z.B. wenn »Plattform-Unabhängigkeit« , oder »Skalierbarkeit« eine Rolle spielen. Die beiden in Frage kommenden Möglichkeiten werden im Folgenden technisch analysiert und vorgestellt.

Lösungsansatz Resource-Files

Feste Elemente, können in einer Anwendung durch Resources in der jeweiligen Sprache definiert werden. Durch ein einfaches Duplizieren einer bestehenden Resource.SPRACHE.resx und der Anpassung dieser, wird automatisch eine neue Resource.SPRACHE.dll erstellt. Das garantiert ein schnelles Implementieren weiterer Sprachen durch den Entwickler¹. Die verschiedenen Sprachdateien haben aber nun den Nachteil das die entsprechenden Variable redundant vorliegt, was auch ein Erweitern der jeweiligen Sprache beim späteren Hinzufügen einer neuen Variable erschwert.

Technische Umsetzung

Benötigte Tools:

  • ResXFileCodeGeneratorEx – http://dmytro.kryvko.googlepages.com/
  • Klasse CultureResources.cs – http://www.codeproject.com/KB/WPF/WPFLocalize.aspx

Vorbereitung / Installation

  • Order Cultures in der Solution anlegen
  • Die Klasse CultureResources.cs² anlegen
  • Die Resources.resx aus Propertys nach Cultures verschieben, Achtung! Der Namensraum der CodeBehind muss hier aber weiter auf „Properties“ zeigen.
  • Die Settings.settings um “DefaultCulture” vom Typ “System.Globalization.Culture” erweitern (Die app.config wird dabei automatisch aktualisiert). Hier kann als Wert die Defaultsprache eingestellt werden, zB. en-US
  • Erstellen einer ResourceDictionary in Cultures mit den Namen ResourceDictionary.xaml mit folgendem Inhalt:
1
2
<ObjectDataProvider x:Key="Resources" ObjectType="{x:Type Cultures:CultureResources}"  MethodName="GetResourceInstance"/>
<ObjectDataProvider x:Key="CultureResourcesDS" ObjectType="{x:Type Cultures:CultureResources}"/>

Die ResourceDictionary.xaml in der App.xaml bekannt machen () / ggfls. Mergen

In der View können nun die Inhalte welche übersetzt werden sollen um folgenden Syntax erweitert werden: {Binding Path=HIER.DER.RESOURCE.NAME, Source={StaticResource Resources}}

Beispiel View:

1
<MenuItem Header="{Binding Path=MenuItemEdit, Source={StaticResource Resources}}">

Es empfiehlt sich die Anwendung soweit vorzubereiten und innerhalb einer Resource die Sprachvariablen festzulegen und dann erst diese zu duplizieren und in der jeweiligen Sprache zu übersetzen. Ein späteres Hinzufügen von neuen Variablen ist recht aufwändig, da dieses in jeder erstellten Sprache ausgeführt werden muss. Die Möglichkeit dieses mit der Baml.exe, welche die DLL in *.csv Dateien parsen kann ist zwar gegeben, jedoch bleibt es nicht aus, alle Sprachdateien für sich anzupassen. Auch Bilder, Datei u.a. können über Resources gesteuert werden

——————–

¹Um eine Sprache hinzuzufügen, genügt es eine vorhandene Resources zu duplizieren und anzupassen. Die Resourcedatei muss in den Buildeigenschaften (“Benutzerdefiniertes Tool”) um den Suffix “Ex” (ResXFileCodeGeneratorEx) erweitert werden. Vorher muss vom Entwickler das Tool ResXFileCodeGeneratorEx heruntergeladen und installiert werden (siehe benötigte Tools – ResXFileCodeGeneratorEx)

²Die Klasse Culture beinhaltet Methoden, welche die vorhandenen Resources findet und in einer Liste bereitstellt.

Nachteile von Resources

Resources haben den entscheidenden Nachteil, dass hier während der Laufzeit keine neuen „Sprachvariablen“ hinzugefügt werden können, da dieses ein Neukompilieren voraussetzt, um die entsprechende *.dll zu erzeugen. Des Weiteren ist das Anpassen aller implementierten Resources bei neuen Sprachvariablen (notwendige Schritte: exportieren, anpassen, importieren, kompilieren) umständlich.

Resources sind Redundant zu den darunterliegenden Ebenen, das heißt, dass die Variablen in jeder Sprach.dll sich wiederholen, so entsteht auch die Gefahr von nicht vorhandenen oder „falsch“ geschriebenen Sprachenvariablen.

Diese Implementierung ist in dieser Weise Plattformabhängig und so auch proprietär an Windowssysteme bzw. an .NET Plattformen gebunden.


Lösungsansatz XML Files

Wenn Ressource-Files wegfallen, liegen “normale” XML-Files nahe. Alle Strings werden in ein zentrales XML-File geladen.
Der Zugriff auf die Strings funktioniert über eine zentrale Klasse, welche die ZugriffsLogik kapseln soll.

Anforderungen:

  • 1-n Sprachen werden unterstützt (muss soweit skalierbar sein, dass neue Sprachen jederzeit hinzugefügt werden können,  ohne dass die Applikation angepasst werden, oder neu kompiliert muss).
  • Die Strings müssen in verschiedene Bereiche (Regions) unterteilt werden können – sonst verliert man schnell den Überblick über die zu verwalteten Strings.
  • Eine StringVariable soll einmalig sein
  • Das Source-File sollte “Notepad-kompatibel” sein
  • Modularität muss unterstützt werden
  • Muss von allen Systemen unterstützt werden
  • Möglichkeit einfacher Übersetzung durch Outsourcing

Exemplarischer Aufbau einer XML Struktur:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Region name="TopMenu">
	<MenuItemEdit>
		<de-DE>Bearbeiten</de-DE>
		<en-US>Edit</en-US>
		<nl-NL>Bewerken</nl-NL>
		<fr-FR>Modifier</fr-FR>
	</MenuItemEdit>
</RegionName>
<Region name="OutlookBar">
	<NavigationPaneMinimizedText>
		<de-DE>Arbeitsplatz</de-DE>
		<en-US>Workplace</en-US>
		<nl-NL>Werkplek</nl-NL>
		<fr-FR>Poste de Travail</fr-FR>
	</NavigationPaneMinimizedText>
</Region>

Die Unterteilung in die Region-Nodes ermöglicht es, eine gewisse Struktur in das XML-File zu bringen. So behält man auch bei hunderten von Texten den Überblick.

Die Sprachen können frei ergänzt werden, die Bezeichnung der Sprach-Nodes (hier de-DE, en-US, nl-NL, fr-FR) ist dabei egal, da dessen Bezeichnung dann einfach als Parameter mitgegeben wird.
In einer dynamischen Anwendungslandschaft, in deren der Anwender eigene Elemente oder Controls anlegen kann, ist dieses System einzusetzen. Hier kann durch eine Serializierung neuer „Vokabeln“ sichergestellt werden, das die Übersetzung des jeweiligen Elements zur Laufzeit geschehen kann.

Umsetzung in GUI:

Als Datenspeicher für die Daten bieten sich durch das Key / Value – Verfahren Dictionary-Objekte an.
Jede Hierarchie-Stufe (Key, Value) wird in ein eigenes Dictionary-Objekt gespeichert – und diese werden dann entsprechend verschachtelt.

Zwei einfache Methoden für die Realisierung:

  • LoadSourceFile(“language.xml”) Wird nur dazu verwendet, das XML-File in eine XmlDocument-Instanz zu laden, die dann anschliessend in der GetTranslatedDictionaryFromXML verarbeitet wird.
  • GetTranslatedDictionaryFromXML() wird aufgerufen wenn die Standard-Spracheinstellungen genutzt werden. Diese ruft die im folgenden beschriebene Methode mit der default-Spracheinstellung auf und gibt das Dictionary zurück.
  • Die eigentliche Methode GetTranslatedDictionaryFromXML(String language) iteriert die XML und speichert im Key/Value Verfahren die Werte im Dictionary und gibt dieses zurück.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#region Method: GetTranslatedDictionaryFromXML
    public static Dictionary<String, String> GetTranslatedDictionaryFromXML(String language)
    {
      var xmlDoc = LoadSourceFile(SourceFile);
      var translatedDictionary = new Dictionary<String, String>();
 
      try
      {
        foreach (XmlNode regionNode in xmlDoc.SelectNodes(XML_ROOTNODE))
        {
          foreach (XmlNode keyNode in regionNode.ChildNodes)
          {
            foreach (XmlNode value in keyNode.ChildNodes)
            {
              if (language == value.Name)
              {
                Value = value.InnerText;
              }
            }
            Key = keyNode.Attributes["key"].Value;
            translatedDictionary.Add(Key, Value);
          }
        }
      }
      catch (Exception ex)
      {
        throw new Exception("Error - " + SourceFile + ": " + ex.Message);
      }
      return translatedDictionary;
    }
    public static Dictionary<String, String> GetTranslatedDictionaryFromXML()
    {
      return GetTranslatedDictionaryFromXML(DefaultLanguage);
    }
    #endregion

Hierbei werden beide Methoden zur Verfügung stehen, somit kann optional die gewünschte Sprache mit übergeben werden, anders verwendet die Methode die definierte Default-Sprache.

[Die Besonderheit in diesem Verfahren ist, dass es möglich ist die angezeigten Sprachen zu „mischen“, so könnten Kundenwünsche in der Art „Ich hätte gerne die Inhalte in Englisch, aber die Menüstruktur auf Holländisch“, umgesetzt werden.]

MVVM

In der View wird das Element wie folgt gebunden, diese Methode kann auf alle Elemente abgebildet weredn:

1
2
<MenuItem Header="{Binding Translation[MenuItemEdit]}">
<Image ToolTip="{Binding Translation[LogoToolTip]}" Source=“…“>

Im ViewModel, wird in das jeweilige ViewModelProperty das Dictionary gespeichert:

1
2
3
Translation = TranslateXml.GetTranslatedDictionaryFromXML();
TranslationFR = TranslateXml.GetTranslatedDictionaryFromXML(“fr-FR“);
TranslationNL = TranslateXml.GetTranslatedDictionaryFromXML(“nl-NL“);

Fazit

Der 2. Lösungsansatz ist dem Ersten vorzuziehen, da hier die Vorteile klar auf der Hand liegen. Es wird gewährleistet, das betriebsystemunabhängige Systeme, gleichermaßen eine einzige LanguageSource bereitsteht, die von allen Systemarchitekturen gelesen werden kann.
Dieses Konzept kann ohne Probleme auch auf andere Datenquellen angewendet werden.

Sprachvariablen liegen in einer XML einmalig vor, so wird verhindert das man sich in einer anderen Sprache verschreibt, oder vergisst diese hinzuzufügen. Weiterhin können XML Dateien als Services bereitgestellt und in Workflows mit eingebunden und ggfl. angepasst oder verändert werden.
Eine Erweiterbarkeit der Sprachdateien während der Laufzeit wird ebenfalls durch diese Lösung gewährleistet. Vorteile dieses Ansatzes liegen sicherlich auch in der einfachen Handhabung des XML-Files.

Es werden keine speziellen Programme oder User-Interfaces benötigt, um die Strings anzupassen, z.B. kann die XML nach Excel exportiert werden und an einem Übersetzer “outgesourced” werden.

Im Endeffekt muss verglichen werden, wie weit man seine Entwicklung modular aufbauen möchte und wie weit man eine Skalierung des System gewährleisten will. Die Entwicklung zeigt aber, dass es wichtig ist, eine Anwendungslandschaft zu entwickeln, die in klaren Schichten voneinander getrennt ist um so Business und GUI vollkommen voneinander zu trennen. Wenn es später heißt, lasst uns die Anwendung auch auf dem IPhone, Smartphone oder mittels Website in PHP/ASP/Silverlight abbilden, spielen solche Entscheidungen eine gravierende Rolle.

by Mario Binder

Suche im Firefox um Eigene erweitern

Seit neuestem kann man sich ja die Suche auf Biggle.de in den Firefox integrieren. Für den einen oder anderen sicher uninteressant, sich gerade mein Blog in den Firefox zu packen. Aber wie man das macht, kann sicher interessant sein : )

suchenimfirefox

Wie folgt geht man dabei vor:

1. Schritt:
Man legt sich eine *.xml auf dem Space und benennt diese nach seiner Wahl. In meinem Fall nenn ich diese “openSearch.xml”.

2.Schritt:
Der Inhalt der xml, sieht wie folgt aus:


<?xml version="1.0" encoding="UTF-8" ?>
 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
  <ShortName>biggle.de</ShortName>
  <Description>Biggle.de</Description>
  <Image>src="http://www.biggle.de/favicon.ico"</Image>
  <Url type="text/html" template="http://www.biggle.de/blog/?s={searchTerms}" />
 </OpenSearchDescription>

Wichtig ist hierbei, die URL so anzupassen, das der jeweilige SuchString angegeben werden muss. In meinem Fall:


http://www.biggle.de/blog/?s=

Man kann damit diese Suche für den Firefox, nahezu für jede Webseite erstellen, die bei der Suche, der URL die jeweiligen Parameter anhängen.

Das Favicon sollte sich auch auf dem Space befinden.

Erstellen kann man sich so ein Favicon bereits recht easy (Photoshop Plugin) und das sogar online.

suchenimfirefox21

3.Schritt:
Dem Browser bekannt machen:
Dazu folgende Anweisung in den Headerbereich setzen.

1
<link title="Suche auf Biggle.de" type="application/opensearchdescription+xml" rel="search" href="http://www.biggle.de/blog/openSearch.xml" />

4.Schritt:
Zu guter letzt, wird der entsprechende Link gesetzt:

javascript:window.external.AddSearchProvider('http://www.biggle.de/blog/openSearch.xml');

Biggle-Suche im FireFox

Viel Spass beim nachmachen : )

by Mario Binder

WP – RSS Syndication sortiert nach Kategorien

Ein paar Artikel habe ich ja schon geschrieben in meinem Blog und erst mit der Zeit kristalisiert sich heraus, welche Kategorien man wirklich benutzt und heute habe ich die vorerst wahllos erstellten Kategorien, einfach mal eine Struktur gegeben.

Ich dachte auch, oh man jeden Artikel editieren das kann dauern, jedoch ist mir dazu ein wunderbares WordPress Plugin in die Hände gefallen, welches ich euch nicht vorenthalten möchte.

Es nennt sich Admin Management Xtended, unterstützt durch Ajax, ermöglicht dieses ein unkompliziertes bearbeiten von Kategorien und Tags . Einfach mal das Videorial dazu anschaun.

Ausserdem kann man, wenn man möchte nun auch einzeln die Kategorien hier im Blog als RSS abonnieren. Dazu einfach oben in der Adressleiste, das RSS Symbol wählen, welches ein Kontexmenü mit den entsprechenden Kategorien öffnet.


Wenn Du in Deinem Blog dieses Kontextmenü benutzen möchtest, erweitere Deinen Header Bereich pro Kategorie um folgende Zeile(n):


1
<link rel="alternate" type="application/atom+xml" title="Photoshop-Themen" href="/blog/?cat=32&amp;feed=rss2" />

Editieren solltest Du den title=”” und natürlich die entsprechende ?cat=

Wenn Du die verschiedenen RSS Spezifikationen anbieten möchtest, dann jeweils durch die Typen ergänzen, dazu stehen derzeit folgende Syndication bereit:

RSS 2.0 -> type=”application/rss+xml”

RSS 0.92 -> type=”text/xml”

Atom 0.3 -> type=”application/atom+xml”