none
GridView wird nicht korrekt ausgelsen

    Frage

  • Hallo,

    ich habe in meiner WinForms Applikation folgende GridView erstellt, in die Dateiinhalte eingelesen werden:

    Veränderte Inhalte der GridView sollen zunächst in eine List<string> eingelesen werden. Folgender Algorithmus macht das auch, allerdings nur die ersten beiden Zeilen. Die letzte Zeile wird immer so übernommen, wie sie bereits dasteht. Erst, wenn ich  innerhalb der GridView auf eine beliebige leere Zelle der letzten Zeile geklickt habe, werden alle Inhalte in die List verfrachtet. Woran liegt das?

    int zeilenAnzahl = DataGridView_CSVContent.Rows.Count - 1, spaltenAnzahl;
    .
    .
       for (int reihe = 0; reihe < zeilenAnzahl; reihe++) {
                    spaltenAnzahl = DataGridView_CSVContent.Rows[reihe].Cells.Count;
                    try {
                        for (int spalte = 0; spalte < spaltenAnzahl; spalte++) {
                            splitList.Add(DataGridView_CSVContent.Rows[reihe].Cells[spalte].Value.ToString());
                        }
                    }
                    catch (NullReferenceException) {
                        message = " Die GridView hat einen invaliden Aufbau! Sie dürfen die Struktur nicht verändern. Haben sie vielleicht Zellinhalte gelöscht?";
                        this.Ausgabe(message, "Error", MessageBoxIcon.Error);
                        return;
                    }
                }
    .
    .




    • Bearbeitet tklustig Freitag, 19. April 2019 11:39
    Freitag, 19. April 2019 11:16

Antworten

  • Hi Thomas,
    nachfolgend eine kleine Demo (ohne Designer, einfach in eine leere Form kopieren). Mit dem obersten Button wird eine CVS-Datei (Semikolon-separiert) geladen und im DataGridView angezeigt, dann können die Zellen geändert werden, mit dem zweiten Button können die aktuellen Daten wieder in einer CSV-Datei abgelegt werden.

    Wenn Du da noch die Befehle zur Fehlerprotokollierung hinzufügst, dann leibt das Programm doch bedeutend kürzer.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Windows.Forms;
    
    namespace WindowsFormsApp1
    {
      public partial class Form1 : Form
      {
        public Form1()
        {
          InitializeComponent();
          this.Load += new System.EventHandler(this.Form02_Load);
        }
    
        // Arbeit ohne Designer in einer leeren Form
        private Button btnLoad = new Button() { Text = "Laden", Dock = DockStyle.Top };
        private Button btnSave = new Button() { Text = "Speichern", Dock = DockStyle.Top };
        private DataGridView dgv = new DataGridView() { Dock = DockStyle.Top };
        private string fileNameInput = @"Form03Demo.csv";
        private string fileNameOutput = @"Form03Demo_neu.csv";
        private List<Data> daten = new List<Data>();
    
        private void Form02_Load(object sender, EventArgs e)
        {
          this.Controls.AddRange(new Control[] { dgv, btnSave, btnLoad });
          btnLoad.Click += BtnLoad_Click;
          btnSave.Click += BtnSave_Click;
        }
    
        private void BtnLoad_Click(object sender, EventArgs e)
        {
          if (File.Exists(fileNameInput))
          {
            // alten Inhalt der Datenliste löschen, um beim wiederholten Mal Daten nicht doppelt erscheinen
            daten.Clear();
            // Reader initialisieren, Schleife, solange Daten noch einzulesen sind, Zeile einlesen und der Datenliste hinzufügen
            // Reader schließen und entsorgen (beim Ende von Using)
            using (StreamReader rdr = new StreamReader(fileNameInput))
              while (!rdr.EndOfStream) daten.Add(new Data(rdr.ReadLine()));
            // Datenliste binden
            dgv.DataSource = daten;
          }
        }
    
        private void BtnSave_Click(object sender, EventArgs e)
        {
          // Writer initialisieren
          // Schleife über alle Elemente in der Datenliste, zeilenweise in Datei ausgeben
          // Writer schließen und entsorgen
          using (StreamWriter wrt = new StreamWriter(fileNameOutput))
            foreach (var item in daten) wrt.WriteLine(item.ToString());
        }
    
        public class Data
        {
          public Data(string line)
          {
            var cols = line.Split(';');
            for (int i = 0; i < cols.Length; i++)
            {
              if (i == 0) Column1 = cols[i];
              if (i == 1) Column2 = cols[i];
              if (i == 2) Column3 = cols[i];
            }
          }
          public override string ToString() => $"{Column1};{Column2};{Column3}";
          public string Column1 { get; set; }
          public string Column2 { get; set; }
          public string Column3 { get; set; }
        }
      }
    }


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    • Als Antwort markiert tklustig Samstag, 20. April 2019 12:50
    Samstag, 20. April 2019 10:10

