Import, eksport, edycja plików CSV

Ostatnio dostałem bardzo ciekawego maila z prośbą o pomoc przy tworzeniu pewnej aplikacji.

Witam Panie Piotrze.

Generalnie uczę się VBA, z książki internetu. Korzystam czasami też z pomocy kolegów z pracy. W poszukiwaniu różnych kodów i rozwiązań trafiłem między innymi na Pana stronę.
Szukam/modyfikuję/tworzę program który:
1. zaimportuje plik CSV lub XLS do VB.  Plik ma 47 na 334 linie (jak Pan chce to mogę podesłać). Tu prawie się udało, prawie bo niestety dane importuje mi tylko w ostatniej linii.
2. pokaże tabele DataGridView. W tabeli VB robie drobne zmiany czas, data.Tu można powiedzieć, że działa.
3. utworzy mi plik CSV. I tu okazuje sie jest spory problem, żeby zrobić prawdziwą CSV. Taką, żeby można było zobaczyć to w np ++Notepad
W Pana programie mi coś po prostu nie hula 🙂
Zagadnie zrobiło się dość skomplikowane jak dla mnie przynajmniej.
Żeby nie być gołosłownym w załączniku mój klucz/kod (nie wiem jak to nazwać).
Dlaczego uparłem się na CSV. Bo mam w pracy program który potrzebuje CSV tylko i wyłącznie. Dlaczeg VB – żeby to uprościć.
Mam nadzieje, że napisałem w miarę zrozumiale…

Pliki *.CSV to bardzo prosta i elastyczna forma przechowywania informacji. Forma ta charakteryzuje się dużą elastycznością, łatwą edycją i jasną strukturą. Jeśli chcielibyśmy otworzyć plik *.CSV przy pomocy LibreOffica/Excela musielibyśmy najpierw określić jego strukturę:

Wybór separatora jest najważniejszym elementem całego pliku *.CSV, to on oddziela dane od siebie i określa jednocześnie ilość kolumn. Wiersze oddzielone są zawsze za pomocą nowej linii (enter, vbNewLine). Plik poniżej przedstawia takie dane:

Do pobrania tutaj: Dane_Giełdowe

Pierwszy wiersz to nazwy kolumn, a reszta to dane. Często jest tak, że eksportując dane do pliku CSV, nie będziemy mieli tej pierwszej linii lub możemy mieć kilka linii określających kolumny, jak numeracja i nazwa. Tworzymy formę tak jak na obrazku poniżej:

 
Rodzaj elementu Nazwa elementu Ustawienia
Form Form1 Name: Form1
Text: Import/Eksport CSV
Size: 639; 482
DataGridView DataGridView1  Size: 598; 389

Location: 13; 42

Anchor: Top, Bottom, Left, Right

Button Button1 Size: 133; 23

Name: Button1
Text: Pobierz dane CSV
Location: 13;13

Button Button2 Size: 133; 23

Name: Button2
Text: Zapisz dane w CSV
Location: 478; 13

Zaczniemy od wyboru pliku CSV i stworzeniu zestawu danych, które umieścimy w naszym DatagridView, tworzymy uchwyt dla przycisku ładującego dane „Pobierz dane CSV”:

Public Class Form1

    Private ds As New DataSet()    'zestaw danych
    Dim MojeDane As DataTable        'tabele
    Dim Dialog As New OpenFileDialog  'dialog z użytkownikiem, wskazujący lokalizację pliku

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dialog.Filter = "csv, txt files|*.csv;*.txt|All files (*.*)|*.*"
        Dialog.Title = "Wybierz źródło danych."
        Dialog.FileName = ""
        'jeśli plik został wybrany
        If Dialog.ShowDialog() = DialogResult.OK Then
            ds.Tables.Clear() 'czyści zestaw danych na wypadek gdyby były jakieś tabele
            'daje nam to możliwość wielokrotnego używania programu bez konieczności jego restartu.
            'Wprowadzamy znak separatora dzielącego dane (w naszym wypadku będzie to przecinek)
            Dim sep As String = InputBox("Podaj znak separatora?" +
                                         "(pozostawienie pustego pola oznać będzie użycie TABULATORA)" _
                                         , "Separator danych", "")

            '' MojeDane = TworzTabele(Dialog.FileName, sep)
            ds.Tables.Add(MojeDane) ' wstawiamy tabele do zestawu danych
            DataGridView1.DataSource = MojeDane ' Przypisujemy zestaw danych do DataGridView
            'Dzięki takiemu zabiegowi, nie będziemy musieli ręcznie dodawać kolumn i wierszy
        End If
    End Sub
End Class

Nasza Aplikacja nic jeszcze nie robi, ale można już zacząć wybierać dane. Zapiszcie sobie plik Dane_Giełdowe w jakimś znanym miejscu i odblokujcie funkcje:

             MojeDane = TworzTabele(Dialog.FileName, sep)

Funkcja ta ma za zadanie pobrać dane ze wskazanego pliku i przy użyciu wpisanego separatora sformatować dane:

Public Class Form1

    Private ds As New DataSet()    'zestaw danych
    Dim MojeDane As DataTable        'tabele
    Dim Dialog As New OpenFileDialog  'dialog z użytkownikiem, wskazujący lokalizację pliku

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dialog.Filter = "csv, txt files|*.csv;*.txt|All files (*.*)|*.*"
        Dialog.Title = "Wybierz źródło danych."
        Dialog.FileName = ""
        'jeśli plik został wybrany
        If Dialog.ShowDialog() = DialogResult.OK Then
            ds.Tables.Clear() 'czyści zestaw danych na wypadek gdyby były jakieś tabele
            'daje nam to możliwość wielokrotnego używania programu bez konieczności jego restartu.
            'Wprowadzamy znak separatora dzielącego dane (w naszym wypadku będzie to przecinek)
            Dim sep As String = InputBox("Podaj znak separatora?" +
                                         "(pozostawienie pustego pola oznać będzie użycie TABULATORA)" _
                                         , "Separator danych", "")

            MojeDane = TworzTabele(Dialog.FileName, sep)
            ds.Tables.Add(MojeDane) ' wstawiamy tabele do zestawu danych
            DataGridView1.DataSource = MojeDane ' Przypisujemy zestaw danych do DataGridView
            'Dzięki takiemu zabiegowi, nie będziemy musieli ręcznie dodawać kolumn i wierszy
        End If
    End Sub

    Private Function TworzTabele(ByVal SciezkaDoPliku As String, ByVal separator As String) As DataTable
        'separator domyślny
        If separator = "" Then separator = "vbTab"
        'przygotowuje tabelę, do której wstawimy nasze dane i którą nasza funkcja zwróci w odpowiedzi
        Dim Tabela As DataTable = New DataTable("MojaTabela")
        Dim wiersze As DataRow
        Dim wartosc As String()

        'StreamReader będzie otwierał nasz plik, dlatego jeśli plik jest źle odczytywany,
        'nie ma polskich znaków itp. należy zmienić sposób kodowania, w tym wypadku 
        'ustawiony jest UTF8
        Dim rodzajkodu As New IO.StreamReader(SciezkaDoPliku, System.Text.Encoding.UTF8)

        Try
            'pobieramy pierwszą linijkę pliku do zmiennej i dzielimy ją przy użyciu separatora
            wartosc = rodzajkodu.ReadLine().Split(separator)
            'dodajemy nazwy kolum do naszego setu
            For i = 0 To wartosc.Length() - 1
                Tabela.Columns.Add(New DataColumn(wartosc(i).ToString))
            Next
            'Kontynuujemy pobieranie linii pliku, aż do jego kończ
            While rodzajkodu.Peek() <> -1
                'inicjujemy nowy wiersz
                wiersze = Tabela.NewRow
                'dzielimy pobraną linie pliku
                wartosc = rodzajkodu.ReadLine().Split(separator)
                'dodajemy elementy wiersza
                For i = 0 To wartosc.Length() - 1
                    wiersze.Item(i) = wartosc(i).ToString
                Next
                'dodajemy nasz wiersz do zestawu
                Tabela.Rows.Add(wiersze)
            End While
        Catch ex As Exception
            MsgBox("Problem!!! Coś poszło nie tak ;( : " & ex.Message)
            Return New DataTable("Empty")
        Finally
            'zamykamy StreamReader
            rodzajkodu.Close()
        End Try
        'zwracamy nasz zestaw
        Return Tabela
    End Function
End Class

Nasz program już pobiera i wyświetla dane:

Krótki kod a efekt bardzo zadowalający. Oczywiście, jeśli któryś wiersz będzie miał złą ilość kolumn, lub wpiszemy zły separator, możemy otrzymać błąd. Teraz gdy dane wyświetliły się w programie, możemy dokonać ich edycji lub zamiany. Ja posiadam taką aplikację, która zamienia mi wartości w kolumnia4 z 0 na 101, jeśli godzina jest większy lub równy 15:00 a wartość jest 0, jeśli takie są, podświetla na czerwono komórkę w pierwszej kolumnie, wskazując, że w tym wierszu nastąpiła zmiana:

Ostatni element to zapisywanie danych w formacie CSV. Tutaj zrobimy sobie dwa sposoby, jeden będzie zapisywał wszystkie elementy DataGridView a drugi, będzie korzystał z DataSet. Tworzymy uchwyt do drugiego przycisku:

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        'tworzymy dialog, przy użyciu którego wybierzemy miejsce i nazwe nowego pliku
        Dim sfd As New SaveFileDialog
        If sfd.ShowDialog = DialogResult.OK Then
            'dodatkowym elementem o który byłe proszony jest szczególny sposób zapisu 
            Dim czas As DateTime = DateTime.Now
            Dim formatA As String = "HH-mm"
            Dim formatB As String = "HH:mm"
            Dim isd1 As Date = Date.Today.AddDays(1)
            ' (nazwa pliku) + data+ godzina np: /test 2017-08-30 12-46.csv
            Try
                ''ten sposób tworzy nowy plik na podstawie Datagridview
                daneDoCSV(DataGridView1, sfd.FileName & " " & Format(isd1, ("yyyy-MM-dd")) & " " & czas.ToString(formatA) & ".csv")

                ''ten sposób tworzy nowy plik na podstawie DataSet
                '  daneDoCSVzDataset(ds, sfd.FileName & " " & Format(isd1, ("yyyy-MM-dd")) & " " & czas.ToString(formatA) & ".csv")

            Catch ex As Exception
                MsgBox("Coś poszło nie tak: " + ex.ToString)
            Finally
                MsgBox("Zapis udany.")
            End Try
        End If
    End Sub

Kod dla daneDoCSV() wygląda następująco:

    Private Sub daneDoCSV(ByVal dt As DataGridView, ByVal filename As String)
        'inicjujemy StreamWriter
        Dim sw As New IO.StreamWriter(filename, False)
        'sprawdzamy, czy nasz DatagridView zawiera kolumny i wiersze, jeśli nie to nie ma co zapisywać
        'aplikacja nie podejmuje działania
        If dt.Columns.Count < 0 OrElse dt.Rows.Count < 0 Then
            sw.Close()
            Exit Sub
        End If
        'określamy separator danych
        Dim sep As String = InputBox("Wpisz rodzaj separatora oddzielającego dane. 
(pozostawienie pustego pola oznać będzie użycie TABULATORA)", "Separator danych", "")

        'pobieramy nazwy kolumn
        For col As Integer = 0 To dt.Columns.Count - 1
            If Not col = dt.Columns.Count - 1 Then
                'zapisujemy je z separatorem lub domyślnym tabulatorem
                If sep = "" Then
                    sw.Write(dt.Columns(col).HeaderText & ControlChars.Tab)
                Else
                    sw.Write(dt.Columns(col).HeaderText & sep)
                End If
            Else
                'ostatni element zapisujemy bez separatorów
                sw.Write(dt.Columns(col).HeaderText)
            End If
        Next
        'tworzymy nową linijkę
        sw.WriteLine()
        ''zapisujemy wiersze, użyłem (dt.Rows.Count - 2) ponieważ do tabeli można dodawać wiersze,
        'gdybym tego nie zrobił, program dopizał by mi pusty wiersz do pliku
        For row As Integer = 0 To dt.Rows.Count - 2
            'dla każdej kolumny
            For col As Integer = 0 To dt.Columns.Count - 1
                'ostatnia komórka zapisywana jest bez separatora
                If Not col = dt.Columns.Count - 1 Then
                    'pobieramy wartość komórki
                    If sep = "" Then
                        sw.Write(dt.Rows(row).Cells(col).Value & ControlChars.Tab)
                    Else
                        sw.Write(dt.Rows(row).Cells(col).Value & sep)
                    End If
                Else
                    sw.Write(dt.Rows(row).Cells(col).Value)
                End If
            Next
            'następna linia
            sw.Write(Environment.NewLine)
        Next
        sw.Close()
    End Sub

Kod robiący dokładnie to samo przy użyciu DataSet wygląda następująco:

    Private Sub daneDoCSVzDataset(ByVal DtSet As DataSet, ByVal filename As String)
        Dim sw As New IO.StreamWriter(filename, False)

        If DtSet.Tables.Item(0).Columns.Count < 0 OrElse DtSet.Tables.Item(0).Rows.Count < 0 Then
            sw.Close()
            Exit Sub
        End If
        Dim sep As String = InputBox("Wpisz rodzaj separatora oddzielającego dane. (pozostawienie pustego pola oznać będzie użycie TABULATORA)", "Separator danych", "")

        For col As Integer = 0 To DtSet.Tables.Item(0).Columns.Count - 1
            If Not col = DtSet.Tables.Item(0).Columns.Count - 1 Then
                If sep = "" Then
                    sw.Write(DtSet.Tables.Item(0).Columns(col).ToString & ControlChars.Tab)
                Else
                    sw.Write(DtSet.Tables.Item(0).Columns(col).ToString & sep)
                End If
            Else
                sw.Write(DtSet.Tables.Item(0).Columns(col).ToString)
            End If
        Next
        sw.WriteLine()
        For row As Integer = 0 To DtSet.Tables.Item(0).Rows.Count - 1
            For col As Integer = 0 To DtSet.Tables.Item(0).Columns.Count - 1
                If Not col = DtSet.Tables.Item(0).Columns.Count - 1 Then
                    If sep = "" Then
                        sw.Write(DtSet.Tables(0).Rows(row)(col).ToString & ControlChars.Tab)
                    Else
                        sw.Write(DtSet.Tables(0).Rows(row)(col).ToString & sep)
                    End If
                Else
                    sw.Write(DtSet.Tables(0).Rows(row)(col).ToString)
                End If

            Next
            sw.Write(Environment.NewLine)
        Next
        sw.Close()
    End Sub

To by było na tyle. Pełen projekt do pobrania tutaj: ImportCSV

 

Permalink do tego artykułu: https://visualmonsters.cba.pl/eksport-edycja-plikow/

Dodaj komentarz

Twój adres email nie będzie publikowany.