get the solution

Blog

Seite: 1


Martin Martin
10.12.2009 12:03

Filter Data mit MVVM (Filtering ListView, ListBox mit MVVM)



In diesem Eintrag zeige ich, wie man in WPF Daten (IList<T>) mit dem MVVM Pattern filtern kann.
Ich habe darauf geachtet, dass man möglichst individuell bestimmen kann wie Daten gefiltert werden können. Natürlich gibt es noch andere Möglichkeiten Listen zu filtern (CollectionView) die ihre Vor- und Nachteile gegenüber dieser Methode haben. Seht selbst ob sie euch gefällt.

Im Prinzip ist dieser Lösungsvorschlag relativ einfach zu realisieren. Im ViewModel hat man wie üblich die Liste mit den Daten auf die eine ListBox, ListView... bindet. Wenn wir nun aber die Liste filtern wollen, benötigen wir zusätzlich eine weitere Liste in dem nur die gefilterten Daten gespeichert sind. Das Control zum Anzeigen der Daten bindet nun auf die Gefilterte Liste mit den Daten und nicht mehr auf die orginal Liste. Die gefilterte Liste macht folgendes.
 
Sie führt eine Linq Abfrage auf die Original Liste aus. Zu dieser Linq Abfrage fügt man sämtliche Bedinungen ein die dem Filter entsprechen. Schlussendlich wird aus der Linq Abfrage die gefilterte Liste erstellt, welche dann eine ListBox, ListView… anzeigt.

Um diese Vorgehensweise besser veranschaulichen zu können habe ich ein Beispiel erstellt. In diesem Beispiel sollen sämtliche Produkte eines Geschäftes angezeigt werden. Damit der Kunde eine bessere Übersicht über die Produkte hat, soll man einen Filter hinzufügen.
 
Einmal soll der Kunde nach Produkten suchen können und diese auch nach dem Preis und dem Attribut Ausverkauft filtern können.
Unser Produkt hat die Eigenschaften Name, Preis, Ausverkauft.
 
In Unserem ViewModel haben wir einmal die Liste mit den Orginal Daten (ProduktList) und eine Liste welche nur die Gefilterten Daten zurück gibt (FilteredList). Zusätzlich haben wir noch pro Filter eine Propertie die den Filter representiert. Also FilterAusverkauft, FilterName zum nach Produkten suchen zu können und FilterPreis um nur Produkte mit einem bestimmten Preis an zu zeigen.

 
ref

 
Zuerst der XAML Code. Die Oberfläche zeigt einmal eine ListView an, in der die gefilterten Produkte je nach Filter Einstellung angezeigt werden. Die ListView bindet auf die Gefilterte Liste vom ViewModel. Zusätzlich hat der Benutzer die Möglickeit die Filter mittels TextBox, CheckBox zu setzen.

	            <GroupBox Header="Filter" DockPanel.Dock="Top">
	                <StackPanel Orientation="Horizontal">
	                    <!-- Hier kann man die Filter aktivieren -->
	                    <TextBlock Text="Name" VerticalAlignment="Top" />
	                    <TextBox Text="{Binding Path=FilterName,UpdateSourceTrigger=PropertyChanged}"/>
	                    <TextBlock Text="Preis" VerticalAlignment="Top" />
	                    <TextBox Text="{Binding Path=FilterPreis,UpdateSourceTrigger=PropertyChanged}"/>
	                    <TextBlock Text="Ausverkauft" VerticalAlignment="Top" />
	                    <CheckBox IsChecked ="{Binding Path=FilterAusverkauft,UpdateSourceTrigger=PropertyChanged}"/>
	                </StackPanel>
	            </GroupBox>
	            <!-- Daten anzeigen -->
	            <ListView ItemsSource="{Binding Path=FilteredList,UpdateSourceTrigger=PropertyChanged}" DockPanel.Dock="Top">
	                <ListView.View>
	                    <GridView>
	                        <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>
	                        <GridViewColumn Header="Preis" DisplayMemberBinding="{Binding Preis}"></GridViewColumn>
	                        <GridViewColumn Header="Ausverkauft" DisplayMemberBinding="{Binding Ausverkauft}"></GridViewColumn>
	                    </GridView>
	                </ListView.View>
	            </ListView>