Alle Antworten

  • Wenn ich an der richtigen Stelle diesen Code einfüge, ist der Bug behoben. Warum dem so ist, entzieht sich jedoch meiner Kenntnis. Kann das jemand erläutern, bitte?!

    this.DataGridView_CSVContent.CurrentCell = this.DataGridView_CSVContent[0,zeilenAnzahl];

    • Bearbeitet tklustig Freitag, 19. April 2019 20:04
    Freitag, 19. April 2019 11:48
  • Hi THomas,
    warum machst Du Dir so viel Stress? Warum nutzt Du nicht eine einfache Datenbindung und bearbeitest in der Logik ausschließlich die Datenquelle. Als Datenquelle nutzt Du am besten eine typisierte Liste mit Datenobjekten eines Typs (einer Klasse), die mindestens pro Spalte eine Eigenschaft enthält. Deine splitLst kannst dann einfach aus diesen Datenobjekten füllen, z.B. mit einem AddRange und einer Methode der Klasse, die eine Liste der Eigenschaftswerte liefert.

    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Freitag, 19. April 2019 16:28
  • Ist das in diesem Fall nicht  overheaded?

    Auslesen muss ich die Datenquelle(CSV Datei) so oder so. Die Spalteninhalte der Property einer neuen Klasse zuzuweisen ergibt für mich keinen erkennbaren Vorteil. Hier der funktionale Code, wie ich ihn derzeit verwende. Wie würdest Du das alternativ im Sinne der von Dir erwähnten Datenbindung machen?

            #region Sichert Zelländerungen in die CSV Datei
            private void saveToolStripMenuItem_Click(object sender, EventArgs e) {
                try {
                    _logger.Info("Load succesfully saveToolStripMenuItem_Click()");
                    string message = String.Empty, csvContentUnordered = String.Empty;
                    int zeilenAnzahl = DataGridView_CSVContent.Rows.Count - 1, spaltenAnzahl;
                    Verschluesselung encryptDecrypt = new Verschluesselung();
                    List<string> listPassword = new List<string>();
                    List<string> listUsername = new List<string>();
                    List<string> listPasswordEncrypted = new List<string>();
                    List<string> listUsernameEncrypted = new List<string>();
                    List<string> splitList = new List<string>();
                    if (EditGrid) {
                        message = "Warum wollen Sie speichern? Ändern Sie erst einen beliebigen Zellenwert.";
                        this.Ausgabe(message, "Warnung", MessageBoxIcon.Question);
                        _logger.Info("Informed user, saving is nonsens without having done any changes");
                        return;
                    }
                    /*  Die Property CurrentCell muss implementiert werden, damit alle Änderungen übernommen werden. Näheres dazu hier:
                        https://social.msdn.microsoft.com/Forums/de-DE/46cc3af5-490b-490f-8c4e-f6e77cd09bc1/gridview-wird-nicht-korrekt-ausgelsen?forum=visual_studiode
                    */
                    for (int reihe = 0; reihe < zeilenAnzahl; reihe++) {
                        spaltenAnzahl = DataGridView_CSVContent.Rows[reihe].Cells.Count;
                        DataGridView_CSVContent.CurrentCell = DataGridView_CSVContent[0, zeilenAnzahl];
                        try {
                            for (int spalte = 0; spalte < spaltenAnzahl; spalte++) {
                                splitList.Add(DataGridView_CSVContent.Rows[reihe].Cells[spalte].Value.ToString());
                            }
                        }
                        catch (NullReferenceException er) {
                            message = " Die GridView hat einen invaliden Aufbau! Sie dürfen die Struktur nicht verändern. Haben Sie vielleicht Zellinhalte gelöscht?";
                            this.Ausgabe(message, "Error", MessageBoxIcon.Error);
                            _logger.Warn("Informed user, it's not allowed removing any items from GridView{0}{1}", Environment.NewLine, er.Message);
                            return;
                        }
                    }
                    String[] splitArray = splitList.ToArray();
                    for (int i = 1; i <= splitArray.Length; i++) {
                        if (i % 5 == 0) {
                            listUsername.Add(splitArray[i - 2]);
                            listPassword.Add(splitArray[i - 1]);
                        }
                    }
                    _logger.Info("Succesfully added username and password from GridView to generic list");
                    foreach (string item in listUsername) {
                        listUsernameEncrypted.Add(encryptDecrypt.EncryptString(item));
                    }
                    foreach (string item in listPassword) {
                        listPasswordEncrypted.Add(encryptDecrypt.EncryptString(item));
                    }
                    _logger.Info("Succesfully encrypted every username and password from GridView");
                    string path = Path.Combine(rootFolder, filename);
                    StreamWriter objWriter = new StreamWriter(path);
                    for (int reihe = 0; reihe < zeilenAnzahl; reihe++) {
                        if (reihe > 0)
                            objWriter.Write(Environment.NewLine);
                        spaltenAnzahl = DataGridView_CSVContent.Rows[reihe].Cells.Count;
                        DataGridView_CSVContent.CurrentCell = DataGridView_CSVContent[0, zeilenAnzahl];
                        for (int spalte = 0; spalte < spaltenAnzahl; spalte++) {
                            if (spalte < spaltenAnzahl - 1) {
                                if (spalte == spaltenAnzahl - 2)
                                    objWriter.Write(listUsernameEncrypted[reihe] + ";");
                                else
                                    objWriter.Write(DataGridView_CSVContent.Rows[reihe].Cells[spalte].Value.ToString() + ";");
                            }
                            else
                                objWriter.Write(listPasswordEncrypted[reihe]);
                        }
                    }
                    objWriter.Close();
                    message = "Ihre Änderungen wurden in die Datei " + filename + " übernommen. Username und Passwort wurden verschlüsselt eingetragen!";
                    this.Ausgabe(message, "Info", MessageBoxIcon.Information);
                    _logger.Info("Succesfully wrote content of GridView into file {0}", filename);
                }
                catch (Exception er) {
                    _logger.Error(er.Message + Environment.NewLine + er.ToString());
                    string message = "Unbekannter Fehler. Die Ursache wurde protokolliert." + Environment.NewLine + "Bitte informieren Sie den Entwickler thomas.kipp@automotivesystems.de";
                    this.Ausgabe(message, "Error", MessageBoxIcon.Error);
                }
            }
            #endregion




    • Bearbeitet tklustig Freitag, 19. April 2019 20:18
    Freitag, 19. April 2019 20:10
  • Hi Thomas,
    nachfolgend eine kleine Demo (ohne Designer, einfach in eine leere Form kopieren). Mit dem obersten Button wird eine CVS-Datei (Semikolon-separiert) geladen und im DataGridView angezeigt, dann können die Zellen geändert werden, mit dem zweiten Button können die aktuellen Daten wieder in einer CSV-Datei abgelegt werden.

    Wenn Du da noch die Befehle zur Fehlerprotokollierung hinzufügst, dann leibt das Programm doch bedeutend kürzer.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Windows.Forms;
    
    namespace WindowsFormsApp1
    {
      public partial class Form1 : Form
      {
        public Form1()
        {
          InitializeComponent();
          this.Load += new System.EventHandler(this.Form02_Load);
        }
    
        // Arbeit ohne Designer in einer leeren Form
        private Button btnLoad = new Button() { Text = "Laden", Dock = DockStyle.Top };
        private Button btnSave = new Button() { Text = "Speichern", Dock = DockStyle.Top };
        private DataGridView dgv = new DataGridView() { Dock = DockStyle.Top };
        private string fileNameInput = @"Form03Demo.csv";
        private string fileNameOutput = @"Form03Demo_neu.csv";
        private List<Data> daten = new List<Data>();
    
        private void Form02_Load(object sender, EventArgs e)
        {
          this.Controls.AddRange(new Control[] { dgv, btnSave, btnLoad });
          btnLoad.Click += BtnLoad_Click;
          btnSave.Click += BtnSave_Click;
        }
    
        private void BtnLoad_Click(object sender, EventArgs e)
        {
          if (File.Exists(fileNameInput))
          {
            // alten Inhalt der Datenliste löschen, um beim wiederholten Mal Daten nicht doppelt erscheinen
            daten.Clear();
            // Reader initialisieren, Schleife, solange Daten noch einzulesen sind, Zeile einlesen und der Datenliste hinzufügen
            // Reader schließen und entsorgen (beim Ende von Using)
            using (StreamReader rdr = new StreamReader(fileNameInput))
              while (!rdr.EndOfStream) daten.Add(new Data(rdr.ReadLine()));
            // Datenliste binden
            dgv.DataSource = daten;
          }
        }
    
        private void BtnSave_Click(object sender, EventArgs e)
        {
          // Writer initialisieren
          // Schleife über alle Elemente in der Datenliste, zeilenweise in Datei ausgeben
          // Writer schließen und entsorgen
          using (StreamWriter wrt = new StreamWriter(fileNameOutput))
            foreach (var item in daten) wrt.WriteLine(item.ToString());
        }
    
        public class Data
        {
          public Data(string line)
          {
            var cols = line.Split(';');
            for (int i = 0; i < cols.Length; i++)
            {
              if (i == 0) Column1 = cols[i];
              if (i == 1) Column2 = cols[i];
              if (i == 2) Column3 = cols[i];
            }
          }
          public override string ToString() => $"{Column1};{Column2};{Column3}";
          public string Column1 { get; set; }
          public string Column2 { get; set; }
          public string Column3 { get; set; }
        }
      }
    }


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    • Als Antwort markiert tklustig Samstag, 20. April 2019 12:50
    Samstag, 20. April 2019 10:10
  • Okay. Überzeugt :=)

    Eine Frage habe ich noch: Warum überschreibst du in der Klasse Data die toString()-Methode? Was erwirkt... 

    => $"{Column1};{Column2};{Column3}";

    genau. Ist Dein Konstrukt die kürzere Variante von:

        public override string ToString()
        {
            return Column1+Column2+Column3;
        }

    ?

    Samstag, 20. April 2019 13:01
  • Hi,

    zu $"...{Referenz}..." siehe:

      $ – Zeichenfolgeninterpolation (C#-Referenz)


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET (2001-2018)
    https://www.asp-solutions.de/ - IT Beratung, Softwareentwicklung, Remotesupport

    Samstag, 20. April 2019 13:57
    Moderator
  • Hallo Thomas,

    mit => wird ein Lambdaausdruck eingeleitet. Einfach ausgedrückt ist das eine Funktion mit oder ohne Rückgabewert.

    Bei der Zeichenverkettung mit $ oder string.Format werden Ressourcen eingespart.

    Bei der Zeichenverkettung mit + werden immer unnötige instanzen der Klasse String erstellt.

    Column1 + Column2 ergibt eine neue String Klasse mit dem Inhalt aus 1 und 2

    Column1/2 + Column3 ergib wieder eine neue Sting Klasse.

    Es geht nicht um die verkürzte Schreibweise sondern um die Einsparung von Ressourcen.

    Wenn man so etwas in einer Schleife macht, kann es unter umständen zur einer Stackoverflow Exception kommen


    Gruß Thomas
    13 Millionen Schweine landen jährlich im Müll
    Dev Apps von mir: UWP Segoe MDL2 Assets, UI Strings

    Samstag, 20. April 2019 15:14