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 ++NotepadW 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:
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”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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:
1 |
MojeDane = TworzTabele(Dialog.FileName, sep) |
Funkcja ta ma za zadanie pobrać dane ze wskazanego pliku i przy użyciu wpisanego separatora sformatować dane:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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