Wichtig ist, dass man bei den Filter Properties beim Binding ein UpdateSourceTrigger=PropertyChanged an gibt. Dann werden sämtliche aktualisierungen sofort weiter gegeben. Wenn jetzt jemand beim Filter Name einen Buchstaben eingibt, wird sofort die GefilterteListe erneuert.

	        public string FilterName
	        {
	            get
	            {
	                return _FilterName;
	            }
	            set
	            {
	                _FilterName = value;
	                NotifyPropertyChanged("FilterName");
	                NotifyPropertyChanged("FilteredList");
	            }
	        }
 
Beim Propertie FilterName wird zusätzlich die Methode NotifyPropertyChanged("FilteredList") ausgeführt. Was zur Folge hat, dass die ListView sich nochmal die Daten von der Propertie FiltedList holt.

	        public ObservableCollection<Produkt> FilteredList
	        {
	            get
	            {
	                return GetFilteredList(ProduktList);
	            }
	        }

Dabei wird unsere GetFiltedList Methode aufgerufen. Dieser übergeben wir die orginal Liste. Diese führt die Linq Abfrage aus und erstellt uns eine neue gefilterte Liste.

	        private ObservableCollection<Produkt> GetFilteredList(ObservableCollection<Produkt> pOrginalProduktList)
	        {
	            ObservableCollection<Produkt> filteredproduktlist = new ObservableCollection<Produkt>();
	 
	            var x = from p in pOrginalProduktList
	                    where
	                        FilterNameMethod(p, this.FilterName) &&
	                        FilterAusverkauftMethod(p, this.FilterAusverkauft) &&
	                        FilterPreisMethod(p, this.FilterPreis)
	                    select p;
	 
	            foreach (var u in x)
	                filteredproduktlist.Add(u);
	 
	            return filteredproduktlist;
	        }

Der Linq Abfrage übergeben wir unsere Bedingungs Methoden FilterNameMethode, FilterAusverkauftMethode, FilterPreisMethode. In dieser können wir selbst bestimmen wie wir was überprüfen. Schauen wir uns dazu die FilterNameMethode etwas genauer an.

	        public static bool FilterNameMethod(Produkt pProdukt, string pName)
	        {
	            if (pProdukt == null) return false;
	            if (pName == null) return false;
	            if (pName.Equals(string.Empty)) return true;
	            return (pProdukt.Name.ToUpper().StartsWith(pName.ToUpper()));
	        }

Die Bedinungsmethode muss immer einen Boolean zurückgeben. Sinnvoll ist es auch, das gerade itterierende Item der Linq Abfrage der Methode zu übergen. Als zweiten Parameter übergeben wir die FilterName Propertie. Damit können wir die Linq Abfrage beeinflussen.

In der dritten if Bedinung wird überprüft ob pName (=FilterName) leer ist. Wir geben hier true zurück. Das Element soll also in die gefilterte Liste hinzugefügt werden, da der Filter nicht aktiv scheint. Erst wenn jemand in die Propertie Filtername bzw. in pName etwas hineinschreibt soll je nach dem, wenn der Anfangsbuchstabe passt, das gerade itterierende Item zur gefilterten Liste hinzugefügt werden.

Die Vorgangsweise bei den anderen Filter Methoden ist die gleiche. Statt einer String Überprüfung wird ein int oder bool Wert geprüft. Soll das Item in die gefilterte Liste aufgenommen werden gibt die Filter Methode true zurück andern falls false.

Will man ein Element Manipulieren, löschen, duplizieren etc. muss man die Änderungen immer an der Orginal Liste durchführen. Wichig dabei ist, dass nach der Änderung ein NotifyPropertyChanged("FilterList") ausgeführt wird, damit die FiltedList neu erstellt wird.

Ich denke, dass man dieses Filter Prinzip relativ einfach umsetzen kann. Bei unklarheiten, ergänzungen schreibt mir ein Comment. Das gesamte Projekt kann vom Anhang heruntergeladen werden.



Beispielprojekt Download:

 

kick it on dotnet-kicks.de

