none
Benachrichtigt werden wenn sich ObservableCollection ändert

    Frage

  • Hallo ich habe ein DataGrid welches mit einer ObservableCollection verbunden ist. Nun möchte ich gern wenn sich die Liste über das Datagrid ändert. Eine Methode ausführen. Nur irgendwie komm ich nicht in das Set. 

    private ObservableCollection<Auto> _lAuto;
    public ObservableCollection<Auto> lAuto
            {
                get
                {
                    return _lAuto;
                }
                set
                {
                    if(_lAuto != value)
                    {
                        _lAuto = value;
                        NotifyPropertyChanged();
                        float p = 0;
                        foreach(Auto a in _lAuto)
                        {
                            p += a.Preis;
                        }
                    }
                }
            }

    <Window.DataContext>
            <main:AutoMain/>
        </Window.DataContext>
    
    <DataGrid ItemsSource="{Binding lAuto, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

    Mittwoch, 11. Juli 2018 12:38

Antworten

  • Hi,

    das Verhalten der ObservableCollection ist normal an der Stelle (ich habe es zumindest noch nie anders festgestellt).

    Das CollectionChanged Event ist ganz nett, aber oft unzureichend weil das Event gefeuert wird, sobald du beginnst in der neuen Zeile im Datagrid zu schreiben.

    Ich nutze für derartige Fälle oft eine Erweiterung der ObservableCollection:

        /// <summary>
        /// Implements the "ItemPropertyChanged" Event for a ObservableCollection
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <seealso cref="System.Collections.ObjectModel.ObservableCollection{T}" />
        public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
        where T : INotifyPropertyChanged
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="TrulyObservableCollection{T}"/> class.
            /// </summary>
            public TrulyObservableCollection()
            {
                CollectionChanged += FullObservableCollectionCollectionChanged;
            }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="TrulyObservableCollection{T}"/> class.
            /// </summary>
            /// <param name="pItems">The p items.</param>
            public TrulyObservableCollection(IEnumerable<T> pItems) : this()
            {
                foreach (var item in pItems)
                {
                    this.Add(item);
                }
            }
    
            /// <summary>
            /// Fulls the observable collection collection changed.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
            private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.NewItems != null)
                {
                    foreach (Object item in e.NewItems)
                    {
                        ((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
                    }
                }
                if (e.OldItems != null)
                {
                    foreach (Object item in e.OldItems)
                    {
                        ((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
                    }
                }
            }
    
            /// <summary>
            /// Items the property changed.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
            private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
                OnCollectionChanged(args);
            }
        }

    Diese habe ich mal hier gefunden.

    Diese dann einfach Deklarieren:

    public TrulyObservableCollection<T> MyCollection { get; set; }

    Und im Constructor:

    MyCollection = new TrulyObservableCollection<T>();
    MyCollection.CollectionChanged += MyCollection_CollectionChanged;
    Das Event sieht dann so aus, welches etwas mehr liefert:

            private void DataG2_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.Action == NotifyCollectionChangedAction.Replace)
                {
                    ;
                }
                else if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    ;
                }
                else if (e.Action == NotifyCollectionChangedAction.Move)
                {
                    ;
                }
                else if (e.Action == NotifyCollectionChangedAction.Remove)
                {
                    ;
                }
                else if (e.Action == NotifyCollectionChangedAction.Reset)
                {
                    ;
                }
            }

    "Replace" wird gefeuert, wenn du neue oder vorhandene Einträge editierst. Wenn du das DataGrid-Binding auf "UpdateSourceTrigger=LostFocus" stellst, wird das Event nur gefeuert, wenn du die Zeile verlässt.

    Gruß

    Stefan


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP

    • Als Antwort markiert MietzeTatze Donnerstag, 12. Juli 2018 12:57
    Mittwoch, 11. Juli 2018 20:06
  • Hi,
    ich kann Dein Problem nicht nachvollziehen. Nachfolgend mal eine kleine Demo:

    XAML:

    <Window x:Class="WpfApp1CS.Window63"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1CS"
            mc:Ignorable="d"
            Title="Window63" Height="450" Width="800">
      <Window.Resources>
        <local:Window63VM x:Key="vm"/>
      </Window.Resources>
      <StackPanel DataContext="{StaticResource vm}">
        <DataGrid ItemsSource="{Binding View, UpdateSourceTrigger=LostFocus}"/>
      </StackPanel>
    </Window>

    Dazu der Code:

    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Data;
    
    namespace WpfApp1CS
    {
      public class Window63VM 
      {
        private CollectionViewSource cvs = new CollectionViewSource();
        public ICollectionView View
        {
          get
          {
            if (cvs.Source == null)
            {
              var col = GetData();
              cvs.Source = col;
              cvs.View.CollectionChanged += (sender, e) =>
              {
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                  var l = cvs.Source as ObservableCollection<Window63Data>;
                  var d = ((ListCollectionView)cvs.View).CurrentAddItem as Window63Data;
                  if (d != null) d.ID = l.Max<Window63Data>(dd => dd.ID) + 1;
                }
              };
            }
            return cvs.View;
          }
        }
    
        private ObservableCollection<Window63Data> GetData()
        {
          ObservableCollection<Window63Data> liste = new ObservableCollection<Window63Data>();
          for (int i = 1; i < 10; i++) liste.Add(new Window63Data() { ID = i, Info = $"Zeile {i}" });
          return liste;
        }
        public class Window63Data : INotifyPropertyChanged
        {
          private int _id = 0;
          public int ID { get { return _id; } set { _id = value; OnPropertyChanged(); } }
          private string _info;
          public string Info { get { return _info; } set { _info = value; OnPropertyChanged(); } }
    
          #region  OnPropertyChanged
          public event PropertyChangedEventHandler PropertyChanged;
          private void OnPropertyChanged([CallerMemberName] string propName = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
          #endregion
        }
      }
    }
    


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Montag, 23. Juli 2018 18:50

