05 - Procedury


W tej lekcji nauczymy się wywoływać procedury. Poznamy także sposoby zatrzymywania makra w trakcie jego działania.

Lekcja została podzielona na następujące części:

Wywoływanie procedur

Jak dowiedzieliśmy się w drugiej lekcji, cały kod wykonujący zadania musi być umieszczony w procedurach (Sub) lub funkcjach (Function). W tej samej lekcji wspomniano też, że każde zadanie powinno znaleźć się w oddzielnej procedurze. Nauczyliśmy się również kilku sposobów ręcznego uruchamiania makr.

Wyobraźmy więc teraz sobie, że chcemy napisać makro wykonujące dziesięć różnych czynności. Zgodnie ze wspomnianą wyżej zasadą oddzielania od siebie zadań, trzeba w tym celu stworzyć dziesięć różnych procedur. Sam przyznasz, że ręczne uruchamianie każdej z tych dziesięciu procedur nie byłoby zbyt wygodne - musielibyśmy uruchomić pierwszą procedurę, zaczekać aż skończy się wykonywać, następnie uruchomić drugą, zaczekać aż skończy się wykonywać itd.

Na szczęście Visual Basic umożliwia automatyczne uruchamianie potrzebnych procedur, jedna po drugiej.

Aby pokazać jak działa automatyczne uruchamianie procedur wykorzystamy oklepany już nieco przykład wyświetlania w arkuszu kolejnych potęg dwójki i trójki. Dla utrudnienia załóżmy, że makro ma ponadto wyświetlać kolejne potęgi czwórki i piątki.

Aby być wiernym zasadzie rozdzielania od siebie poszczególnych zadań, należy stworzyć oddzielną procedurę dla każdej liczby wyjściowej, łącznie trzeba więc napisać cztery różne procedury:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Sub potegiDwojki()
    Dim potega As Long

    potega = 1
    Worksheets("Arkusz1").Cells(1, 1) = potega

    potega = potega * 2
    Worksheets("Arkusz1").Cells(2, 1) = potega

    potega = potega * 2
    Worksheets("Arkusz1").Cells(3, 1) = potega

    potega = potega * 2
    Worksheets("Arkusz1").Cells(4, 1) = potega

    potega = potega * 2
    Worksheets("Arkusz1").Cells(5, 1) = potega

End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Sub potegiTrojki()
    Dim potega As Long

    potega = 1
    Worksheets("Arkusz1").Cells(1, 2) = potega

    potega = potega * 3
    Worksheets("Arkusz1").Cells(2, 2) = potega

    potega = potega * 3
    Worksheets("Arkusz1").Cells(3, 2) = potega

    potega = potega * 3
    Worksheets("Arkusz1").Cells(4, 2) = potega

    potega = potega * 3
    Worksheets("Arkusz1").Cells(5, 2) = potega

End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Sub potegiCzworki()
    Dim potega As Long

    potega = 1
    Worksheets("Arkusz1").Cells(1, 3) = potega

    potega = potega * 4
    Worksheets("Arkusz1").Cells(2, 3) = potega

    potega = potega * 4
    Worksheets("Arkusz1").Cells(3, 3) = potega

    potega = potega * 4
    Worksheets("Arkusz1").Cells(4, 3) = potega

    potega = potega * 4
    Worksheets("Arkusz1").Cells(5, 3) = potega

End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Sub potegiPiatki()
    Dim potega As Long

    potega = 1
    Worksheets("Arkusz1").Cells(1, 4) = potega

    potega = potega * 5
    Worksheets("Arkusz1").Cells(2, 4) = potega

    potega = potega * 5
    Worksheets("Arkusz1").Cells(3, 4) = potega

    potega = potega * 5
    Worksheets("Arkusz1").Cells(4, 4) = potega

    potega = potega * 5
    Worksheets("Arkusz1").Cells(5, 4) = potega

End Sub

Zwróć uwagę, że zmienna potega w każdej procedurze jest deklarowana na nowo.