Schlüsselwörter: WPF, XAML, LINQ
zuletzt geändert: 11. Dezember 2009 16:35
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen




Simon Simon
19.11.2009 19:24

Probleme mit php-gettext



Vor kurzem wurde bei einem unserer Hoster eine PHP-Aktualisierung vorgenommen. Diese führte dazu, dass auf sämtlichen Webseiten nur noch die englische Sprache angezeigt wurde. Der Grund dafür war eine Änderungen bezüglich der Byteorder in PHP.
Die von uns eingesetzte Übersetzungs-Engine php-gettext hatte noch keinen Patch für diesen "Fehler".

Um den Fehler zu beheben muss man in der Datei gettext.php Zeile 117 den Code von:

$this->error = 1; // not MO file
	
auf

$this->BYTEORDER = 0; /* change this line */
	
abändern.

Das Code-Fragment sieht dann so aus:

	    $this->STREAM = $Reader;
	    $magic = $this->readint();
	    if ($magic == $MAGIC1) {
	      $this->BYTEORDER = 0;
	    } elseif ($magic == $MAGIC2) {
	      $this->BYTEORDER = 1;
	    } else {
	      $this->BYTEORDER = 0; /* change this line */ 
	      return false;
	    }
	
Homepage des Projekts: http://savannah.nongnu.org/projects/php-gettext/

Bugreport auf Savannah



Schlüsselwörter: php-gettext, no translations, PHP 5.2.11
zuletzt geändert: 19. November 2009 23:28
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen




Martin Martin
22.10.2009 16:02

ColorAnimation im XAML vs ColorAnimation im C# Code



Vorgeschichte:

CoffeClock ist ein Erinnerungsprogram. Wenn man gerade etwas auf dem Herd hat, und nebenbei am Computer sitzt kommt es schnell vor, dass man alles um sich herum vergisst. Bei mir ist das jedenfalls so. So kam es schon öfter vor, dass einige Leckerein verbrannten. Um dieses Problem vor zu beugen habe ich dieses Erinnerungsprogramm geschrieben.

Man gibt den Erinnerungstext und die Zeit ein, wann man an etwas Erinnert werden soll. Danach versteckt sich das Programm und wartet im Hintergrund bis die Zeit abgelaufen ist. Wenn die Zeit abgelaufen ist wird der Komplette Bilschirm schwarz und der Erinnerungstext wird weiß eingeblendet.

Wenn man die ESC Taste drückt, soll das Erinnerungsprogramm beendet werden und man kehrt zum Desktop zurück.

Die Animation:

Jetzt wär es cool wenn man das Zurückkehren zum Desktop mittels einer Animation machen könnte. Von Schwarz überlaufend in den Desktop (bzw. von Schwarz in Transparent).

Animationen habe ich bis jetzt immer im XAML erstellt. Also wollte ich das auch in diesem Fall so machen. Das hätte dann so ausgesehen:

<Window.Triggers>
	        <EventTrigger RoutedEvent="Window.PreviewKeyDown">
	            <BeginStoryboard>
	                <Storyboard>
	                    <ColorAnimation Storyboard.TargetProperty="Background.Color" 
	From="Black" To="Transparent" Duration="00:00:01.000" />
	                </Storyboard>
	            </BeginStoryboard>
	        </EventTrigger>
	    </Window.Triggers>

Tja, leider kam mir keine Idee, wie ich im XAML abfragen kann ob der Benutzer die ESC Taste gedrückt hat. Hinzu kommt, dass ich nach der Animation das Programm beenden muss. In XAML wäre das wahrscheinlich ebenso schwer bis gar unmöglich zu realisieren.

Also musste ich die Animation doch im C# Code erstellen.

