none
im DataGrid nach Nutzereingabe Daten neu berechnen und anzeigen? RRS feed

  • Frage

  • Hallo, 

    ich schreibe z.Z. mein 1. WPF-Projekt.

    Ich möchte den Wertverlust über mehrere Jahre in einer Tabelle anzeigen. Der Nutzer kann den Prozentsatz/Jahr verändern. Nach jeder Eingabe soll die Tabelle neu berechnet und angezeigt werden. Mit nachfolgenden Code klappt dies auch:

                       <DataGrid x:Name="DGridWertV"  HorizontalAlignment="Left" Margin="0,103,0,0" Width="230" Grid.Column="3" Height="429" VerticalAlignment="Top" AutoGenerateColumns="False" FrozenColumnCount="1" CellEditEnding="DGridWertV_CellEditEnding" IsSynchronizedWithCurrentItem="True" SelectionUnit="Cell" SelectionMode="Single" >
                            <DataGrid.Columns>
                                <DataGridTextColumn Binding="{Binding Jahr}" ClipboardContentBinding="{x:Null}" Header="Jahr" IsReadOnly="True" CanUserSort="False" CanUserReorder="False" CellStyle="{StaticResource CellAlignCenterInfoColor}" Foreground="Black"/>
                                <DataGridTextColumn Binding="{Binding Prozent}" CanUserReorder="False" ClipboardContentBinding="{x:Null}" Header="Prozent" CellStyle="{StaticResource CellAlignCenter}" />
                                <DataGridTextColumn Binding="{Binding Wertverlust}" ClipboardContentBinding="{x:Null}" Header="Wertverlust" IsReadOnly="True" CanUserSort="False" CanUserReorder="False" CellStyle="{StaticResource CellAlignCenterInfoColor}" Foreground="Black"/>
                                <DataGridTextColumn Binding="{Binding ZeitWert}" ClipboardContentBinding="{x:Null}" Header="Zeitwert" IsReadOnly="True" CanUserSort="False" CanUserReorder="False" CellStyle="{StaticResource CellAlignCenterInfoColor}" CanUserResize="False"  Width="*" Foreground="Black"/>
                            </DataGrid.Columns>
                        </DataGrid>
     

    public partial class Windows_Main : Window { private List<WertVerlData> dGrid_WertVerl_Lst = null; public Windows_Main() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { ........

    dGrid_WertVerl_Lst = new DGrid_WertVerl_Source().WertVerlToLst(fzg.Abschreibung, fzg.ListPreis); DGridWertV.ItemsSource = dGrid_WertVerl_Lst; } ......... private void DGridWertV_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e) { if (e.Column.DisplayIndex == 1) { // 1. Nutzereingabe manuell in Liste übernehmen, da die Eingabe erst nach Beendigung von "CellEditEnding" autom. übernommen wird: dGrid_WertVerl_Lst[e.Row.GetIndex()].Prozent = ((TextBox)e.EditingElement).Text; // 2. Daten in Liste neu berechnen: new DGrid_WertVerl_Source().BerechneWertverluste(dGrid_WertVerl_Lst, aktuFzg.ListPreis); // 3. geänderte Liste in DataGrid wieder anzeigen: DGridWertV.ItemsSource = null; DGridWertV.ItemsSource = dGrid_WertVerl_Lst; } }

    Da das DataGrid die Nutzereingabe erst nach Beendigung von "CellEditEnding" in meine "dGrid_WertVerl_Lst" schreibt, glaube ich, dass meine Lösung nicht die offizielle ist. ich habe aber nichts gefunden, um mein Problem anders zu lösen. D.h. die Daten (Wertverlust, Zeitwert) in "dGrid_WertVerl_Lst" müssen nach der Nutzereingabe neu berechnet und wieder angezeigt werden.

    Wie macht man das Richtig?

    Vielleicht habt ihr ein paar Stichworte nach denen ich suchen sollte?

    Viele Grüße

    Klaus

    Freitag, 6. April 2018 10:59

Antworten

  • Hi Klaus,
    wie ich Dir geschrieben hatte, wird nur das aktuelle Element geändert.

    Wenn es anders sein soll, dann ist es am wichtigsten festzulegen, wie die Änderung ausgelöst werden soll. Deine Lösung schient mir dafür nicht optimal zu sein, da das Datenobjekt die Liste kennen muss. Aber, solange der Code funktioniert, ist da schon ok.

    List ist eine allgemeine Listenklasse. Im Gegensatz dazu implementiert die ObservableCollection nicht nur das INotifyPropertyChanged-, sondern auch  INotifyCollectionChanged-Interface. Wie in der Doku beschrieben, werden in dieser Klasse Benachrichtigungsfunktionen implementiert, wodurch man das Aktualisieren der Benutzeroberfläche automatisieren kann.

    Alle anderen Einsatzgebiete von Listen und Arrays verlieren dadurch nicht ihre Berechtigung. ObservableCollection ist eine spezielle Collection für einen speziellen Anwendungsfall, wie bereits der Name sagt.

     

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




    Samstag, 7. April 2018 09:20

Alle Antworten

  • Hi Klaus,
    ich würde das Problem anders lösen.

    Das DataGrid ist an eine Liste mit Werten gebunden. Jede Datenzeile ist von Typ eines Datenobjektes, in welchem alle Werte gehalten werden. Sobald ein Wert geändert wird (z.B. Prozent), wird über ein NotifyPropertyChanged der Oberfläche mitgeteilt, dass sich etwas geändert hat. Damit holt sich die Oberfläche alle anzuzeigenden Werte neu. In den Eigenschaften der Werte des Datenobjektes wird beim Abruf (Getter) einfach der auszugebende Wert neu berechnet. 


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

    Freitag, 6. April 2018 12:17
  • Hallo Peter,

    Danke für die schnelle Antwort.

    Auf "NotifyPropertyChanged" bin ich bei meinen Recherchen auch schon gestoßen. Ich habe aber keine Möglichkeit gesehen meine Neuberechnung der Daten auszuführen. Deshalb habe ich diesen Weg nicht weiter verfolgt.

    Ich habe es jetzt in mein Datenobjekt integriert. Dadurch kann ich jetzt in "DGridWertV_CellEditEnding" den Punkt 3.  (s.o.) entfernen. Dies löst aber nur einen Teil meines Problems.

    Nach der Änderung der Property "Prozent" wird "NotifyPropertyChanged" aufgerufen. Jetzt oder danach muss die Neuberechnung der nachfolgenden Datenobjekte erfolgen. In "NotifyPropertyChanged" sind aber die nachfolgenden Datenobjekte nicht bekannt.  

    Wie kann ich meine Methode zur Neuberechnung (s.o. Punkt 2.)  an diesem Punkt aufrufen?

    Gibt es noch einen andere Möglichkeit die Neuberechnung auszuführen?

    Viele Grüße

    Klaus

    Freitag, 6. April 2018 14:55
  • Hi Klaus,
    unklar ist mir, wer löst Änderung aus und in welchen Datenobjekten soll neu berechnet werden. Hier mal eine Demo, wenn nur in einem Datenobjekt geändert wird.

    XAML:

    <Window x:Class="WpfApp1CS.Window40"
            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="Window40" Height="300" Width="300">
      <Window.Resources>
        <local:Window40VM x:Key="vm"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <DataGrid AutoGenerateColumns="False" FrozenColumnCount="1" ItemsSource="{Binding View}">
          <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Jahr}" ClipboardContentBinding="{x:Null}" Header="Jahr" IsReadOnly="True" CanUserSort="False" CanUserReorder="False" Foreground="Black"/>
            <DataGridTextColumn Binding="{Binding Prozent}" CanUserReorder="False" ClipboardContentBinding="{x:Null}" Header="Prozent" />
            <DataGridTextColumn Binding="{Binding Wertverlust}" ClipboardContentBinding="{x:Null}" Header="Wertverlust" IsReadOnly="True" CanUserSort="False" CanUserReorder="False"  Foreground="Black"/>
            <DataGridTextColumn Binding="{Binding ZeitWert}" ClipboardContentBinding="{x:Null}" Header="Zeitwert" IsReadOnly="True" CanUserSort="False" CanUserReorder="False" CanUserResize="False"  Width="*" Foreground="Black"/>
          </DataGrid.Columns>
        </DataGrid>
      </Grid>
    </Window>

    Und dazu die Klassen (ViewModel und Data):

    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Data;
    
    namespace WpfApp1CS
    {
      public class Window40VM
      {
        private ObservableCollection<Window40Data> col = new ObservableCollection<Window40Data>();
        private CollectionViewSource cvs = new CollectionViewSource();
        public ICollectionView View
        {
          get
          {
            if (cvs.Source == null) cvs.Source = GetData();
            return cvs.View;
          }
        }
    
        private ObservableCollection<Window40Data> GetData()
        {
          ObservableCollection<Window40Data> col = new ObservableCollection<Window40Data>();
          for (int i = 0; i < 10; i++)
          {
            col.Add(new Window40Data() { Jahr = 2000 + i, Prozent = 10, Wertverlust = 100, ZeitWert = 1000 });
          }
          return col;
        }
      }
      public class Window40Data : INotifyPropertyChanged
      {
        public int Jahr { get; set; }
    
        private decimal _prozent;
        public decimal Prozent
        {
          get
          { return this._prozent; }
          set
          {
            this._prozent = value;
            Wertverlust = ZeitWert * value / 100;
            OnPropertyChanged(nameof(Wertverlust));
          }
        }
        public decimal Wertverlust { get; set; }
        public decimal ZeitWert { get; set; }
    
        #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

    Freitag, 6. April 2018 18:26
  • Hallo Peter.

    Danke für Dein Beispiel. Es hat mir etwas geholfen. Leider werden die der Nutzereingabe nachfolgenden Zeilen nicht aktualisiert. Das DataGrid soll in etwa so aussehen, das Design ist noch nicht fertig:

    Ich habe Dank Deiner Hilfe jetzt meinen Code neu gestrickt. Er sieht jetzt so aus:

    private void Window_Loaded(object sender, RoutedEventArgs e) {

    .... wertverluste = new WertVerlSource(aktuFzg.Abschreibung, aktuFzg.ListPreis); DGridWertV.ItemsSource = wertverluste.Lst; }

       public class WertVerlData: INotifyPropertyChanged
        {
            private string FProzent;
            private int FWertverlust;
            private int FZeitWert;
            
            private WertVerlSource FWertVerl_Source;
    
            public WertVerlData(WertVerlSource wvs)
            {
                FWertVerl_Source = wvs;
            }
            
            public int Jahr { get; set; }
            public string Prozent
            {
                get { return FProzent; }
                set
                {
                    if (FProzent != value)
                    {
                        FProzent = value;
                        NotifyPropertyChanged(nameof(Prozent));
                    }
                }
            }
    
            public int Wertverlust
            {
                get { return FWertverlust; }
                set
                {
                    if (FWertverlust != value)
                    {
                        FWertverlust = value;
                        NotifyPropertyChanged(nameof(Wertverlust));
                    }
                }
            }
    
            public int ZeitWert
            {
                get { return FZeitWert; }
                set
                {
                    if (FZeitWert != value)
                    {
                        FZeitWert = value;
                        NotifyPropertyChanged(nameof(ZeitWert));
                    }
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            public void NotifyPropertyChanged(string propName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propName));
                    if (propName == nameof(Prozent))
                       FWertVerl_Source.BerechneWertverluste();
                }
            }
        }
    
        public class WertVerlSource
        {
            private double FPreis;
            private List<WertVerlData> FLst;
    
            public WertVerlSource(string wertVerl, double preis)
            {
                FPreis = preis;
                FLst = WertVerlToLst(wertVerl, FPreis);
                BerechneWertverluste();
            }
    
            public List<WertVerlData> Lst { get { return FLst; } set { FLst = value; } }
    
            private string GetWert(int idx, string[] werte)
            {
                string result = TFzgConst.WertVerlProz;
                if (idx >= 0 && idx < werte.Length)
                    if (werte[idx].Trim() != "")
                        result = werte[idx];
                return result;
            }
    
           private List<WertVerlData> WertVerlToLst(string wertVerl, double preis)
            {
                string[] wv = (wertVerl ?? "").Split(TFzgConst.TzData);
                List<WertVerlData> lst = new List<WertVerlData>();
    
                for (int i = 0; i < TFzgConst.WertVerlJahre; i++)
                {
                    WertVerlData wvd = new WertVerlData(this);
                    wvd.Jahr = i + 1;
                    wvd.Prozent = GetWert(i, wv);
                    wvd.ZeitWert = 0;
                    lst.Add(wvd);
                }
                return lst;
            }
    
            public void BerechneWertverluste()
            {
                int aktuPreis = (int)FPreis;
                int proz = 0;
                WertVerlData wvd;
                TStrings str = new TStrings();
    
                for (int i = 0; i < TFzgConst.WertVerlJahre; i++)
                {
                    wvd = FLst[i];
                    proz = str.ToInt(wvd.Prozent);
                    wvd.Wertverlust = proz * aktuPreis / 100;
                    wvd.ZeitWert = aktuPreis - wvd.Wertverlust;
                    aktuPreis = wvd.ZeitWert;
                }
    
            }
    
        }
     
     

    Der Code funktioniert erstmal so wie gewünscht. 

    Eine Frage hätte ich dennoch. Ich konnte auf die Schnelle nicht herausfinden welchen Vorteil die Verwendung von "ObservableCollection" statt "List" hat? Die Benachrichtigung über Daten-Änderungen übernimmt doch "INotifyPropertyChanged"?

    Nochmals Danke für Deine Hilfe. Es hat mir viel gebracht.

    Viele Grüße

    Klaus

    Samstag, 7. April 2018 07:48
  • Hi Klaus,
    wie ich Dir geschrieben hatte, wird nur das aktuelle Element geändert.

    Wenn es anders sein soll, dann ist es am wichtigsten festzulegen, wie die Änderung ausgelöst werden soll. Deine Lösung schient mir dafür nicht optimal zu sein, da das Datenobjekt die Liste kennen muss. Aber, solange der Code funktioniert, ist da schon ok.

    List ist eine allgemeine Listenklasse. Im Gegensatz dazu implementiert die ObservableCollection nicht nur das INotifyPropertyChanged-, sondern auch  INotifyCollectionChanged-Interface. Wie in der Doku beschrieben, werden in dieser Klasse Benachrichtigungsfunktionen implementiert, wodurch man das Aktualisieren der Benutzeroberfläche automatisieren kann.

    Alle anderen Einsatzgebiete von Listen und Arrays verlieren dadurch nicht ihre Berechtigung. ObservableCollection ist eine spezielle Collection für einen speziellen Anwendungsfall, wie bereits der Name sagt.

     

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




    Samstag, 7. April 2018 09:20
  • Hi, Peter.

    Danke für Deine ausführliche Hilfe. Ich habe viel gelernt.

    Ein schönes Wochenende.

    Grüße Klaus.

    Samstag, 7. April 2018 12:25