Dzieje się tak, ponieważ zmienna zadeklarowana za pomocą słowa kluczowego Dim działa tylko wewnątrz procedury, w której jest zadeklarowana. W późniejszej części kursu poznasz inne sposoby deklaracji zmiennych, dzięki którym zmienne są widoczne w wielu procedurach równocześnie.

Gdybyś chciał teraz ręcznie uruchomić wszystkie te procedury, zajęłoby to trochę czasu - musiałbyś po kolei ustawiać kursor w każdej z nich, wciskać klawisz F5 (albo uruchamiać je innym sposobem omówionym w części kursu poświęconej uruchamianiu makr), czekać aż zakończy ona swoje działanie, przejść do następnej, itd. Krótko mówiąc - niepotrzebne marnowanie czasu i energii.

Poniżej znajduje się dodatkowa procedura, której jedynym zadaniem jest kolejne wywoływanie każdej z czterech stworzonych przed chwilą procedur, dzięki czemu proces ich uruchamiania zostaje skrócony do uruchomienia jednego makra.

1
2
3
4
5
6
Sub wywolajWszystkieProcedury()
    Call potegiDwojki
    Call potegiTrojki
    Call potegiCzworki
    Call potegiPiatki
End Sub

Jak łatwo się domyślić patrząc na powyższy kod, wywołanie jakiejś procedury z wnętrza innej procedury odbywa się poprzez napisanie słowa kluczowego Call oraz nazwy wywoływanej procedury.

Przy wywoływaniu procedur można pominąć słowo kluczowe Call i napisać samą nazwę procedury.

Jednak dla jasności kodu, warto każdorazowo stosować polecenie Call i tak też będzie to praktykowane podczas niniejszego kursu.

Oprócz czytelności kodu, inną zaletą wynikającą ze stosowania słowa kluczowego Call jest możliwość bardzo łatwego wyszukiwania wszystkich wywołań danej procedury.

Wywoływanie procedur z argumentami

Jeżeli przyjrzysz się omówionym powyżej procedurom wyświetlającym potęgi poszczególnych liczb, zauważysz, że wszystkie są niemal identyczne i różnią się tylko liczbą podnoszoną do potęgi oraz numerem kolumny, w której wyświetlane są wyniki.

Sprawia to, że wielokrotnie powtarzane są te same fragment kodu, czego kategorycznie powinno się unikać poprzez tworzenie procedur uogólniających.

Poniżej zostało wypisanych kilka fragmentów kodu, z powyższego przykładu, które powtarzają się w każdej z czterech procedur:
 
Dim potega As Long
 
potega = 1
 
potega = potega * _
 
Worksheets("Arkusz1").Cells(2, _) = potega

W takiej sytuacji doskonałym wyjściem byłoby napisanie jakiejś ogólnej procedury, w której zmianie ulegałaby tylko liczba podnoszona do potęgi i numer kolumny z danymi, a cała reszta byłaby wspólna.

Taka ogólna procedura dla wyświetlania potęg została przedstawiona w poniższej ramce:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Sub wyswietlajPotegi(liczba As Byte, numerKolumny As Byte)
    Dim potega As Long

    potega = 1
    Worksheets("Arkusz1").Cells(1, numerKolumny) = potega

    potega = potega * liczba
    Worksheets("Arkusz1").Cells(2, numerKolumny) = potega

    potega = potega * liczba
    Worksheets("Arkusz1").Cells(3, numerKolumny) = potega

    potega = potega * liczba
    Worksheets("Arkusz1").Cells(4, numerKolumny) = potega

    potega = potega * liczba
    Worksheets("Arkusz1").Cells(5, numerKolumny) = potega

End Sub

Wykasuj więc teraz z edytora VBA wszystkie cztery procedury, które wcześniej stworzyłeś i zastąp je powyższą procedurą wyswietlajPotegi. Procedury z poprzedniego przykładu były użyte tylko po to, aby pokazać ideę wywoływania procedur. Zasadniczo w takich sytuacjach należy tworzyć właśnie takie ogólne procedury, jak prezentowana tutaj procedura wyswietlajPotegi.

Spróbuj teraz uruchomić procedurę wywolajWszystkieProcedury, która uruchamia pozostałe procedury.

Oczywiście procedura ta nie zadziała! A to dlatego, że procedury, które wywoływała, zostały już usunięte.

Jeżeli makro będzie próbowało wywołać procedurę, która nie istnieje, edytor wyświetli błąd Compile error: Sub or Function not defined.

Przed uruchomieniem makra w nowej postaci, procedura wywolajWszystkieProcedury musi jeszcze zostać dostosowana do nowych okoliczności. Jej nowa postać będzie wyglądała tak:

1
2
3
4
5
6
Sub wywolajWszystkieProcedury()
    Call wyswietlajPotegi(2, 1)
    Call wyswietlajPotegi(3, 2)
    Call wyswietlajPotegi(4, 3)
    Call wyswietlajPotegi(5, 4)
End Sub

Teraz nie powinno już być żadnych problemów i po uruchomieniu makro wyświetli w arkuszu Arkusz1oczekiwane wyniki.

Zwróć uwagę, jak bardzo udało się skrócić cały kod, dzięki zastąpieniu każdej z poszczególnych procedur jedną procedurą uogólniającą.

W zmodyfikowanej wersji procedury wyswietlajPotegi pojawiły się nowe elementy języka VBA, które należy wyjaśnić.

W pierwszym wierszu procedury musi być ona oczywiście otwarta słowem kluczowym Sub.

 
Sub wyswietlajPotegi(liczba As Byte, numerKolumny As Byte)

Tę część znasz już bardzo dobrze, bo pojawiła się już wielkrotnie podczas tego kursu.

Jednak oprócz tradycyjnego otwarcia procedury pojawia się w niej jeszcze dodatkowy element - argumenty wejściowe, które zostały podane w nawiasie po nazwie procedury.

Wiersz otwarcia procedury z argumentami wejściowymi ma następującą postać ogólną:

 
Sub nazwaProcedury(arg1 As typ, arg2 As typ..., argN As typ)

W opisywanym przykładzie zostało określone, że procedura wyswietlajPotegi posiada dwa argumenty wejściowe:
  • argument liczba typu Byte (a więc liczba z zakresu 0-255), który będzie określał jaka liczba jest podnoszona do potęgi,
  • argument numerKolumny typu Byte (a więc liczba z zakresu 0-255), który będzie określał w której kolumnie arkusza wyświetlone zostają wyniki,
a więc od teraz przy każdym wywołaniu tej procedury, po słowie kluczowym Call i jej nazwie, w nawiasie będą musiały zostać również podane wartości tych dwóch argumentów.

Zadeklarowanie argumentów wejściowych oznacza, że aby wywołać potem daną procedurę konieczne będzie podanie tych argumentów.

Jeżeli będziesz próbował wywołać procedurę wymagającą argumentów, bez podania tych argumentów, edytor wyświetli błąd Compile error: Argument not optional .

Podanie zmiennej jako argumentu w linii otwarcia procedury jest równoznaczne z jej normalnym zadeklarowaniem w danej procedurze (więcej informacji na temat deklarowania zmiennych). Oznacza to, że w tej procedurze nie może już zostać zadeklarowana zmienna o takiej samej nazwie.

W drugim wierszu procedury wyswietlajPotegi zostaje zadeklarowana zmienna potega typu Long. W tym momencie w procedurze są więc już zadeklarowane trzy zmienne: potega oraz dwie zmienne, które pojawiły się w wierszu otwarcia - liczba i numerKolumny.

W wierszu 4 do zmiennej potega zostaje przypisana początkowa wartość 1. W kolejnej linijce jest ona wyświetlana w arkuszu Arkusz1, w pierwszym wierszu tego arkusza i kolumnie o takim numerze, jaki jest podany jako argument wejściowy numerKolumny.

W wierszu 7 wartość zmiennej potega zostaje pomnożona przez wartość argumentu liczba, który jest podawany przy wywoływaniu procedury.

W dalszej części tej procedury wszystko odbywa się według scenariusza znanego już z poprzednich przykładów.

Zmianie uległa również procedura wywolajWszystkieProcedury.

Zamiast wywoływać cztery różne procedury, tak jak miało to miejsce wcześniej, wywołuje ona teraz czterokrotnie tę samą procedurę wyswietlajPotegi, zmieniając tylko wartości podawanych do nich argumentów.

I tak na przykład w wierszu 2, procedura wyswietlajPotegi wywoływana jest z argumentami wejściowymi 2 oraz 1:
2
Call wyswietlajPotegi(2,1)
co oznacza, że liczbą, która będzie podnoszona do potęgi będzie 2, a wyniki będą wypisywane w pierwszej kolumnie arkusza Arkusz1.

Analogicznie w trzecim wierszu wywoływana jest procedura wyswietlajPotegi, która podnosi do potęgi liczbę 3 i wyświetla wyniki w drugiej kolumnie, itd.

Mimo, że w procedurze wyswietlajPotegi argumenty wejściowe nie zmieniają się w trakcie wykonywania tej procedury, sytuacja taka byłaby całkowicie poprawna.

Zmodyfikuj teraz procedurę wyswietlajPotegi, aby wyglądała tak jak poniżej (nowe wiersze zostały podświetlone na czerwono):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sub wyswietlajPotegi(liczba As Byte, numerKolumny As Byte)
    Dim potega As Long

    liczba = 2
    potega = 1
    Worksheets("Arkusz1").Cells(1, numerKolumny) = potega

    potega = potega * liczba
    Worksheets("Arkusz1").Cells(2, numerKolumny) = potega

    potega = potega * liczba
    Worksheets("Arkusz1").Cells(3, numerKolumny) = potega

    potega = potega * liczba
    Worksheets("Arkusz1").Cells(4, numerKolumny) = potega

    potega = potega * liczba
    Worksheets("Arkusz1").Cells(5, numerKolumny) = potega

End Sub

Jak widzisz, w wierszu 4 do argumentu liczba jest przypisywana wartość 2.

W takiej sytuacji, niezależnie od tego, jaka wartość argumentu liczba zostanie podana przy wywoływaniu tej procedury, zawsze będzie ona podnosić do potęgi liczbę 2.

Na koniec poruszymy jeszcze jedną ważną kwestię, związaną z wywoływaniem procedur z argumentami.

Wklej do edytora poniższy kod:
1
2
3
4
5
6
7
8
Sub zarobki()
    Dim pensja As Long

    pensja = 2500
    Call odliczeniePodatku(pensja)
    Cells(1, 1) = pensja 'To jest Twoja wypłata netto

End Sub
1
2
3
4
5
Sub odliczeniePodatku(podstawa As Long)
    podstawa = podstawa - (podstawa * 0.18)
    'tu mogą dziać się jeszcze jakieś inne rzeczy
    'na przykład wpłacenie podatku do urzędu skarbowego
End Sub

Do przedstawienia tego zagadnienia celowo wybrałem tak życiowy przykład, który każdy odczuwa co miesiąc na własnej skórze, aby dobrze przemówił do Twojej wyobraźni.

Procedura zarobki jest główną procedurą, natomiast odliczeniePodatku, jest wywoływane z poziomu procedury zarobki w trakcie jej wykonywania.

Co się stanie po uruchomieniu tego kodu? Poniżej znajduje się szczegółowa analiza wydarzeń.

W drugim wierszu procedury zarobki zadeklarowano zmienną liczbową pensja, a po chwili zostaje do niej przypisana wartość 2500.

W wierszu 5 wywoływana jest procedura odliczeniePodatku, która wymaga podania argumentu liczbowego podstawa (aby odliczyć podatek trzeba przecież znać wysokość pensji brutto). Jako ten argument podawana jest oczywiście zmienna pensja, która cały czas ma jeszcze wartość 2500.

W momencie wywołania procedury odliczeniePodatku wykonywanie całego makra przenosi się do tej właśnie procedury i dopiero po jej zakończeniu powróci z powrotem do procedury zarobki, do miejsca, w którym ją opuściło.

W tym momencie kompilator znajduje się więc w procedurze odliczeniePodatku, w którym zadeklarowana jest jedna zmienna podstawa (nie była ona zadeklarowana za pomocą słowa kluczowego Dim, ale występuje jako argument w wierszu startowym procedury, co jak wspomniano kilka akapitów wcześniej jest równoznaczne z jej zadeklarowaniem).

Pamiętaj, że w momencie wywołania procedury, jako argument podstawa nie została podana liczba 2500, ale zmienna pensja, która miała wartość 2500. O tym, że jest to zasadnicza różnica możesz się przekonać w wierszu 2 procedury odliczaniePodatku. Wtedy to od argumentu podstawa zostaje odjęte 18% jej wartości, a więc przyjmie on teraz wartość 2050.

Fakt, że jako argument podstawa podana została zmienna pensja, sprawia, że filozoficznie mówiąc, jest to jeden i ten sam byt.

A więc jeżeli wartość argumentu podstawa zmieniła się na 2050, to w tym samym momencie wartość zmiennej pensja znajdującej się w procedurze zarobki również zmieniła się na 2050, ponieważ argument podstawa i zmienna pensja to w rzeczywistości jedno i to samo.

Gdyby tak nie było, po odliczeniu podatku od podstawy, którą jest pensja, pensja pozostawałaby na niezmienionym poziomie. Niestety, jak widzisz, takie piękne rzeczy nie zdarzają się nawet w programowaniu.

Następnie w procedurze odliczeniePodatku mogą dziać się jeszcze jakieś operacje, które zostały tylko opisane komentarzem w wierszach 3-4, ponieważ nie są istotne dla omawianego tu zagadnienia.

Istotne jest natomiast to, co dzieje się po powrocie makra do wykonywania procedury zarobki. Makro wraca do niej, po zakończeniu procedury odliczeniePodatku i rozpoczyna dalsze działanie od następnego wiersza (czyli w tym przypadku od wiersza 7).

W procedurze zarobki zmienna pensja nie ma już wartości 2500, ale 2050 (bo taka wartość została jej nadana wewnątrz procedury odliczaniePodatku, do której ta zmienna została przekazana).

W wierszu 6 wartość zmiennej pensja zostaje wyprintowana do arkuszaArkusz1, tak, abyś po uruchomieniu makra mógł się przekonać na własne oczy, że w komórce A1 rzeczywiście widnieje liczba 2050.

Podsumowując opisany powyżej przykład: jeżeli jakaś zmienna zostaje przekazana jako argument do wywoływanej procedury, to po powrocie do funkcji bazowej może mieć już całkiem inną wartość.

W tym miejscu jeszcze raz trzeba poruszyć kwestię używania słowa kluczowego Call przy wywoływaniu procedur.

Jak wspomniałem wcześniej, procedura może być wywołana poprzez napisanie w linijce samej nazwy, bez poprzedzania jej słowem kluczowym Call.

W sytuacji, gdy procedura nie posiada żadnych argumentów wejściowych, oba sposoby wywołania nie różnią się zbytnio od siebie, a wiersz jej wywołania może wyglądać tak:
 
Call wywoływanaProcedura
lub tak:
 
wywoływanaProcedura

Jeżeli jednak wywoływana procedura wymaga podania jakichś argumentów, musisz pamiętać o jednej zasadzie - pomijając słowo kluczowe Call, nie umieszczasz argumentów w nawiasie, tylko wypisujesz po nazwie procedury oddzielając przecinkami.

Poniżej znajduje się przykład poprawnego wywołania procedury z argumentami przy użyciu słowa kluczowego Call oraz bez tego słowa:
 
Call odliczaniePodatku(pensja, stawka)
 
odliczaniePodatku pensja, stawka

Pamiętaj jednak, że stosowanie słowa kluczowego Call jest o wiele lepszym wyjściem, ponieważ to tylko 4 znaki, a pomaga zachować porządek i przejrzystość w kodzie oraz ułatwia późniejsze wyszukiwanie wywołań danej procedury.

Wstrzymywanie działania makra

Pisząc dłuższe programy często będziesz miał potrzebę zatrzymać w którymś momencie ich działanie, żeby przekonać się czy wszystkie polecenia do określonego momentu wykonują się poprawnie lub aby sprawdzić aktualną wartość zmiennych.

W tym celu możesz ustawiać tzw. punkty zatrzymania (toogle breakpoints). Jeżeli ustawisz w makrze punkty zatrzymania, to po jego uruchomieniu zawsze zawiesza ono swoje działanie w miejscu, w którym ustawiony jest taki punkt.

Punkty zatrzymania oznaczone są w edytorze VBA poprzez podświetlenie całego wiersza kodu na brązowo oraz umieszczenie brązowej kropki z lewej strony wiersza.

Punkt zatrzymania w edytorze VBA

Ustawienie punktu zatrzymania jest bardzo proste i może być wykonane na kilka sposobów:
  • najszybszym sposobem jest ustawienie kursora w wierszu, w którym chcesz ustawić punkt zatrzymania i kliknięcie klawisza F9. Sposób ten jest zdecydowanie najwygodniejszy, gdyż nie wymaga odrywania rąk od klawiatury.
  • drugim sposobem jest ustawienie się w wierszu, w którym ma być ustawiony punkt zatrzymania i kliknięcie ikony przedstawiającej białą dłoń na pasku narzędzi Edit (na poniższym rysunku otoczona czerwoną ramką).

    Ikona punktu zatrzymania

  • trzecim sposobem jest kliknięcie na szarym pasku z lewej strony edytora kodu (na poniższym rysunku otoczony czerwoną ramką), na wysokości wiersza, w którym ma zostać ustawiony punkt zatrzymania.

    Ustawienie punktu zatrzymania poprzez kliknięcie paska

Jedną z ogromnych zalet zatrzymywania makra w trakcie jego działania jest możliwość sprawdzenia aktualnej wartości zmiennych.

Na poniższym rysunku widzisz printscreena z wykonywania makra odliczającego podatek z poprzedniego przykładu. W makrze tym punkt zatrzymania został ustawiony w szóstej linijce procedury zarobki, tuż przed wyprintowaniem wartości zmiennej pensja do arkusza.

Podpatrywanie wartości zmiennych

W momencie zrzucania printscreena kursor był ustawiony na słowie pensja w drugiej linijce kodu, dzięki czemu nieco niżej pojawiła się żółta ramka przedstawiająca aktualną wartość tej zmiennej (niestety przy zrzucaniu printscreena nie wiedzieć czemu znika kursor, dlatego nie widać go na powyższym obrazku).

Do zaprezentowania tej funkcjonalności specjalnie wybrałem fragment kodu z innej linijki niż punkt zatrzymania, aby pokazać Ci, że po zatrzymaniu makra można podpatrywać nie tylko wartości z tego wiersza, ale też wszystkie inne.

Dodatkowym plusem jest fakt, że zadeklarowane zmienne to nie jedyne wartości, które możesz podejrzeć - równie dobrze, możesz ustawić się kursorem na przykład na poleceniu Cells(1,1) (które, jak już się zdążyłeś dowiedzieć w poprzednich lekcjach, jest odniesieniem do komórki A1), aby zobaczyć jaka wartość znajduje się aktualnie w komórce A1.

Zatrzymywanie makr jest więc bardzo przydatną funkcją, która w wielu sytuacjach ułatwi Ci pracę przy tworzeniu aplikacji.

Teraz, kiedy umiemy już zatrzymywać makro i podglądać wartości zmiennych, zupełnie niepotrzebny staje się na przykład wiersz szósty omawianego wcześniej makra zarobki:
 
Cells(1,1) = pensja

Jego zadaniem było tylko wyświetlenie w arkuszu wartości zmiennej pensja, tak aby móc potem sprawdzić ile ona wynosiła. Ale po co to robić, skoro znasz już o wiele szybszy, wygodniejszy i łatwiejszy w użyciu sposób na wykonanie tej samej czynności, czyli ustawienie punktu zatrzymania i najechanie kursorem na nazwę zmiennej.

Punktów zatrzymania nie można ustawiać w pustych wierszach oraz w wierszach, w których znajduje się tylko deklaracja zmiennych.