Alle Antworten

  • Nur irgendwie komm ich nicht in das Set

    Hallo,

    was genau meinst Du damit?

    Sollte so eigentlich funktionieren, eine Alternative ist ObservableCollection.CollectionChanged Event


    Olaf Helper

    [ Blog] [ Xing] [ MVP]

    Mittwoch, 11. Juli 2018 13:32
  • Dachte ich eigentlich auch nur wenn ich eine neuen Eintrag in das Datagrid mache löst es nicht das set aus. Ich schau mir mal dein Link mit den Event an. Aber vlt fällt jemand anderen noch was ein
    Mittwoch, 11. Juli 2018 14:07
  • Hi,

    das Verhalten der ObservableCollection ist normal an der Stelle (ich habe es zumindest noch nie anders festgestellt).

    Das CollectionChanged Event ist ganz nett, aber oft unzureichend weil das Event gefeuert wird, sobald du beginnst in der neuen Zeile im Datagrid zu schreiben.

    Ich nutze für derartige Fälle oft eine Erweiterung der ObservableCollection:

        /// <summary>
        /// Implements the "ItemPropertyChanged" Event for a ObservableCollection
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <seealso cref="System.Collections.ObjectModel.ObservableCollection{T}" />
        public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
        where T : INotifyPropertyChanged
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="TrulyObservableCollection{T}"/> class.
            /// </summary>
            public TrulyObservableCollection()
            {
                CollectionChanged += FullObservableCollectionCollectionChanged;
            }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="TrulyObservableCollection{T}"/> class.
            /// </summary>
            /// <param name="pItems">The p items.</param>
            public TrulyObservableCollection(IEnumerable<T> pItems) : this()
            {
                foreach (var item in pItems)
                {
                    this.Add(item);
                }
            }
    
            /// <summary>
            /// Fulls the observable collection collection changed.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
            private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.NewItems != null)
                {
                    foreach (Object item in e.NewItems)
                    {
                        ((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
                    }
                }
                if (e.OldItems != null)
                {
                    foreach (Object item in e.OldItems)
                    {
                        ((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
                    }
                }
            }
    
            /// <summary>
            /// Items the property changed.
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
            private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
                OnCollectionChanged(args);
            }
        }

    Diese habe ich mal hier gefunden.

    Diese dann einfach Deklarieren:

    public TrulyObservableCollection<T> MyCollection { get; set; }

    Und im Constructor:

    MyCollection = new TrulyObservableCollection<T>();
    MyCollection.CollectionChanged += MyCollection_CollectionChanged;
    Das Event sieht dann so aus, welches etwas mehr liefert:

            private void DataG2_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.Action == NotifyCollectionChangedAction.Replace)
                {
                    ;
                }
                else if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    ;
                }
                else if (e.Action == NotifyCollectionChangedAction.Move)
                {
                    ;
                }
                else if (e.Action == NotifyCollectionChangedAction.Remove)
                {
                    ;
                }
                else if (e.Action == NotifyCollectionChangedAction.Reset)
                {
                    ;
                }
            }

    "Replace" wird gefeuert, wenn du neue oder vorhandene Einträge editierst. Wenn du das DataGrid-Binding auf "UpdateSourceTrigger=LostFocus" stellst, wird das Event nur gefeuert, wenn du die Zeile verlässt.

    Gruß

    Stefan


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP

    • Als Antwort markiert MietzeTatze Donnerstag, 12. Juli 2018 12:57
    Mittwoch, 11. Juli 2018 20:06
  • Danke das ist echt super 
    Donnerstag, 12. Juli 2018 12:57
  • Hey ich habe es ausprobiert und es klappt super nochmal danke. Aber habe irgendwie ein Problem. Wenn ich Daten ins Datengrid eintrage. Wird kene neue Zeile zum eintragen bereitgestellt. Erst wenn ich das Datengrid nach einer Spalte zum Beispiel sortiere. Gibt es dazu auch eine Lösung?
    Dienstag, 17. Juli 2018 21:40
  • Im Normalfall wird eine neue Zeile bereitgestellt, sobald du die Zeile verlässt, die du gerade neu befüllt hast. Also zum Beispiel mit ENTER bestätigt hast oder durch einen KLICK auf eine andere Zeile...

    Das ist das normale Verhalten.

    Und das funktioniert bei dir nicht so?


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP

    Mittwoch, 18. Juli 2018 05:41
  • Habe das Problem gefunden. Aber nicht die Lösung. Ich möchte immer wenn ich eine neue Zeile hinzufüge eine Positionsnummer hinzufügen. 

    dafür rufe ich bei NotifyCollectionChangedAction.Add 

    die Funktion setPosAuto auf. 

    private void setPosAuto()
            {
                int maxPos = 0;
                foreach (Auto i in lAuto)
                {
                    if (i.Pos > maxPos) maxPos = (int)i.Pos;
                }
                foreach (Auto i in lAuto)
                {
                    if (i.Pos == -1) i.Pos = ++maxPos;
                }
            }
    Das funktionier auch. Mir wird immer in Pos max Position in der Liste gegeben. Problem ist aber wenn ich dies drin hab wird keine neue Zeile automatisch erstellt
    Montag, 23. Juli 2018 16:39
  • Hi,
    ich kann Dein Problem nicht nachvollziehen. Nachfolgend mal eine kleine Demo:

    XAML:

    <Window x:Class="WpfApp1CS.Window63"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1CS"
            mc:Ignorable="d"
            Title="Window63" Height="450" Width="800">
      <Window.Resources>
        <local:Window63VM x:Key="vm"/>
      </Window.Resources>
      <StackPanel DataContext="{StaticResource vm}">
        <DataGrid ItemsSource="{Binding View, UpdateSourceTrigger=LostFocus}"/>
      </StackPanel>
    </Window>

    Dazu der Code:

    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Data;
    
    namespace WpfApp1CS
    {
      public class Window63VM 
      {
        private CollectionViewSource cvs = new CollectionViewSource();
        public ICollectionView View
        {
          get
          {
            if (cvs.Source == null)
            {
              var col = GetData();
              cvs.Source = col;
              cvs.View.CollectionChanged += (sender, e) =>
              {
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                  var l = cvs.Source as ObservableCollection<Window63Data>;
                  var d = ((ListCollectionView)cvs.View).CurrentAddItem as Window63Data;
                  if (d != null) d.ID = l.Max<Window63Data>(dd => dd.ID) + 1;
                }
              };
            }
            return cvs.View;
          }
        }
    
        private ObservableCollection<Window63Data> GetData()
        {
          ObservableCollection<Window63Data> liste = new ObservableCollection<Window63Data>();
          for (int i = 1; i < 10; i++) liste.Add(new Window63Data() { ID = i, Info = $"Zeile {i}" });
          return liste;
        }
        public class Window63Data : INotifyPropertyChanged
        {
          private int _id = 0;
          public int ID { get { return _id; } set { _id = value; OnPropertyChanged(); } }
          private string _info;
          public string Info { get { return _info; } set { _info = value; OnPropertyChanged(); } }
    
          #region  OnPropertyChanged
          public event PropertyChangedEventHandler PropertyChanged;
          private void OnPropertyChanged([CallerMemberName] string propName = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
          #endregion
        }
      }
    }
    


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Montag, 23. Juli 2018 18:50
  • Hi, ich kann dein dein Problem auch nicht nachvollziehen oder ich verstehe es nicht...

    Und auch wenn der Peter Fleischer mal wieder schneller war, ich hatte auch gerade ein kleines Beispiel gebastelt:

    <Window x:Class="Demo3348.Views.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:ViewModel="clr-namespace:Demo3348.Viewmodels"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Window.Resources>
            <ViewModel:MainViewModel x:Key="vm"/>
        </Window.Resources>
        <Grid DataContext="{StaticResource vm}">
            <DataGrid IsReadOnly="False" Margin="20,20,20,20"
                      ItemsSource="{Binding DataCollection, Mode=TwoWay}" 
                      Grid.Column="1" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Name" Binding="{Binding AName}"/>
                    <DataGridTextColumn Header="Position" Binding="{Binding Pos}"/>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows.Data;
    
    namespace Demo3348.Viewmodels
    {
        public class MainViewModel : INotifyPropertyChanged
        {
            public MainViewModel()
            {
                DataCollection = new ObservableCollection<DataModel>();
                
    
                DataCollectionView = CollectionViewSource.GetDefaultView(DataCollection);
                DataCollectionView.CurrentChanged += (o, e) =>
                {
                    var itm = (DataModel)DataCollectionView.CurrentItem;
                    if (itm != null)
                    {
                        if (itm.Pos == 0)
                        {
                            itm.Pos = DataCollection.Max(p => p.Pos) + 1;
                        }
                    }
                };
    
                ReadData();
            }
    
            public ObservableCollection<DataModel> DataCollection { get; set; }
            public ICollectionView DataCollectionView { get; set; }
    
            public void ReadData()
            {
                DataCollection.Add(new DataModel() { AName = "Name 1", Pos = 1 });
                DataCollection.Add(new DataModel() { AName = "Name 2", Pos = 2 });
                DataCollection.Add(new DataModel() { AName = "Name 3", Pos = 3 });
                DataCollection.Add(new DataModel() { AName = "Name 4", Pos = 4 });
                DataCollection.Add(new DataModel() { AName = "Name 5", Pos = 5 });
            }
            
            #region INotifyPropertyChanged
    
            /// <summary>
            /// Occurs when a property value changes
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
    
            /// <summary>
            /// Raise the  <see cref="PropertyChanged"/> event.
            /// </summary>
            /// <param name="propertyName"></param>
            protected void NotifyPropertyChange(string propertyName)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
    
            #endregion
        }
    
        public class DataModel : INotifyPropertyChanged
        {
            private string name = "";
            public string AName
            {
                get { return name; }
                set
                {
                    if (name != value)
                    {
                        name = value;
                        NotifyPropertyChange("AName");
                    }
                }
            }
    
            private int pos = 0;
            public int Pos
            {
                get { return pos; }
                set
                {
                    if (pos != value)
                    {
                        pos = value;
                        NotifyPropertyChange("Pos");
                    }
                }
            }
    
            #region INotifyPropertyChanged
    
            /// <summary>
            /// Occurs when a property value changes
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
    
            /// <summary>
            /// Raise the  <see cref="PropertyChanged"/> event.
            /// </summary>
            /// <param name="propertyName"></param>
            protected void NotifyPropertyChange(string propertyName)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
    
            #endregion
        }
    Ich hoffe ich habe einigermaßen verstanden was du eigentlich vor hast...

    Gruß


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP


    Montag, 23. Juli 2018 19:07
  • Hi Stefan,
    Dein Beispiel funktioniert genau so wie mein Beispiel.

    Ohne die genauen weiteren Hintergründe zu kennen, ist es unmöglich festzulegen, welche Lösung wirklich besser ist.

    Ich finde Deine Lösung aber nicht optimal. Du erzeugst die neue Nummer im CurrentChanged, wenn die Nummer gleich Null ist, was aber ggf. so gewünscht sein kann. Ich erzeuge die Nummer im AddItem der View, d.h. immer nur, wenn ein neues Datenobjekt angelegt wird.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Montag, 23. Juli 2018 19:20
  • Hi Peter und Stefan, 

    echt super für eure Lösung. Hat auf Anhieb funktioniert. Habe mir auch noch mal im Netz paar Artikel zur CollectionViewSource durchgelesen und denke hab es jetzt verstanden und das Programm läuft nun =)

    Zu Vollständigkeit wenn jemand sich das nochmal durchliest poste ich nochmal mein Code

    lAuto = new TrulyObservableCollection<Auto>();
                lAuto.CollectionChanged += lAuto_CollectionChanged;
                DCVAuto = CollectionViewSource.GetDefaultView(lWR);
                DCVAuto.CurrentChanged += (o, e) =>
                {
                    var itm = (Auto)DCVAuto.CurrentItem;
                    if (itm != null)
                    {
                        if (itm.Pos == 0)
                        {
                            itm.Pos = lAuto.Max(p => p.Pos) + 1;
                        }
                    }
                };
    
    private void lAuto_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.Action == NotifyCollectionChangedAction.Replace)
                {
                    calcPrice1();
                }
                else if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    calcPrice1();
                }
            }

    Montag, 23. Juli 2018 19:53

  • Ich finde Deine Lösung aber nicht optimal. Du erzeugst die neue Nummer im CurrentChanged, wenn die Nummer gleich Null ist, was aber ggf. so gewünscht sein kann. Ich erzeuge die Nummer im AddItem der View, d.h. immer nur, wenn ein neues Datenobjekt angelegt wird.

    Hallo Peter, du hast absolut recht. Die Nummer im AddItem zu erzeugen ist viel besser. Und die Abfrage auf 0 ist überflüssig.

    Gruß


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP

    Dienstag, 24. Juli 2018 07:42