Mein erster geh versuch sah so aus (der C# Animationscode sollte equivalent zum XAML Code sein):

	            ColorAnimation colorAnimation = new ColorAnimation();
	            colorAnimation.Duration = new Duration(new TimeSpan(0, 0, 10));
	            colorAnimation.From = Colors.Black;
	            colorAnimation.To = Colors.Transparent;
	 
	            this.BeginAnimation(Window.BackgroundProperty, colorAnimation);
Mit diesem Code erhielt ich die Exception:

"AnimationTimeline" vom Typ "System.Windows.Media.Animation.ColorAnimation" kann nicht zum Animieren der Background-Eigenschaft vom Typ "System.Windows.Media.Brush" verwendet werden.
Parametername: animation

Beim näheren betrachten sieht man auch, dass man im XAML als TargetPropertie Background.Color angegeben hat. Leider kann man das im C# Code so nicht setzen, da man mit Window.BackgroundProperty nur auf die Properties des DependencyProperties zugreifen kann.

Die Lösung ist eine Referenz auf den Background Brush. Das sieht dann so aus:

	            Brush backgroundBrush = this.Background;
	            backgroundBrush.BeginAnimation(SolidColorBrush.ColorProperty, colorAnimation);
Nach der Animation habe ich dann den Befehl this.Close()  ausgeführt.

Jetzt stand ich vor dem nächsten Problem. Die Animation wird asynchron ausgeführt. Das heißt man sieht die Animation gar nicht, da sofort das Fenster geschlossen wird.

Zum glück hat die Klasse ColorAnimation ein Event Completed welches, wie der Name bereits verrät, gefeuert wird, wenn die Animation fertig ist.

Ich habe den Close() Befehl also in das Event Completed gesteckt. Nun wird, nach dem drücken der Taste ESC zuerst die Animation abgespielt und zu guter letzt das Programm beendet.

Der komplette und richtige Code sieht so aus:

this.PreviewKeyDown += (sender, e) =>
	            {
	                if (e.Key.Equals(Key.Escape))
	                {
	                    ColorAnimation ani = new ColorAnimation(Colors.Transparent, new Duration(new TimeSpan(0, 0, 0, 1, 0)));
	                    ani.Completed += (Completedsender, Completede) =>
	                    {
	                        this.Close();
	                    };
	 
	                    SolidColorBrush newBrush = new SolidColorBrush(Colors.Black);
	 
	                    this.Background = newBrush;
	 
	                    newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);
	                }
	            };
Ein großes Dank geht an talla vom mycsharp Forum der mir bei meinen Animationsproblemen weiter geholfen hat.

Links:
http://msdn.microsoft.com/de-de/library/system.windows.media.animation.coloranimation.aspx

Das Erinnerungsprogramm CoffeeClock können Sie hier herunterladen ( benötigt .NET 3.5 7Kb groß):

Zum Download

Schlüsselwörter: XAML, WPF, Get
zuletzt geändert: 22. Oktober 2009 21:32
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen




Simon Simon
21.09.2009 17:22

Rezepteprogramm fertiggestellt



Ich habe nun das Rezepteprogramm für die PTS Feldkirch fertiggestellt. Es enthält folgende Features:
  • Bearbeiten, Löschen und Hinzufügen von Rezepten
  • Bearbeiten, Löschen und Hinzufügen von Zutatenvorlagen
  • Ausdrucken von Rezepten
Verwendet wurde das .NET Framework 3.5 mit WPF.



Klicken Sie hier um zur Rezeptseite zu gelangen und das Programm herunter laden zu können.

Simon

Schlüsselwörter: get
zuletzt geändert: 10. Oktober 2009 19:22
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen




Martin Martin
02.09.2009 12:39

MarkupExtension - Bilder aus Resource direkt ins XAML laden



Vor kurzem habe ich eine MarkupExtension geschrieben, die aus einer Resource-Datei (resx.) Bilder lädt. Eine MarkupExtension zu schreiben ist nicht schwer. Man erbt von der Abstrakten Klasse MarkupExtension und muss die zwei Konstruktoren (einmal mit Parameter) implementieren und die Methode ProvideValue überschreiben.

1.0 Einführung - Eine einfache MarkupExtension

Im zweiten Konstruktor der wie folgt aussieht....

public ImageResourceConverterExtension(string pkey) : this()
{
  //Wert Propertie Key zuweisen
  Key = pkey;
}

....steht im Parameter der MarkupExtension Key.

D.h. Wenn im XAML in der MarkupExtension STRING

<TextBlock Text="{GetCommon:MeineMarkupExtension STRING}"/>

steht, erhält man diesen Wert "STRING" über den Konstruktor Parameter pkey. Diesen kann man dann, interpretieren parsen und vieles mehr .

In unserem Beispiel geben wir den Key einfach wieder als string an die Propertie zurück an der die MarkupExtension angewandt wurde.

Die Funktion ProvideValue weist ein object der Propertie zu, an der die MarkupExtension gesetzt wurde. Z.B

public override object ProvideValue(IServiceProvider pserviceProvider)
{
  //Propertie in der wir unseren Key "STRING" gespeichert haben
  return Key;
}


Das heißt im TextBlock Text steht dann, wenn das Fenster geladen wurde, STRING.

1.1 ImageResourceConverter MarkupExtension

So und jetzt meine MarkupExtension die Bilder aus einer Resource.resx lädt.

Achtung: die MarkupExtension funktioniert nur mit PNG Bilder!

Verwendung wie folgt:

<Image Source="{GetCommon:ImageResourceConverter Get.Common.Resource.Image1}"/>

Syntax: Namespace kann auch der ProjektName sein.ResourceName.Name des Bildes auf die man zugreifen will.
Gibt man hinter dem Namespace.ResourceName.BildName kein @ an nimmt die MarkupExtension an, dass die Resource sich in der aktuellen Instanz befindet.

<Image Source="{GetCommon:ImageResourceConverter Get.Demo.Resource.Image1@Get.Demo.exe}" Width="20"/>

Syntax: Namespace.ResourceName.BildName@DLLName in der sich die Resource befindet
Die Dll wird im Verzeichnis Environment.CurrentDirectory gesucht.

Hier der Code der MarkupExtension:

