Obsługa plików w VBA.
Obsługa plików? A co to takiego? Hm... No, po prostu zapisywanie i odczytywanie plików na dysku komputera z poziomu VBA. Do czego nam to potrzebne? Może to być całkiem użyteczna rzecz, oczywiście, jeśli będziemy wiedzieli, do czego to nam może być potrzebne.
Wyobraźmy sobie, że musimy zapisać dane z np. MS Excela do pliku tekstowego. Innym zastosowaniem może być pobieranie danych z pliku tekstowego, uzyskanych np. w wyniku pomiarów do aplikacji.
Do otwarcia pliku w VBA służy instrukcja Open, która umożliwia dostęp do plików w trzech trybach:
Sekwencyjnym (Input, Output i Append) - służy do zapisywana i odczytywania plików tekstowych, takich jak raporty, informacje o błędach (tzw. logi).
Swobodnym (Random) - służy do odczytywania i odczytywania danych bez zamykania pliku. Poprzez dostęp swobodny dane trzymane są w rekordach, co umożliwia łatwy do nich dostęp.
Binarnym (Binary) - służy do zapisywana i odczytywania danych w pliku bajt po bajcie, takich jak zapisywanie czy odczytywanie plików graficznych.
Instrukcja Open nie służy do otwierania plików własnych aplikacji. Nie używajcie instrukcji Open do otwierania plików takich aplikacji jak Word, czy Excel.
W poniżej tabeli przedstawiłem typy dostępu i odpowiednie dla nich instrukcje, omówione poniżej.
Typ dostępu |
Intrukcje do otwarcia |
Zapisywanie danych |
Odczytywanie danych |
sekwencyjny |
Append, |
Print #, |
Input #, |
swobodny |
Random |
Put |
Get |
binarny |
Binary |
Put |
Get |
W niniejszej lekcji skupimy się na dostępie sekwencyjnym. Dlaczego? Bo inne tryby są skomplikowane, niezrozumiałe, przydatne dla programistów z wyższej półki. Ale tacy nie programują w VBA, tylko w C++ albo w Javie.
No dobrze, może się to czasem komuś przydać, i nie tak trudno to zrozumieć, ale najpierw należy poznać podstawy obsługi plików w VBA. Zajmijmy się nimi już teraz.
Co więc musimy zrobić, by zapisać czy odczytać dane z/do pliku? Schemat postępowania jest następujący:
Musimy otworzyć plik,
Musimy dane:
zapisać
odczytać,
Wypada zamknąć plik.
1. Otwieranie pliku.
Plik otwieramy przy użyciu komendy Open. Jej składnia wygląda tak:
Open scieżka For tryb [dostęp] [blokada] As [#]numer_pliku [Len = dł_rek]
Wygląda na skomplikowaną, prawda? Bo jest skomplikowana. Nie będą nam wszystkie jej opcje potrzebne, jednak wymienię je dla porządku i po krótce omówię,
scieżka |
To po prostu ścieżka do pliku, który chcemy otworzyć. Jeśli otwieramy plik do zapisu (Output) to plik zostanie utworzony. Przykładowa ścieżka może wyglądać tak: |
tryb |
Argument tryb określa sposób dostępu do pliku. Chodzi oczywiście o dostęp ze strony VBA, czyli jak VBA ma traktować otwarty plik i w jaki sposób ma zapisywać czy odczytywać dane do niego. A może to robić na kilka sposobów:
|
dostęp |
Słowo kluczowe określające dozwolone operacje na otwartym pliku może przybierać wartości:
Słowo to nie będzie ono miało zastosowania w omawianym przez nas dostępie sekwencyjnym, bo możliwość zapisu czy odczytu określają w trybach sekwencyjnych słowa kluczowe Append, Output i Input. |
blokada |
Opcjonalny argument określający czy inne programy mogą mieć dostęp do otwartego przez nas pliku.
|
numer_pliku |
Numer pliku z zakresu 1 do 511. Poprzez ten numer instrukcje zapisu i odczyt będą miały dostęp do naszego pliku. |
dł_rek |
Składnik opcjonalny. Liczba mniejsza lub równa 32 767. Dla plików otwartych w trybie Random jest to długość rekordu w bajtach. Dla plików otwartych w trybach sekwencyjnych to ilość buforowanych znaków. |
A oto przykładowe instrukcje otwarcia plików.
Widać, że nie są one skomplikowane jak można by się sugerować podaną powyżej składnią instrukcji Open.
Open "c:\plik.txt" For Output As #1
Otwieramy do zapisu plik plik.txt z katalogu głównego na dysku c:\. Jeśli plik nie istnieje, zostanie utworzony, natomiast wszelkie dane z istniejącego pliku zostaną usunięte.
Open "c:\plik2.txt" For Append As #2
Otwieramy do zapisu plik plik.txt z katalogu głównego na dysku c:\. Jeśli plik nie istnieje, zostaje utworzony. Jeśli plik istnieje, dane będą dopisywane do istniejących w tym pliku.
Open "c:\plik.txt" For Input # As #3
Otwieramy do odczytu plik plik.txt z katalogu głównego na dysku c:\. Jeśli plik nie istnieje, pojawi się komunikat błędu.
Musimy jeszcze powiedzieć o szczegółach. Jeśli otwierany plik nie istnieje zostaje utworzony, jeśli otwierany jest w trybach Append i Output.
Gdy otwieramy plik, który poprzednio został już otwarty przez inny program lub po prostu nie zamknięty przy poprzednim wywołaniu naszego makra nastąpi błąd.
2a. Zapis do pliku.
Wyobraźmy sobie, że otworzyliśmy już jakiś plik, możemy więc przystąpić do operacji na nim. Ale jak? Dostęp do pliku po jego otwarciu jest możliwy tylko poprzez jego numer, podany jako argument instrukcji. Instrukcje zapisu i odczytu będą potrzebowały tego numeru, by móc zapisywać czy tez odczytywać dane z tego pliku. Co mamy zrobić, jeśli musimy otworzyć kilka plików, ale nie chce nam się (lub też nie jesteśmy w stanie) określić numerów tych plików? Na szczęście istnieje funkcja FreeFile(), która zwraca pierwszy wolny numer.
Jej składna wygląda następująco:
FreeFile([zakres_numerów])
Opcjonalny argument tej funkcji zakres_numerów określa zakres, z którego będziemy pobierać numery. Podanie wartości 0 lub pominięcie tego argumentu podaje numer pliku z zakresu 1 - 255. Podanie 1 zwraca numer z zakresu 256 - 511.
intNumer = FreeFile()
Open "c:\plik.txt" For Input # As intNumer
Aha, pamiętajcie by nigdy, ale to nigdy nie otwierać pliku tak:
Open "c:\plik.txt" For Input # As FreeFile()
Dlaczego, przecież działa tak samo? No tak, ale tylko przy otwarciu pliku. Potem już mamy problem. Otwarliśmy plik, ale nie mamy jego numeru w żadnej zmiennej. Nie możemy na nim operować (ani go zamknąć), bo instrukcje zapisu i odczytu potrzebują tego numeru, a my go przecież nie mamy!
Tyle tytułem wstępu, przejdźmy więc do omówienia instrukcji służący do zapisywania i odczytywania z i do pliku.
W trybach sekwencyjnych, tzn, Output , Input i Append ) mamy dwie instrukcje zapisu: Print # i Write # oraz dwie instrukcje zapisu: Input # i Line Input # .
Składnia instrukcji Print # wygląda tak:
Open "c:\plik.txt" For Input # As FreeFile()
numer_pliku |
Numer otwartego pliku. |
lista_wyrażeń |
Wyrażenie lub lista wyrażeń do zapisu. |
Jako lista_wyrażeń możemy podać:
Wyrażenie Spc(numer) - wówczas zapisana zostanie do pliku podana ilość spacji,
Wyrażenie Tab(numer) - określa numer kolumny, w którym zapisuje my dane.
Można nie podawać argumentu numer, a wówczas... zresztą poczytajcie sobie, jeśli was to interesuje w pomocy VBA.
Następnie możemy (ale nie musimy) podać listy zmiennych do zapisania. Jest najważniejsza część instrukcji Print #, to tutaj następuje zapis wartości, które chcemy umieścić w pliku. Jeśli chcemy zapisać wiele wyrażeń, musimy oddzielić je spacjami albo przecinkami.
Ostatni argument, również nieobowiązkowy oznacza pozycję następnej zapisywanej zmiennej. Jeśli zostanie on pominięty, to następna zmienna zastanie zapisana w nowej linii. Jeśli podamy ten argument, jako ";" czyli znak średnika to następna zapisywana zmienna zapisana zostanie umieszczona w tej samej linii, co zapisana aktualnie. Więcej o tej opcji można poczytać w pomocy do VBA.
Dane zapisane przy użyciu instrukcji Print # zwykle odczytujemy z pliku przy użyciu instrukcji Line Input # .
Instrukcja Print # zapisuje liczby w postaci zależnej od ustawień lokalnych systemu, to znaczy, jeśli jako znak dziesiętny w naszym systemie ustawiony jest przecinek, to przecinek zostanie zapisany do pliku.
Instrukcja Print # lepiej nadaje się do zapisywania informacji, które potem użytkownik będzie odczytywał poprzez otwarcie pliku tekstowego w np. notatniku. Mogą to na przykład być logi, czyli meldunki, zapisywane przez program w trakcie jego wykonywania.
Jeśli kiedyś przyjdzie nam do głowy odczytać zmienne zapisane do pliku to albo musimy po poszczególnych zmiennych postawić tzw. separator. Separator umożliwia instrukcji odczytującej Input # prawidłowe rozdzielnie odczytywanych danych.
Separatorem może być przecinek, albo znak tabulacji.
Mówiąc prostymi słowami, musimy zrobić tak:
Print # 2, a "," b "," c
albo
Print # 2, a, "," , b, ",", c
albo, ze znakiem tabulacji:
Print # 2 a Tab b Tab c
Ech, wygląda to niedobrze, prawda? Po za tym jest trochę nieczytelne.
Zapomnijcie więc o tym sposobie.
Do zapisu danych, które potem będziemy odczytywać lepiej jest użyć instrukcji Write #.
Składnia Write # :
Write # numer_pliku, [lista_wyrażeń]
numer_pliku |
Oczywiście, to jest numer otwartego pliku. |
lista_wyrażeń |
Wyrażenie lub lista wyrażeń do zapisu. |
Parametr lista_wyrażeń nie jest parametrem obowiązkowym, ale to tutaj zapisujemy nasze zmienne, jako oddzielone przecinkiem zmienne. Jeśli go nie podamy, wówczas do pliku zostanie zapisana pusta linia. Dane zapisane przy użyciu instrukcji Write # zwykle odczytujemy z pliku przy użyciu instrukcji Input #. Jeśli zapiszemy dane jako:
Write #1, a, b, c
To odczytamy jako:
Input #1, a, b, c
Każda zmienna w instrukcji Write # ma odpowiednik w instrukcji Input # . Innymi słowy instrukcja Write # zapisuje dane pogrupowane w kolumny podobnie jak wyglądają one w Excelu czy też w bazach danych.
W odróżnieniu od Print # , instrukcja Write # zapisuje liczby zawsze z kropką jako znakiem dziesiętnym.
Dlaczego? Po pierwsze, dlatego, że pomiędzy zmienne zawsze wstawia przecinki. Gdyby jako znaki dziesiętne wstawiałaby również przecinki instrukcja Input # miałaby kłopoty z prawidłowym odczytem tych danych.
Po drugie, dlatego, że dane te powinny być jednakowo traktowane przez wszystkie komputery, niezależnie od ustawień lokalnych na nich.
Wyobraźmy sobie, że mamy firmę z siedziba w Pacanowie, w której przygotowano cennik artykułów w postaci pliku tekstowego, a znakiem dziesiętnym jest u nas przecinek Wysyłamy ten cennik do naszego mało ważnego oddziału w Nowym Jorku, w którym komputery maja ustawiony jako znak dziesiętny kropkę. Nie uda się nawet wybitnym informatykom amerykańskim zaimportować tych danych, bo VBA nie rozpozna zapisanych danych!
Dodatkowo Write # zawsze zapisuje zmienne typu String w cudzysłowu. Znaczy to, że samych znaków cudzysłowu nie możemy zapisać do pliku instrukcja Write # w takiej postaci, by móc je potem prawidłowo odczytać.
Potrafimy już otworzyć plik i zapisać do niego dane. No ale wyobraźmy sobie, że chcemy zapisać zawartość tablicy do pliku. Jak tego dokonać?
To proste, wykorzystamy pętle.
Jeśli tablica ma n elementów numerowanych od 1 do n, to napiszemy pętlę, która będzie przebiegać po numerach od 1 do n i w każdym przebiegu zapisze jeden z elementów naszej tablicy.
Option Base 1
'Tę deklarację należy umieścić na samej górze modułu
Sub ZapisPliku()
'deklarujemy tablicę
nasza_tablica = Array(3, 45, 34, 46, 12, 42)
Open "c:\tablica.txt" For Output As #1
'mamy 6 elementów
For i = 1 To 6
Write #1, nasza_tablica(i)
Next i
Close #1
End Sub
Proste, prawda?
2. b. Odczyt danych z pliku.
Do odczytu danych posługujemy się instrukcją Input # , która odczytuje dane z pliku i przypisuje do zmiennych.
Składnia instrukcji Input # .
Input #numer_pliku,lista_zmiennych
numer_pliku |
Tak, to już wiemy. Numer pliku. |
lista_zmiennych |
Lista zmiennych, do których będziemy przypisywać odczytane z pliku dane. |
Znaki cudzysłowu (" ") obejmujące odczytywaną daną są ignorowane.
Wiemy jak wyglądają składnie instrukcji do odczytu danych, ale to za mało by odczytać dane z pliku.
Musimy wiedzieć, że po otwarciu pliku VBA ustawia tzw. wskaźnik, wskazujący na bieżącą linię otwartego pliku. Po otwarciu pliku ustawiony on jest na pierwszej linii, gdy odczytamy tę linię wskaźnik automatycznie przejdzie do linii następnej. Odczytanie wszystkich linii może więc być zrealizowane w pętli.
W tym przypadku mamy sześć wierszy, pętla może przebiegać od 1 do 6. Ale innym razem możemy tych linii mieć więcej. Jeśli linii byłoby mniej, niż powtórzeń w pętli pojawiłby się komunikat o błędzie.
Na szczęście mamy funkcję, która informuje nas czy osiągnęliśmy koniec pliku i wykorzystamy ją w programie, który odczyta plik tekstowy c:\tablica.txt, utworzony przy użyciu instrukcji Write # :
Sub OdczytPliku()
'deklarujemy tablicę. Nie wiemy ile będzie miała ona elementów, więc posłużymy się
'tzw. deklaracją dynamiczną.
Dim nasza_tablica() as Integer
'Otwieramy plik
Open "c:\tablica.txt" For Input # As #1
'odczytujemy w pętli dane z pliku
Do While Not EOF(1) Redim Preserve nasza_tablica(1 to i)
Input #1, nasza_tablica(i)
i=i+1
Loop
Close #1
End Sub
W wyniku działania tego programu zawartość pliku została przepisana do tablicy nasza_tablica.
Drugą instrukcją służącą do odczytu danych jest Line Input #.
Instrukcja Line Input # czyta z pliku całą linię i postawią j ą do podanej zmiennej typu String, czyli łańcucha znaków.
Jej składania jest następująca:
Line Input #numer_pliku, nazwa_zmiennej
numer_pliku |
Ach, to już było. To przecież numer pliku. |
nazwa_zmiennej |
Nazwa zmiennej, do której postawiamy odczytaną linię. |
Line Input # jest używana do odczytywania danych zapisanych instrukcją Print #.
Również przy odczycie danych z jej użyciem możemy korzystać z funkcji EOF().
3. Zamykanie pliku.
Dobrym zwyczajem jest zamykanie pliku po jego użyciu. Dlaczego? Bo należy trzymać porządek. Po ugotowaniu i zjedzeniu obiadu zmywamy naczynia a po dokonaniu operacji na pliku zamykamy go. Jasne?
Po za tym próba otwarcia pliku, który nie został zamknięty spowoduje pojawienie się komunikatu o błędzie.
Do zamykania pliku używamy instrukcji Close :
Close [lista_plików]
Jako argument lista_plików należy podać numer pliku, pod jakim otworzyliśmy dany plik.
Możemy też podać listę numerów plików, które są otwarte:
Close #1, #4
Jeśli nie podamy dla instrukcji Close żadnego argumentu, zamknięte zostaną wszystkie pliki, otwarte przy użyciu instrukcji Open.
Po zamknięciu pliku skojarzenie pliku z jego numerem zostaje utracone.
To wszystko, co powinniście wiedzieć o operacjach na plikach w VBA. To także wszystko, co kiedykolwiek było mi potrzebne przy pisaniu moich programów. Tych, którzy chcą się dowiedzieć więcej odsyłam do pomocy VBA, dostępnej chociażby w pakiecie MS Office.