Eine CSV-Preisliste mit VB.NET einlesen – 2024-Version
Inhaltsverzeichnis
- 1 Artikeldaten-Beispiel: Einlesen aus .csv-Datei
- 2 Keine Lust auf Text? Einfach als Video schauen
- 3 Gedanken, die man sich machen sollte: Objektorientierung
- 4 Die Preisliste.csv-Datei für VB.NET
- 5 Die Artikel-Klasse
- 6 Auf geht’s in die Form
- 7 Was ist zu tun – der Pseudo-Code
- 8 Wenn alles so einfach wäre..
- 9 Verfeinerung – yummy!
- 10 Verarbeiten eines jeweiligen Artikels / Datensatzes
- 11 Darstellen im DataGridView
- 12 Kompletter Code
- 13 Downloads
- 14 Weiterführende Links
Artikeldaten-Beispiel: Einlesen aus .csv-Datei
Hast Du Dich auch schonmal gefragt, wie Du eine CSV-Preisliste in VB.NET einlesen kannst? Gestern habe ich einen YouTube-Kommentar, bzw. einen Video-Wunsch eines Benutzers erhalten, wo es genau darum ging. Natürlich bin ich ein guter Klein-YouTuber *schnief* und setze die Wünsche meiner Zuschauer selbstverständlich um 😉!
Da der User unter dem VB-Video kommentiert hatte, dachte ich, ich mache es direkt passend – dennoch arbeite ich heutzutage fast nur noch mit C# (außer ein Kunde wünscht es anders, oder ich mache ein Video zu VB.NET).
Keine Lust auf Text? Einfach als Video schauen
Falls Du keine Lust hat, diesen Beitrag in Textform zu lesen, kannst Du natürlich auch zum passenden YouTube-Video von mir rüberschwenken. Einfach in das Rechteck hier drüber klicken und schon geht es los.
Gedanken, die man sich machen sollte: Objektorientierung
Bevor wir mit dem eigentlichen „Problem“, also der programmiertechnischen Herausforderung starten, setzen wir unsere Segel erstmal Richtung Datentypen. Wir machen uns also im ersten Schritt Gedanken darüber, mit welcher Art Daten, bzw. mit welchen Objekten wir arbeiten werden. Hier kommt bei einer Artikel-, bzw. Preisliste natürlich ein „Ding“ sofort in unseren Kopf: Der Artikel!
Eine letztendliche Auflistung dieser Artikel ist ja dann praktische unsere Preisliste im objektorientierten Sinne. Wenn Diese schön sauber gebaut ist, haben wir anschließend sogar noch die Möglichkeit, Sie zu filtern und weiterzuverarbeiten. Dazu kommen wir dann weiter unten..
Damit wir die Klasse allerdings gut strukturieren können (erneut – hier einfach gehalten), schauen wir uns im nächsten Schritt die Datei selbst an. Bringt ja schließlich nichts, mit Spekulationen zu programmieren, wir brauchen harte Fakten!
Die Preisliste.csv-Datei für VB.NET
Wenn wir an eine typische CSV-Datei denken, dann denkt man wohl an verschiedene Werte, Welche durch Kommata getrennt sind (naja, „comma separated values“ halt, gell?). Jede Zeile steht hierbei für einen jeweils eigenen Datensatz / Artikel.
Also ungefähr so:
Name,Preis Artikel 1,12.95 Artikel 2,120.95 Artikel 3,1000
Man trifft allerdings häufiger auf so eine Variante hier an, also durch Semikola (ja, ist tatsächlich der Plural von Semikolon und hat nichts mit dem Getränk zu tun!) getrennte Spalten:
Name;Preis Artikel 1;12.95 Artikel 2;120.95 Artikel 3;1000
Im Endeffekt ist uns das auch fast egal, denn dann verarbeiten wir die Spalten einfach nur durch ein anders getrenntes Zeichen – muss man aber trotzdem drauf achten. Ebenso wichtig ist hier das Encoding, es könnte zu Problemen mit Umlauten sowie Sonderzeichen führen. Hierbei bin ich ein Fan von UTF-8 :)!
Da wir die Daten nun kennen, bauen wir im nächsten Schritt einfach die Artikel-Klasse.
Die Artikel-Klasse
Fangen wir also nun mit der Erstellung einer passenden Klasse an. Beachte jedoch, dass ich Diese für dieses Beispiel hier klein und einfach gehalten habe. Je nach Anwendungsfall, müsstest Du die Klasse natürlich noch um die ein oder andere Eigenschaft / Funktionalität erweitern.
Lege also eine neue Datei im Projekt an und bezeichne Diese als „Artikel.vb“:
Public Class Artikel Public Property Name As String Public Property Preis As Decimal End Class
public class Artikel { public string Name { get; set; } public decimal Preis { get; set; } }
Hierbei habe ich die beiden für mich wichtigsten Eigenschaften des Artikels gewählt:
- Name – natürlich ein String
- Preis – ein Decimal
Optionaler, leerer Konstruktor
Nachdem wir diese beiden Eigenschaften definiert haben, könntest Du eventuell einen expliziten, leeren Konstruktor anlegen. Auch wenn Dieser praktisch von Haus aus vorhanden und „KISS – Keep It Smart & Simple“ an der Tagesordnung ist, finde ich es so (persönlich) trotzdem sauberer. Allerspätestens wenn Du (wie häufig) Parameter bei der Erstellung übergeben möchtest, bist Du darauf sowieso angewiesen.
Erweitere also die Artikel-Klasse bei Bedarf um den parameterlosen Konstruktor:
' Rest der Klasse... Sub New() End Sub ' ...
// Rest der Klasse... public Artikel() { } // ...
Auf geht’s in die Form
Im nächsten Step hüpfen wir in das Formular, Welches uns lediglich einen Button und auch noch eine Tabelle (DataGridView) bereitstellen wird. Wenn wir auf den Button klicken, sollen die Artikeldaten aus der CSV-Datei verarbeitet und anschließend dargestellt werden. Schaue Dir hierzu ggf. noch einmal das Beitragsbild von ganz oben an und designe es nach Bedarf ähnlich.
Klicke nun einmal doppelt auf die Form, damit wir direkt einmal das DataGridView konfigurieren können. Wir stellen hierbei die automatische Generierung der Spalten aus, Diese legen wir selbst fest, weil wir später noch eine „lesbarere Spalte“ definieren wollen! Durch den Doppelklick (oder einem anderen Weg) solltest Du nun im Load-Ereignishandler des Formulars sein:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 'Dim enUsCulture = New CultureInfo("en-US") 'Thread.CurrentThread.CurrentCulture = enUsCulture DataGridView1.AutoGenerateColumns = False End Sub
Beachte, dass die obigen beiden Anweisungen gebraucht werden, wenn Du in der CSV-Datei ein englisches Zahlenformat (also „49.95“ statt „49,95“) verwendest. Damit setzt Du die sogenannte CultureInfo auf „Englisch“.
Danach kommen wir zum wichtigen, gerade eben angesprochenen Punkt: Wir schalten die automatische Generierung von Spalten bei Zuweisung einer Datenquelle aus. Dies geht durch das Zuweisen von „False“ mit der „AutoGenerateColumns“-Eigenschaft. Falls Du die automatisch generierten Spalten behalten möchtest, kommentiere diese Zeile natürlich dementsprechend aus.
DataGridView-Spalten konfigurieren
Nun kannst Du die Spalten des DataGridViews (bei ausgeschalteter Auto-Generierung) manuell festlegen. Gehe dazu mit einem Klick auf Dein DataGridView und wähle im Eigenschaftsfenster (Designer!) die „Columns“-Eigenschaft. Verwende die 3 Punkte, damit sich ein Menü öffnet und pflege die erwünschten Spalten ein. Im Beispiel von oben sind das zwei: Name und Preis.
Achte hier auf die Bezeichnung der Spalte (cName, cPreis z. B.) und wähle einen entsprechenden Kopf-Text. Das Wichtigste bei der Verwendung via Datenbindung ist hier, dass Du die „DataPropertyName“-Eigenschaft setzt. Diese muss auf den Namen der zu bindenden / darzustellenden Eigenschaft gesetzt werden. In unserem Fall ist das „Name“ und „Preis“ – siehe Eigenschaften der Artikel-Klasse.
Dies ist besonders relevant, wenn Du (wie ich) lieber die sauber als Euro-Wert formatierte Variante des Preises sehen möchtest. Ergänze hierzu die Artikel-Klasse um eine passende ReadOnly-Eigenschaft:
' restliche Artikel-Klasse.. Public ReadOnly Property LesbarerPreis As String Get Return Preis.ToString("C2") End Get End Property ' ...
Achtung bei xx.xx und xx,xx!
Beachte hierbei, dass Du ggf. oben die CultureInfo geändert hast, er würde daher nun die englische Ausgabe verwenden. Erinnere Dich hierzu, dass wir die „Sprache“ global geändert/gesetzt haben, damit er die Zahlen aus der CSV-Datei richtig verarbeitet. Du könntest hier nun z. B. die deutsche CultureInfo mitgeben, damit dies hier funktioniert:
' restliche Artikel-Klasse... ' Shared - bitte nicht bei jedem Abruf der Property neu instanziieren 🙈 Private Shared deutscheCultureInfo = New CultureInfo("de-DE") Public ReadOnly Property LesbarerPreis As String Get Return Preis.ToString("C2", deutscheCultureInfo) End Get End Property ' ....
// restliche Artikel-Klasse... // Shared - bitte nicht bei jedem Abruf der Property neu instanziieren 🙈 private static var deutscheCultureInfo = new CultureInfo("de-DE"); public string LesbarerPreis { get { return Preis.ToString("C2", deutscheCultureInfo); } } // ...
Erstelle im nächsten Schritt einen Handler für das Klick-Ereignis des Buttons und den dementsprechenden Code. Um Dich nicht direkt mit dem Code zu erschlagen, teile ich Ihn in verdauliche Aspekte auf.
Was ist zu tun – der Pseudo-Code
- Unser Quell der Daten ist eine Datei -> ergo, wir brauchen Dateioperationen
- Dateipfad zusammenstückeln -> woher lesen wir?
- Datei einlesen -> Dateioperation durchführen
- Wir möchten 0, 1, oder mehrere Artikel aus der Datei bekommen -> wird verwenden einen dementsprechenden Datentyp: Array / List
- Die erste Zeile der Datei ist die Kopf-Zeile -> hier gibt es wichtige Informationen über die Struktur der Datei
- Jede sonstige Zeile beinhaltet jeweils einen Artikel
Anhand der Fakten sähe unser (erstes) Vorhaben also pseudo-code-mäßig so aus:
- Lese Zeilen aus Datei - Verarbeite die erste Zeile, um Basis-Informationen zu erhalten - Lese und verarbeite die restlichen Zeilen und erstelle jeweils einen Artikel daraus - Fügen den Artikel unserer objektorientierten / virtuellen Preisliste hinzu
Dies würde näher an .NET formuliert erstmal grob wie gleich folgend aussehen. Man beachte hier von Beginn an das „Async“ Schlüsselwort – wir haben sicherlich nicht vor eine synchrone und eventuell hängende IO-Operation durchzuführen. Stell Dir vor, die Datei wäre 10MB groß, dann freut man sich hier mit der synchronen Variante sicherlich nicht! Nun zum Code:
Private Async Sub btnPreislisteEinlesen_Click(sender As Object, e As EventArgs) Handles btnPreislisteEinlesen.Click Dim dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv") Dim zeilen = Await File.ReadAllLinesAsync(dateipfad) Dim artikelListe = New List(Of Artikel)() For Each zeile In zeilen Dim artikel = New Artikel() ' zeile verarbeiten und Artikel befüllen artikelListe.Add(artikel) Next ' Rest... End Sub
private async void btnPreislisteEinlesen_Click(object sender, EventArgs e) { var dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv"); var zeilen = await File.ReadAllLinesAsync(dateipfad); var artikelListe = new List<Artikel>(); foreach (var zeile in zeilen) { var artikel = new Artikel(); // zeile verarbeiten und Artikel befüllen artikelListe.Add(artikel); } }
Wenn alles so einfach wäre..
Grundsätzlich würde das obige Vorgehen vermutlich erstmal aushalten, aber jeder, der mal „echte Daten“ importiert hat, wird wissen, dass das leider nicht so einfach ist. Kunden, Exporte von System, usw. spielen einem häufig eben genau das aus, was man nicht erwartet, deswegen ist jede Art von Flexibilität willkommen!
Denke z. B. an die folgende Situation – wir erwarten:
Name;Preis Artikel 1;12.95 Artikel 2;120.95 Artikel 3;1000
Es kommt aber eine getauschte Variante.. und stell‘ Dir dies nun mit 15 vertauschten Spalten vor 🤮..
Preis;Name 12.95;Artikel 1 120.95;Artikel 2 1000;Artikel 3
Im nächsten Schritt bauen wir daher eine Funktionalität ein, Welche von selbst erkennt, wo sich die jeweiligen Spalten befinden. Wir lesen also die Header-Spalte und schauen: „Wo befindet sich der Preis?“. Dies könnte man sogar noch weiter spinnen und mit Übersetzungen, Abkürzungen und Co. machen, aber gut, ich lasse die Kirche mal wieder im Dorf.
Verfeinerung – yummy!
Die Erkennung der korrekten Spalten (Indices) wäre dementsprechend in dem Moment angebracht, wenn wir in der ersten Zeile sind. Nun kannst Du auch sehen, warum ich eine For- und keine For-Each-Schleife genommen habe. Dies kannst Du selbstverständlich so machen, wie Du es für richtig hältst. Achte nur darauf, dass Du nicht etwa in jeder Iteration irgendwie so einen Quatsch wie „IndexOf“ machst, da er sonst pro Schleifendurchlauf nochmal alle Einträge durch muss.
Da wir uns die Positionen natürlich nicht in jedem Durchlauf erneut suchen möchten (und auch nicht können, da wir nicht „nochmal“ in Zeile 0/1 sind), initialisieren wir uns die Variablen außerhalb. Ich nehme hier bewusst die -1, da man so ggf. noch prüfen könnte, ob eine jeweilige Spalte überhaupt gefunden wurde.
' außerhalb der For-Schleife Dim nameIndex = -1 Dim preisIndex = -1 ' innerhalb der For-Schleife Dim zeile = zeilen(i) Dim daten = zeile.Split(";") Dim ersteZeile = i = 0 If ersteZeile Then For j = 0 To daten.Length - 1 Dim headerBezeichnung = daten(j) If headerBezeichnung = "Name" Then nameIndex = j ElseIf headerBezeichnung = "Preis" Then preisIndex = j End If Next Continue For End If ' .. weiterer Inhalt der For-Schleife
Verarbeiten eines jeweiligen Artikels / Datensatzes
Nun ist es an der Zeit, den Datensatz, also die Zeile eines jeweiligen Artikels zu verarbeiten. Wir kennen an dieser Stelle bereits die Positionen der jeweiligen Informationen können daher also direkt damit arbeiten. Hier eine Möglichkeit den Artikel zu befüllen, man könnte freilich auch einen passenden Konstruktor verwenden:
' Innerhalb der For-Schleife... Dim artikel = New Artikel() artikel.Name = daten(nameIndex) artikel.Preis = Convert.ToDecimal(daten(preisIndex)) artikelListe.Add(artikel) ' ...
// Innerhalb der For-Schleife... var artikel = new Artikel(); artikel.Name = daten(nameIndex); artikel.Preis = Convert.ToDecimal(daten(preisIndex)); artikelListe.Add(artikel); // ...
Private Async Sub btnPreislisteEinlesen_Click(sender As Object, e As EventArgs) Handles btnPreislisteEinlesen.Click Dim dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv") Dim zeilen = Await File.ReadAllLinesAsync(dateipfad) Dim nameIndex = -1 Dim preisIndex = -1 Dim artikelListe = New List(Of Artikel) For i = 0 To zeilen.Length - 1 Dim zeile = zeilen(i) Dim daten = zeile.Split(";") Dim ersteZeile = i = 0 If ersteZeile Then For j = 0 To daten.Length - 1 Dim headerBezeichnung = daten(j) If headerBezeichnung = "Name" Then nameIndex = j ElseIf headerBezeichnung = "Preis" Then preisIndex = j End If Next Continue For End If Dim artikel = New Artikel() artikel.Name = daten(nameIndex) artikel.Preis = Convert.ToDecimal(daten(preisIndex)) artikelListe.Add(artikel) Next DataGridView1.DataSource = artikelListe End Sub
private async void btnPreislisteEinlesen_Click(object sender, EventArgs e) { var dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv"); var zeilen = await File.ReadAllLinesAsync(dateipfad); var nameIndex = -1; var preisIndex = -1; var artikelListe = new List<Artikel>(); for (var i = 0; i <= zeilen.Length - 1; i++) { var zeile = zeilen(i); var daten = zeile.Split(";"); var ersteZeile = i == 0; if (ersteZeile) { for (var j = 0; j <= daten.Length - 1; j++) { var headerBezeichnung = daten(j); if (headerBezeichnung == "Name") nameIndex = j; else if (headerBezeichnung == "Preis") preisIndex = j; } continue; } var artikel = new Artikel(); artikel.Name = daten(nameIndex); artikel.Preis = Convert.ToDecimal(daten(preisIndex)); artikelListe.Add(artikel); } DataGridView1.DataSource = artikelListe; }
Darstellen im DataGridView
Nun fehlt nur noch ein letzter Schritt, um unseren verarbeiteten Artikeldaten in unserer Tabelle (DataGridView) darstellen zu können. Füge dazu einfach den gleich folgenden Code nach der For-Schleife ein. Vergiss allerdings nicht, dass Du die Konfiguration der Spalten oben tätigen solltest!
' For Schleife ' For Schleife Ende ' artikelListe = Deine Variable (vom Typ List(Of Artikel)), Welche die verarbeiteten Artikel beinhaltet DataGridView1.DataSource = artikelListe
Kompletter Code
Hast Du es eilig? Kein Problem, kenne ich! Nimm Dir, was Du brauchst einfach hier weg, oder lade das Beispielprojekt weiter unten herunter. Bevor die Wörter-Polizei kommt: „Artikäl“ ist mit Absicht so geschrieben..
Die Preisliste.csv
Name;Preis; Artikäl 1;15,95; Artikel 2;49,95; Artikel 3;120,95;
Die Artikel-Klasse
Public Class Artikel Public Property Name As String Public Property Preis As Decimal Public ReadOnly Property LesbarerPreis As String Get Return Preis.ToString("C2") End Get End Property Sub New() End Sub End Class
public class Artikel { public string Name { get; set; } public decimal Preis { get; set; } public string LesbarerPreis { get { return Preis.ToString("C2"); } } public Artikel() { } }
Der Form-Code
Imports System.IO Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 'Dim enUsCulture = New CultureInfo("de-DE") 'Thread.CurrentThread.CurrentCulture = enUsCulture DataGridView1.AutoGenerateColumns = False End Sub Private Async Sub btnPreislisteEinlesen_Click(sender As Object, e As EventArgs) Handles btnPreislisteEinlesen.Click Dim dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv") Dim zeilen = Await File.ReadAllLinesAsync(dateipfad) Dim nameIndex = -1 Dim preisIndex = -1 Dim artikelListe = New List(Of Artikel) For i = 0 To zeilen.Length - 1 Dim zeile = zeilen(i) Dim daten = zeile.Split(";") Dim ersteZeile = i = 0 If ersteZeile Then For j = 0 To daten.Length - 1 Dim headerBezeichnung = daten(j) If headerBezeichnung = "Name" Then nameIndex = j ElseIf headerBezeichnung = "Preis" Then preisIndex = j End If Next Continue For End If Dim artikel = New Artikel() artikel.Name = daten(nameIndex) artikel.Preis = Convert.ToDecimal(daten(preisIndex)) artikelListe.Add(artikel) Next DataGridView1.DataSource = artikelListe End Sub End Class
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Threading.Tasks; using Microsoft.VisualBasic; public class Form1 { private void Form1_Load(object sender, EventArgs e) { // Dim enUsCulture = New CultureInfo("de-DE") // Thread.CurrentThread.CurrentCulture = enUsCulture DataGridView1.AutoGenerateColumns = false; } private async void btnPreislisteEinlesen_Click(object sender, EventArgs e) { var dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv"); var zeilen = await File.ReadAllLinesAsync(dateipfad); var nameIndex = -1; var preisIndex = -1; var artikelListe = new List<Artikel>(); for (var i = 0; i <= zeilen.Length - 1; i++) { var zeile = zeilen(i); var daten = zeile.Split(";"); var ersteZeile = i == 0; if (ersteZeile) { for (var j = 0; j <= daten.Length - 1; j++) { var headerBezeichnung = daten(j); if (headerBezeichnung == "Name") nameIndex = j; else if (headerBezeichnung == "Preis") preisIndex = j; } continue; } var artikel = new Artikel(); artikel.Name = daten(nameIndex); artikel.Preis = Convert.ToDecimal(daten(preisIndex)); artikelListe.Add(artikel); } DataGridView1.DataSource = artikelListe; } }
Downloads
Du brauchst ein schnelles Code-Beispiel, um alles in eigener Hand ausprobieren zu können? Dann lade es Dir hier einfach kostenlos das herunter!
Weiterführende Links
Vielleicht ist auch einer der folgenden Beiträge für Dich interessant: