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”:
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