[MarkupExtensionReturnType(typeof(ImageSource)), Localizability(LocalizationCategory.NeverLocalize)]
    public class ImageResourceConverterExtension : MarkupExtension
    {
        #region Members
        /// <summary>
        /// Seperator zwischen Namespace und Resource
        /// </summary>
        private const char _point = '.';

        /// <summary>
        /// Seperator zwischen ProjektName.ResourceName.Name und DLLName
        /// </summary>
        private const char _ExternAssemblySpliter = '@';
        #endregion

        #region Konstruktor
        /// <summary>
        /// Konstruktor - Initialisiert die Properties
        /// </summary>
        public ImageResourceConverterExtension()
        {
            ImageName = string.Empty;
            BaseName = string.Empty;
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="pkey">Key der bei der MarkupExtension angegeben wurde.
        ///  Z.B. "Get.Demo.Resource.Image1@Get.Demo.exe"</param>
        public ImageResourceConverterExtension(string pkey)
            : this()
        {
            Key = pkey;

            //Prüfen ob die Resource-Datei aus einer fremden Assembly geladen werden soll
            if (!pkey.Contains(_ExternAssemblySpliter))
            {
                //Properties ImageName und BaseName setzen
                SetImageNameAndBaseName(pkey);
            }
            else
            {
                //Fremde Assembly laden - Dateiname extrahieren
                string filename = pkey.Split(_ExternAssemblySpliter).Last();
                FileInfo fileInfo = new FileInfo(Environment.CurrentDirectory + "" + filename);

                if (!fileInfo.Exists)
                    throw new FileNotFoundException();

                //Assembly laden
                Assembly assembly = Assembly.LoadFrom(fileInfo.ToString());
                ResourceAssembly = assembly;

                SetImageNameAndBaseName(pkey.Replace(_ExternAssemblySpliter.ToString()
                + filename, string.Empty));
            }

        }
        #endregion

        #region Funktionen
        /// <summary>
        /// Extrahiert die Werte aus dem übergeben Key und weist sie den jeweiligen Properties zu.
        /// </summary>
        /// <param name="pkey">MarkupExtension Key Z.B. "Get.Demo.Resource.Image1@Get.Demo.exe"</param>
        private void SetImageNameAndBaseName(string pkey)
        {
            //Letzter Value ist der BildName
            ImageName = pkey.Split(_point).Last();
            //Der Rest besteht aus Namespace.ResourceName
            BaseName = pkey.Replace(_point.ToString() + ImageName, string.Empty);
        }
        /// <summary>
        /// Gibt ein ImageSource Objekt zurück an der die MarkupExtension gesetzt wurde.
        /// </summary>
        /// <param name="pserviceProvider">Objekt, das Dienste für die
        /// Markuperweiterung bereitstellen kann.</param>
        /// <returns>ImageSource Objekt</returns>
        public override object ProvideValue(IServiceProvider pserviceProvider)
        {
            //Bild aus Resource laden
            if (ResourceAssembly == null)
                return GetBitmapImageFromResource(BaseName, ImageName, this.GetType().Assembly);
            else
                return GetBitmapImageFromResource(BaseName, ImageName, ResourceAssembly);
        }
        /// <summary>
        /// Ladet mithilfe der übergebenen Informationen ein Bild aus der
        /// Resource (.resx) und konvertiert es in ein ImageSource Objekt
        /// </summary>
        /// <param name="pBaseName"></param>
        /// <param name="pImageName"></param>
        /// <param name="pAssembly"></param>
        /// <returns></returns>
        public static ImageSource GetBitmapImageFromResource(string pBaseName,
        string pImageName, Assembly pAssembly)
        {
            ResourceManager resourceManager = new ResourceManager(pBaseName, pAssembly);

            //System.Drawing.Bitmap aus Resource holen
            object image = resourceManager.GetObject(pImageName);
            if (image == null) return null;

            if (!image.GetType().Equals(typeof(Bitmap))) return null;

            Bitmap bitmap = image as Bitmap;

            //Bitmap in ImageSource konvertieren
            BitmapImage bitmapImage = new BitmapImage();
            MemoryStream stream = new MemoryStream();

            bitmap.Save(stream, ImageFormat.Png);

            //ImageSource aus Stream holen
            //http://cornucopia30.blogspot.com/2007/08/wpf-point-image-to-embedded-resource.html
            PngBitmapDecoder bitmapDecoder = new PngBitmapDecoder(stream,
            BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
            ImageSource imageSource = bitmapDecoder.Frames[0];
            return imageSource;
        }
        #endregion Funktionen

        #region Properties

        /// <summary>
        /// Gets or sets the resource key.
        /// </summary>
        /// <value>The key.</value>
        [ConstructorArgument("pkey")]
        public string Key { get; private set; }

        /// <summary>
        /// Ruft den Namespace bzw. Projekt-Namen + Resourcename ab oder legt diesen fest.
        /// </summary>
        public string BaseName { get; private set; }

        /// <summary>
        /// Ruft den Wert des Bildnamens ab oder legt diesen fest.
        /// </summary>
        public string ImageName { get; private set; }

        /// <summary>
        /// Ruft die Externe Assembly ab oder legt diese fest.
        /// Diese Propertie ist nur gesetzt wenn man in der MarkupExtension
        /// das @ mit dem DLLNamen festgelegt hat.
        /// </summary>
        public Assembly ResourceAssembly { get; private set; }

        #endregion
    }

Hier eine kurze Beschreibung der MarkupExtension:

Im Key wird angegeben wo sich das Bild in der Resource befindet. Die Resource wird in der Funktion GetBitmapImageFromResource mit dem ResourceManager geladen.

Die Propertie Source der Image Klasse hat den Typ ImageSource. Deshalb muss man das geladene Bitmap in eine ImageSource umwandeln.

Anschließend wird die erzeugte ImageSource in der Funktion ProvideValue zurück gegeben. Diese MarkupExtension habe ich im schnell verfahren geschrieben. Das heißt, dass wichtige Überprüfungen fehlen. Auch habe ich das Ganze nur für .PNG Dateien getestet bzw. Programmiert. Ich überprüfe auch nicht ob man die MarkupExtension bei einem Nicht Image Objekt anwendet.

[Edit:]

Hier noch ein Artikel zur Konvertierung von Bildern (jpg, png, ...) in eine ImageSource.

kick it on dotnet-kicks.de


Andere wichtige und gute MarkupExtensions:

http://tomlev2.wordpress.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/ Ermöglicht Keybindings ohne Codebehind durch Verwendung der MarkupExtension.
Schlüsselwörter: XAML, WPF
zuletzt geändert: 22. September 2009 19:50
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen




Seite: 1



(c) 2003-2009 | Impressum