12 - Pętla Do … Loop


Głównym tematem tej lekcji będzie drugi z trzech dostępnych w VBA rodzajów pętli - pętla Do ... Loop. Pętla ta umożliwia wykonywanie jakichś operacji bez uprzedniego określania ilości powtórzeń, jak to miało miejsce w omówionej w lekcji dziesiątej pętli For ... Next.

Oprócz tego dowiemy się jak wymusić wcześniejsze wyjście z procedury i funkcji lub wcześniejsze zakończenie całego makra.

Na zakończenie tej lekcji poznamy też instrukcję Go To pozwalającą na przenoszenie wykonywania makra w dowolne miejsce kodu.

Podstawowe informacje o pętlach Do ... Loop

W lekcji dziesiątej poznałeś pętlę For ... Next, która wykonywała przypisany do niej blok operacji określoną liczbę razy, uzależnioną od początkowej wartości iteratora oraz górnej granicy pętli.

Często zdarza się jednak, że pętla powinna wykonywać jakieś operacje tak długo, aż zmienne osiągną określoną wartość, i w momencie wywołania tej pętli zupełnie nie wiadomo ilu powtórzeń tej pętli będzie to wymagało.

W lekcji poświęconej pętli For ... Next omówiony został sposób opuszczenia pętli w momencie osiągnięcia określonego stanu przez którąś ze zmiennych (Exit For). Zauważ jednak, że przedstawiona w tamtym przykładzie pętla analizowała po kolei każdy pojedynczy znak jakiegoś tekstu, nie było więc problemu z określeniem górnej granicy pętli (iterator mógł osiągnąć maksymalnie taką wartość, ile znaków liczył rozpatrywany tekst). Zastosowanie w tej pętli polecenia Exit For miało służyć jedynie temu, aby cały kod działał szybciej i nie wykonywał kolejnych powtórzeń pętli w momencie, gdy znany był już wynik działania całej funkcji. W związku z tym, nawet po usunięciu z tego przykładu polecenia Exit For funkcja działałaby prawidłowo, tyle że niezbyt efektywnie.

Zdarzają się jednak sytuacje, że w momencie rozpoczęcia działania pętli, określenie górnej granicy pętli, choćby w przybliżeniu, jest zupełnie niemożliwe. Wyobraź sobie przykładowo makro, która ma za zadanie wypisywać w komórkach arkusza kolejne potęgi jakiejś liczby (np. 1.01), tak długo, aż wartość potęgi osiągnie określony limit (np. 1 000 000). W takim przypadku nie sposób nawet w przybliżeniu określić ile potwórzeń tej pętli będzie potrzebnych, aby osiągnąć zamierzony cel.

Jedynym sposobem na uporanie się z tym problemem za pomocą zwykłej pętli For ... Next jest wykorzystanie nieskończonej pętli. Sposób ten został przedstawiony w poniższej ramce z kodem:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Sub wypisujPotegi(podstawa As Double, limit As Double)
    Dim potega As Double            'wartość potęgi
    Dim n As Long                   'numer wiersza
    Dim i As Long                   'iterator

    potega = 1

    For i = 1 To 1 Step 0
        potega = potega * podstawa
        If potega > limit Then
            Exit For
        Else
            n = n + 1
            Cells(n, 1) = potega
        End If
    Next i
End Sub

Powyższa procedura posiada dwa argumenty wejściowe:
  • podstawa - typu Double; określa liczbę, która będzie podnoszona do potęgi,
  • limit - również typu Double; określa przy jakiej wartości potęgi makro powinno zakończyć wypisywanie potęg w arkuszu.

Ponadto w procedurze zadeklarowane zostały trzy dodatkowe zmienne:
  • potega - typu Double; przechowuje wartości kolejnych potęg; przy każdym powtórzeniu pętli wartość tej zmiennej jest mnożona przez wartość argumentu podstawa, tworząc tym samym jej kolejną potęgę,
  • n - typu Long; ta zmienna określa z kolei numer wiersza, w którym ma zostać wyświetlona aktualna wartość zmiennej potega; przy każdym powtórzeniu pętli wartość tej zmiennej jest zwiększana o 1, dzięki czemu każda kolejna potęga znajdzie się w jednym wierszu poniżej poprzedniej,
  • i - typu Byte; zmienna ta będzie pełniła rolę iteratora w pętli; ponieważ znajdująca się w powyższym przykładzie pętla jest pętlą nieskończoną, wartość zmiennej iteracyjnej cały czas będzie wynosiła 1, dlatego bez obaw można ją zadeklarować jako najmniejszy typ - Byte.

W wierszu 6 zmiennej potega zostaje nadana wartość 1. Do tego momentu wartość tej zmiennej wynosiła 0 i gdyby nie przypisano do niej wartości 1, mnożenie jej przez argument podstawa, które jest wykonywane przy każdym powtórzeniu pętli, nie dawałoby absolutnie żadnego rezultatu i wartość zmiennej potega cały czas wynosiłaby 0, zamiast przyjmować wartości poszczególnych potęg.

W ósmym wierszu opisywanej procedury rozpoczyna się wspomniana już wcześniej nieskończona pętla. Na końcu wiersza otwarcia tej pętli znajduje się polecenie Step 0. Dla przypomnienia, oznacza ono, że przy każdym powtórzeniu pętli wartość iteratora będzie się zwiększać o 0, czyli pozostanie na tym samym poziomie, w związku z czym nigdy nie przekroczy określonej w wierszu otwarcia górnej granicy pętli, co czyni tę pętlę nieskończoną.

Wewnątrz omawianej pętli znajduje się operacja pomnożenia zmiennej potega przez liczbę podnoszoną do potęgi (która jest przechowywana w zmiennej podstawa), w efekcie czego zmienna potega przyjmuje wartość kolejnej potęgi.

Drugim elementem wnętrza pętli jest instrukcja warunkowa If ... Then, która przy każdym powtórzeniu pętli sprawdza czy wartość zmiennej potega nie przekroczyła określonego wcześniej limitu.

Jeżeli warunek ten (potega > limit) jest spełniony, a więc wartość aktualnej potęgi przekroczyła limit, wywoływane jest polecenie Exit For (wiersz 11), wymuszające natychmiastowe opuszczenie pętli.

Jeśli natomiast warunek zawarty w tej instrukcji warunkowej nie jest spełniony (czyli zmienna potega nie przekroczyła jeszcze limitu), najpierw zwiększana jest wartość zmiennej n, tak aby reprezentowała ona kolejny wiersz arkusza, a następnie w tym wierszu i pierwszej kolumnie drukowana jest aktualna wartość zmiennej potega.

Wynik działania opisanej powyżej procedury jest zgodny z wcześniejszymi założeniami - w arkuszu wyprintowane zostały wartości poszczególnych potęg zadanej liczby aż do momentu przekroczenia przez nie określonego limitu. Wykorzystanie pętli nieskończonej pozwoliło w sprytny sposób uniknąć konieczności określania górnej granicy pętli, co byłoby w tym przypadku niewykonalne.

Mimo że przedstawione rozwiązanie działa prawidłowo, przy tworzeniu aplikacji powinno się unikać tego typu rozwiązań. W składni języka VBA znajduje się konstrukcja przeznaczona specjalnie do tego typu zadań - pętla Do ... Loop, która jest o wiele bardziej przejrzysta, zrozumiała i zajmuje znacznie mniej miejsca.

Pętla Do ... Loop jest kombinacją pętli oraz instrukcji warunkowej. W wierszu otwarcia lub zamknięcia pętli należy określić warunek, której spełnienie lub niespełnienie (w zależności od użytego słowa kluczowego) spowoduje opuszczenie pętli.

Pętla Do ... Loop może występować w czterech odmianach:
  • Do ... Loop Until [warunek] - warunek jest określony w wierszu zamknięcia pętli, a pętla kończy działanie w razie jego niespełnienia,
  • Do ... Loop While [warunek] - warunek jest określony w wierszu zamknięcia pętli, a pętla kończy działanie w razie jego spełnienia,
  • Do Until [warunek] ... Loop - warunek jest określony w wierszu otwarcia pętli, a pętla kończy działanie w razie jego niespełnienia,
  • Do While [warunek] ... Loop - warunek jest określony w wierszu otwarcia pętli, a pętla kończy działanie w razie jego spełnienia.

Jak widzisz, pętle ze słowem kluczowym Until różnią się od pętli zawierających słowo While tylko tym, że w tych pierwszych opuszczenie pętli następuje w przypadku niespełnienia warunku, natomiast w drugich w razie jego spełnienia. Z uwagi na to bardzo łatwo jest zamienić jedną pętlę w drugą - wystarczy zmienić słowo kluczowe, a do warunku dodać operator logiczny Not. Ogólna zasada jest taka, aby wybierać zawsze typ pętli z takim słowem kluczowym (Until lub While), który w warunku nie będzie wymagał operatora Not.

Poniżej przedstawiono omawiane wcześniej makro, wypisujące w arkuszu potęgę zadanej liczby aż do osiągnięcia określonego limitu, w którym zamiast nieskończonej pętli For ... Next wykorzystano już pętlę Do ... Loop.
1
2
3
4
5
6
7
8
9
10
11
12
Sub wypisujPotegi(podstawa As Double, limit As Double)
    Dim potega As Double              'wartość potęgi
    Dim n As Long                     'numer wiersza

    potega = 1

    Do Until potega > limit
        n = n + 1
        potega = potega * podstawa
        Cells(n, 1) = potega
    Loop
End Sub

Już na pierwszy rzut oka widać, że w obecnej postaci makro jest o wiele krótsze i bardziej przejrzyste.

Pętle Do ... Loop, w przeciwieństwie do pętli For ... Next nie korzystają ze zmiennych iteracyjnych, dlatego też z sekcji deklaracji zmiennych usunięta została zmienna i, która w poprzedniej wersji makra pełniła właśnie rolę iteratora w nieskończonej pętli. Pozostałe zadeklarowane zmienne oraz argumenty wejściowe są identyczne jak w poprzedniej wersji makra.

W wierszach 7-11 powyższego kodu znajduje się pętla Do ... Loop. Do sprawdzania jak długo powinna działać ta pętla użyty został warunek potega > limit. Słowem kluczowym poprzedzającym ten warunek jest Until, dlatego też pętla będzie wykonywana tak długo, jak warunek ten będzie fałszywy (w momencie, kiedy warunek zostanie spełniony, czyli wartość zmiennej potega przekroczy limit, pętla zostanie opuszczona i wykonywanie kodu zostanie przeniesione tuż za wiersz jej zamknięcia, czyli do wiersza 12).

Zauważ, że warunek został umieszczony już w wierszu otwarcia pętli, dlatego też może zdarzyć się sytuacja, w której pętla nie zostanie ani razu wykonana. Jeżeli przykładowo przy wywoływaniu tej procedury jako argument limit zostanie podana liczba 0, to już przy otwarciu pętli warunek jej opuszczenia zostanie spełniony (ponieważ stwierdzenie 1 > 0 jest prawdziwe) i kompilator przeniesie wykonywanie kodu do wiersza 12, czyli za wiersz zamknięcia tej pętli.

Na tym polega właśnie jedyna różnica pomiędzy pętlami zawierającymi warunek w wierszu otwarcia, a pętlami z warunkiem w wierszu zamknięcia. W tych drugich, w momencie wejścia do pętli warunek nie jest jeszcze sprawdzany, więc wszystkie operacje znajdujące się w jej wnętrzu są zawsze wykonywane przynajmniej raz. Dopiero po wykonaniu poleceń z wnętrza pętli i dotarciu kodu do wiersza zamknięcia sprawdzany jest warunek i ewentualnie dochodzi do opuszczenia pętli.

Gdyby więc w powyższym przykładzie warunek opisujący moment wyjścia z pętli został umieszczony w wierszu zamknięcia:
1
2
3
4
5
6
7
8
9
10
11
12
Sub wypisujPotegi(podstawa As Double, limit As Double)
    Dim potega As Double            'wartość potęgi
    Dim n As Long                   'numer wiersza

    potega = 1

    Do Until
        n = n + 1
        potega = potega * podstawa
        Cells(n, 1) = potega
    Loop Until potega > limit
End Sub
to makro wyświetliłoby w arkuszu pierwszą potęgę zadanej liczby, mimo tego, że przekracza ona określony limit.

Schemat działania znajdującej się w powyższym przykładzie pętli Do ... Loop jest niemal identyczny jak w przykładzie wykorzystującym nieskończoną pętlę For ... Next.

Przy każdym powtórzeniu pętli najpierw zwiększana jest wartość zmiennej n, dzięki czemu każda kolejna wartość potęgi wyświetlana jest w następnym wierszu. Następnie wartość zmiennej potega mnożona jest przez wartość argumentu podstawa, który przechowuje liczbą podnoszoną do potęgi, dając w efekcie kolejną potęgę tej liczby. Wreszcie w ostatniej operacji umieszczonej we wnętrzu pętli aktualna wartość potęgi wypisywana jest w odpowiednim wierszu aktywnego arkusza.

W zdecydowanej większości przypadków tak naprawdę jest zupełnie bez znaczenia czy warunek opisany jest w wierszu otwarcia czy zamknięcia pętli (aczkolwiek w poprzednim przykładzie sprawiało to subtelną różnicę). Zdarzają się jednak sytuacje, że umiejscowienie warunku w istotny sposób pływa na wykonywanie pętli. Za przykład może posłużyć przedstawione poniżej makro, którego schemat działania jest następujący: po jego uruchomieniu na ekranie pojawia się okno InputBox, w którym użytkownik wpisuje jakieś wartości (np. imiona i nazwiska). Po każdorazowym wpisaniu wartości, jest ona dopisywana w kolejnym wierszu arkusza, a na ekranie pojawia się nowe, puste okno InputBox. Proces ten jest powtarzany tak długo, aż użytkownik zamknie okno, kliknie przycisk Cancel lub zatwierdzi pusty wpis (czyli wciśnie przycisk OK nie wpisawszy wcześniej żadnej wartości).

1
2
3
4
5
6
7
8
9
10
Sub podajWartosci()
    Dim value As String            'wartość wpisywana w InputBoxie
    Dim n As Long                  'numer wiersza

    Do
        value = InputBox("Wpisz wartość")
        n = n + 1
        Cells(n, 1) = value
    Loop While Len(value)
End Sub

W powyższym makrze zadeklarowane są dwie zmienne:
  • value - do tej zmiennej przypisywane będą wyniki funkcji InputBox, a więc de facto wartości wprowadzane przez użytkownika w polu tekstowym okna InputBox,
  • n - ta zmienna reprezentuje aktualy wiersz w arkuszu; przy każdym powtórzeniu pętli zmienna ta będzie zwiększana o 1, dzięki czemu każda kolejna wartość znajdzie się w jednym wierszu poniżej poprzedniej.

Główną część kodu stanowi pętla Do, znajdująca się w wierszach 5-9. Warunkiem określonym dla tej pętli jest funkcja Len(value), poprzedzona słowem kluczowym While. Oznacza to, że pętla jest wykonywana tak długo, jak wartość tej funkcji jest równoznaczna wartości logicznej True. Dla przypomnienia - funkcja Len(value) zwraca wartość liczbową, która odpowiada długości tekstu value, a wartość liczbowa jest równoważna wartości True, jeżeli jest różna od zera. W związku z tym całe wyrażenie jest prawdziwe, jeżeli wartość funkcji Len(value) jest różna od zera, a więc jeśli tekst przechowywany w argumencie value nie jest pustym ciągiem znaków.

W trakcie działania pętli, do zmiennej value przypisywane są poszczególne wyniki funkcji InputBox (wiersz 6). W dwóch kolejnych wierszach wartość zmiennej n jest zwiększana o 1, tak aby odpowiadała kolejnemu wierszowi, a następnie wartość zmiennej value (czyli to, co użytkownik wpisał chwilę wcześniej w oknie InputBox) jest printowana w arkuszu.

Jak dowiedziałeś się w lekcji ósmej, jeżeli okno funkcji InputBox zostanie zamknięte krzyżykiem w prawym górnym rogu lub wciśnięty zostanie przycisk Cancel, to niezależnie od tego, co jest wpisane w polu tekstowym tego okna, funkcja zwróci pusty ciąg znaków. Pusty ciąg zostanie zwrócony również w sytuacji, gdy użytkownik pozostawi puste pole tekstowe i wciśnie przycisk OK.

W powyższych trzech sytuacjach (zamknięcie okna krzyżykiem, wciśnięcie Cancel i pozostawienie pustego pola tekstowego) do zmiennej value przypisany zostanie więc pusty ciąg znaków, w związku z czym warunek Len(value) będzie równoważny wartości logicznej False i pętla zakończy swoje działanie.

Zwróć uwagę, że do zmiennej value jakakolwiek wartość jest po raz pierwszy przypisywana podczas pierwszego powtórzenia pętli, w wierszu 6. Do tego czasu zmienna ta przechowuje pusty ciąg znaków.

W związku z tym, gdyby warunek tej pętli umieszczony był w wierszu jej otwarcia, to już przy pierwszym uruchomieniu pętli kompilator stwierdziłby, że pętla powinna zostać zakończona i przeszedłby od razu do instrukcji umieszczonych za tą pętlą, ani razu nie wykonując jej wnętrza. W tym konkretnym przypadku położenie warunku opisującego pętlę Do jest więc bardzo ważne i może zasadniczo wpłynąć na wynik działania całej procedury.

Korzystając z pętli Do ... Loop można stworzyć pętlę nieskończoną jeszcze łatwiej, niż za pomocą pętli For ... Next.

Wystarczy stworzyć pętlę Do ... Loop, która ani w wierszu otwarcia, ani w wierszu zamknięcia nie zawiera warunku opuszczenia pętli:
Do
    'operacje znajdujące się we wnętrzu pętli
Loop

Wcześniejsze opuszczanie pętli Do ... Loop

Podobnie jak to miało miejsce w przypadku pętli For ... Next, język VBA umożliwia opuszczenie pętli Do ... Loop wcześniej, niż wynikałoby to z przypisanych do tej pętli warunków.

Poniżej znajduje się przykładowa procedura korzystająca z polecenia Exit Do, które pozwala wymusić natychmiastowe opuszczenie pętli Do ... Loop.

Użytkownik musi w niej wprowadzić hasło, zezwalające na uruchomienie dalszych czynności.

W razie trzykrotnego podania nieprawidłowego hasła, procedura wykonuje określone operacje zabezpieczające (np. tymczasowe zablokowanie dostępu do makra z danego konta lub powiadomienie administratora o próbie uruchomienia makra przez niepowołaną osobę). Rozwiązania takie często spotykane są przy logowaniu do internetowych kont bankowych - każdemu możę się zdarzyć jedna lub nawet dwie pomyłki podczas wprowadzania hasła, ale jeśli taka sytuacja powtarza się trzy razy z rzędu, istnieje duże prawdopodobieństwo, że ktoś próbuje złamać hasło i należy w takiej sytuacji podjąć jakieś kroki zaradcze.

Jeżeli natomiast użytkownik pomyślnie przejdzie weryfikację hasła, wykonywane są dalsze czynności przewidziane dla tego makra (w przykładzie zostały one zastąpione komentarzem, ponieważ nie mają żadnego znaczenia dla omawianego zagadnienia).

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
Sub weryfikacjaHasla()
    Dim haslo As String
    Dim podaneHaslo As String
    Dim czyPrawidlowe As Boolean
    Dim i As Byte

    haslo = "qwerty"

    Do While i < 3
        podaneHaslo = InputBox("Podaj hasło", "Hasło")
        i = i + 1

        If podaneHaslo = haslo Then
            czyPrawidlowe = True
            Exit Do
        Else
            Call MsgBox("Podane hasło jest nieprawidłowe." & vbCrLf & "Pozostało prób: " & 3 - i)
       End If
    Loop

    If czyPrawidlowe Then
        'właściwa część makra
    Else
        'operacje zabezpieczające, np. zablokowanie dostępu do makra na
        'najbliższą godzinę z danego komputera lub poinformowanie
        'administratora o próbie złamania hasła

    End If

End Sub

W procedurze zadeklarowane są cztery zmienne:
  • haslo - zmienna typu tekstowego, do której przypisane jest prawidłowe hasło,
  • podaneHaslo - zmienna typu tekstowego; w dalszej części makra przypisywane jest do niej hasło podawane przez użytkownika w oknie InputBox,
  • czyPrawidlowe - zmienna typu Boolean; przyjmuje wartość True, jeżeli użytkownik podał prawidłowe hasło, oraz wartość False, jeżeli podane przez niego hasło jest błędne,
  • i - zmienna typu Byte; liczy ile razy próbowano już wpisać hasło; maksymalna liczba prób wynosi 3, dlatego bez problemu można nadać tej zmiennej typ Byte.

W wierszu 7 do zmiennej haslo zostaje przypisane prawidłowe hasło czyli tekst qwerty.

W wierszach 9-20 znajduje się pętla Do ... Loop, której warunkiem ograniczającym jest i < 3 ze słowem kluczowym While, co oznacza, że pętla zakończy swoje działanie, jeżeli wartość zmiennej i będzie wynosiła co najmniej 3 (a więc, jeśli użytkownik wykonał już trzy próby wpisania hasła).

Przy każdym powtórzeniu pętli użytkownik jest proszony o wpisanie w oknie InputBox hasła, które jest potem przypisywane do zmiennej podaneHaslo.

Po każdej takiej próbie wpisania hasła, zmienna i jest zwiększana o 1, ponieważ jak wcześniej wspomniano, zmienna ta służy jako licznik wykonanych prób.

Następnie kompilator sprawdza czy hasło podane przez użytkownika zgadza się z hasłem zapisanym jako prawidłowe (wiersz 13).

Jeżeli oba hasła są identyczne, do zmiennej czyPrawidlowe zostaje przypisana wartość True i wywoływane jest polecenie Exit Do, oznaczające natychmiastowe opuszczenie pętli. Gdyby w tym miejscu zabrakło tego polecenia, jedyną możliwością wyjścia z pętli byłoby osiągnięcie przez zmienną i wartości 3, a więc użytkownik za każdym razem musiałby trzykrotnie wpisywać hasło, nawet jeśli już za pierwszym razem podałby prawidłowe hasło. Byłoby to oczywiście bardzo nieefektywne, a przede wszystkim irytujące dla użytkowników.

Jeżeli natomiast użytkownik podał błędne hasło, na ekranie wyświetlany jest komunikat, że podane przez niego hasło jest nieprawidłowe wraz z informacją o liczbie pozostałych prób (3-i).

Pętla jest więc powtarzana tak długo, aż użytkownik poda prawidłowe hasło lub trzykrotnie poda błędne hasło.

Po wyjściu z pętli, kompilator sprawdza jaki był wynik weryfikacji hasła (przechowywany w zmiennej czyPrawidlowe) i, jeśli jest on pozytywny, wykonuje właściwe operacje. Jeżeli natomiast zmienna czyPrawidlowe wynosi False, uruchamiane są operacje zabezpieczające, jak np. powiadomienie administratora czy zablokowanie na jakiś czas możliwośc korzystania z makra na danym komputerze (przykład ma tylko obrazować wykorzystanie polecenia Exit Do, więc również nie ma sensu w tym miejscu szczegółowo opisywać tych operacji).

Powszechną sytuacją, w której użycie polecenia Exit Do może przynieść wiele pożytku, jest operowanie na obiektach RecordSet, czyli zestawach danych pobranych bezpośrednio z bazy danych. Jednak z uwagi na to, że temat ten znacznie wykracza poza materiał przerobiony w dotychczasowych lekcjach, przykłady obrazujące wykorzystanie polecenia Exit Do w tego typu procedurach pojawią się się dopiero podczas szczegółowego omawiania baz danych.

Wcześniejsze opuszczanie procedur, funkcji i makr

Język VBA pozwala też wymusić wcześniejsze opuszczenie określonej funkcji lub procedury, a nawet całego makra.

Poniżej znajduje się prosty przykład obrazujący opuszczenie procedury, jeszcze zanim wykonywanie kodu dotrze do wiersza jej zamknięcia.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Sub sprawdzanieHasla()
    Dim haslo As String                'prawidłowe hasło
    Dim podaneHaslo As String          'hasło podane przez użytkownika

    haslo = "qwerty"
    podaneHaslo = InputBox("Podaj hasło", "Hasło")

    If haslo <> podaneHaslo Then Exit Sub

    'dalsze instrukcje wykonywane przez makro
    'Call podprocedura_1
    '(...)
    'Call podprocedura_n
End Sub

Powyższa procedura zawiera dwie zmienne tekstowe: haslo (przechowująca prawidłowe hasło) oraz wpisaneHaslo (przechowująca hasło wpisane przez użytkownika).

W wierszach 5-6 do tych dwóch zmiennych przypisywane są odpowiednie wartości: do zmiennej haslo ciąg znaków qwerty, natomiast do zmiennej podaneHaslo wynik działania funkcji InputBox, czyli hasło, które użytkownik wpisuje w polu tekstowym okna InputBox.

W wierszu 8 znajduje się instrukcja warunkowa, sprawdzająca czy hasło wpisane przez użytkownika zgadza się z prawidłowym hasłem. Jeżeli oba hasła są od siebie różne, oznacza to, że użytkownik podał błędne hasło i nie jest uprawniony do uruchomienia makra, w związku z czym wywoływane jest polecenie Exit Sub, które oznacza natychmiastowe opuszczenie tej procedury.

Tak naprawdę dotychczasowa część makra służyła tylko weryfikacji użytkownika. Zasadnicza część makra znajduje się w wierszach 10-13 i została zastąpiona komentarzem, gdyż nieistotne jest w tym momencie, co miałoby wykonywać to makro. Jeżeli jednak użytkownik nie przejdzie wcześniejszej weryfikacji (a więc wpisze nieprawidłowe hasło), wykonywanie kodu nigdy nie dojdzie do tego miejsca, ponieważ opuści tę procedurę już w wierszu 8 w wyniku działania polecenia Exit Sub.

Zwróć uwagę, że polecenie Exit Sub nie jest w tej sytuacji niezbędne. Makro działałoby dokładnie tak samo, gdyby wszystkie operacje umieszczone w wierszach 10-13 znalazły się we wnętrzu instrukcji If ... Then, w bloku przeznaczonym dla spełnionego warunku. Kod wyglądałby w tej sytuacji tak, jak w poniższej ramce (nowe fragmenty zaznaczono na czerwono):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Sub sprawdzanieHasla()
    Dim haslo As String                'prawidłowe hasło
    Dim podaneHaslo As String          'hasło podane przez użytkownika

    haslo = "qwerty"
    podaneHaslo = InputBox("Podaj hasło", "Hasło")

    If haslo = podaneHaslo Then
        'instrukcje wykonywane jeżeli oba hasła się zgadzają
        'Call podprocedura_1
        '(...)
        'Call podprocedura_n
    End If
End Sub

W tej postaci makra, po ustaleniu prawidłowego hasła oraz pobraniu hasła od użytkownika (wiersze 5-6) podobnie jak poprzednio, kod trafia na instrukcję warunkową If ... Then. Warunek przypisany do tej instrukcji jest jednak teraz dokładnie odwrotny.

Cała zasadnicza część makra umieszczona została wewnątrz instrukcji warunkowej, w bloku dla spełnionego warunku, dlatego też będzie wykonana tylko wtedy, jeżeli warunek przypisany dla tej instrukcji (haslo = podaneHaslo) będzie prawdziwy, a więc jeśli użytkownika poda prawidłowe hasło.

Jeżeli natomiast użytkownik poda błędne hasło, warunek przypisany do instrukcji warunkowej będzie fałszywy. Z uwagi na to, że w instrukcji warunkowej nie przewidziano żadnych operacji dla niespełnionego warunku, wykonywanie kodu zostanie przeniesione do wiersza zamknięcia instrukcji warunkowej (wiersz 13), po którym następuje już tylko zakończenie całej procedury.

Możliwość wyeliminowania polecenia Exit Sub (lub Exit Function, bo poza tym, że jedno z nich używane jest w procedurach, a drugie w funkcjach, absolutnie się od siebie nie różnią) przez takie niewielkie modyfikacje kodu nie jest niczym nadzwyczajnym. Tak naprawdę jest to reguła i w zasadzie można byłoby zaryzykować stwierdzenie, że każde polecenie Exit Sub (Exit Function) może być zastąpione innym elementem języka VBA bez utraty funkcjonalności makra.

Jeżeli wyeliminowanie polecenia Exit Sub (Exit Function) jest tak proste, jak w powyższym przykładzie, zawsze lepiej jest stosować wersję niezawierającą tego polecenia, ponieważ jego obecność w kodzie może w niektórych sytuacjach znacznie utrudnić późniejszą analizę kodu aplikacji.

Zdarzają się jednak sytuacje, że zastosowanie polecenia Exit Sub (Exit Function) pozwala uprościć kod, a nawet przyśpieszyć nieco działanie danej procedury lub funkcji, i wtedy jego użycie jest jak najbardziej wskazane.

Bardzo często spotykanym przykładem takiej sytuacji są funkcje sprawdzające kompletność danych wprowadzonych przez użytkownika w formularzu.

Załóżmy, że makro wyświetla użytkownikowi następujący formularz, w którym pola oznaczone gwiazdką są obowiązkowe do wypełnienia:

Formularz z danymi użytkownika

Po kliknięciu przycisku OK, dane z formularza powinny się zapisywać w bazie danych. Przed ich zapisaniem do bazy należy jednak sprawdzić, czy wprowadzone dane są kompletne, czyli czy użytkownik wypełnił wszystkie pola oznaczone jako wymagane.

Oczywiście nie będziemy się w tym miejscu zajmować tworzeniem tego typu formularzy, ani zapisywaniem ich do bazy danych - na to wszystko przyjdzie pora w dalszej części kursu. W tym momencie istotna jest tylko funkcja sprawdzająca kompletność danych.

Spójrz najpierw na kod i analizę tej funkcji przy założeniu, że w ogóle nie wykorzystuje ona polecenia Exit Function:
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
Function czyKompletneDane(Imie As String, Nazwisko As String, _
            PESEL As String, Dowod As String, NIP As String, _
            Ulica As String, Numer As String, KodPocztowy As String, _
            Miasto As String, Telefon As String) As Boolean

    If Len(Imie) Then
        If Len(Nazwisko) Then
            If Len(PESEL) Then
                If Len(Dowod) Then
                    If Len(NIP) Then
                        If Len(Ulica) Then
                            If Len(Numer) Then
                                If Len(KodPocztowy) Then
                                    If Len(Miasto) Then
                                        If Len(Telefon) Then
                                            czyKompletneDane = True
                                        End If
                                    End If
                                End If
                            End If
                        End If
                    End If
                End If
            End If
        End If
    End If
End Function

Funkcja czyKompletneDane posiada 10 argumentów wejściowych, z których każdy odpowiada wartości jednego z obowiązkowych pól na formularzu.

Na przykład przy tak wypełnieniu formularzu:

Wypełniony formularz z danymi użytkownika

poszczególne argumenty posiadają następujące wartości:
Imie = "Krzysztof", Nazwisko = "Jarzyna", PESEL = "53050312114", Dowod = "AAA000001", NIP = "503-520-12-14", Ulica = "", Numer = "", KodPocztowy = "70-003", Miasto = "Szczecin", Telefon = "601-540-321".

Funkcja czyKompletneDane posiada typ Boolean, a więc może zwrócić tylko wartość True (jeżeli dane są kompletne) lub wartość False (jeżeli użytkownik nie uzupełnił któregoś z wymaganych pól).

Na początku funkcja przechowuje wartość False, ponieważ taka jest domyślna wartość dla zmiennej typu logicznego, jeżeli nie przypisano do niej żadnej innej wartości.

W wierszach 6-26 znajduje się dziesięć zagnieżdżonych instrukcji warunkowych, z których każda sprawdza jedną z wartości formularza. Ponieważ każda z tych zagnieżdżonych instrukcji rozpatruje inną zmienną, niemożliwe jest zastąpienie ich instrukcją Select Case (instrukcja Select Case rozpatruje różne warianty pojedynczej zmiennej, w związku z tym może zastępować tylko zagnieżdżone instrukcje warunkowe, które również rozpatrują tylko pojedynczą zmienną).

Kompilator sprawdza po kolei każdą z instrukcji warunkowych i, jeżeli warunek w niej zawarty jest prawdziwy (czyli sprawdzana w tej instrukcji wartość została przez użytkownika uzupełniona), przechodzi do sprawdzania kolejnej itd. Jeżeli sprawdzone zostaną już wszystkie instrukcje i wszystkie będą prawdziwe, wykonanie kodu dociera do wiersza 16, w którym do funkcji zostaje przypisana wartość True. Każda kolejna instrukcja warunkowa jest zagnieżdżona w swojej poprzedniczce w bloku przewidzianym dla spełnionego warunku. Oznacza to, że jeżeli przy którejkolwiek instrukcji warunkowej okaże się, że przypisany do niej warunek nie jest spełniony (czyli użytkownik nie wpisał tej wartości w formularzu), wykonanie kodu nigdy nie dotrze do wiersza, w którym do funkcji zostaje przypisana wartość True. Jest to oczywiście zgodne z założeniem, gdyż w przypadku braku choćby jednej wartości, funkcja ma zwracać wartość False.

Powyższa postać funkcji działa wprawdzie prawidłowo, jednak tworzenie tak wielu zagnieżdżonych pętli nigdy nie jest dobrym pomysłem, ponieważ znacznie wydłuża cały kod, a przy tym łatwo w takiej sytuacji o pomyłkę. Spójrz teraz na umieszczony poniżej kod, który wykonuje dokładnie to samo zadanie, jednak zamiast zagnieżdżania instrukcji warunkowych wykorzystano w nim instrukcje Exit Function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function czyKompletneDane(Imie As String, Nazwisko As String, _
            PESEL As String, Dowod As String, NIP As String, _
            Ulica As String, Numer As String, KodPocztowy As String, _
            Miasto As String, Telefon As String) As Boolean

    If Len(Imie) = 0 Then Exit Function
    If Len(Nazwisko) = 0 Then Exit Function
    If Len(PESEL) = 0 Then Exit Function
    If Len(Dowod) = 0 Then Exit Function
    If Len(NIP) = 0 Then Exit Function
    If Len(Ulica) = 0 Then Exit Function
    If Len(Numer) = 0 Then Exit Function
    If Len(KodPocztowy) = 0 Then Exit Function
    If Len(Miasto) = 0 Then Exit Function
    If Len(Telefon) = 0 Then Exit Function

    czyKompletneDane = True

End Function

Ta postać funkcji również posiada dziesięć argumentów wejściowych, z których każdy odpowiada jednemu polu na formularzu. Początkowa wartość funkcji wynosi False.

W wierszach 6-15 znajduje się 10 jednowierszowych instrukcji warunkowych If ... Then. Każda z tych instrukcji posiada warunek, który sprawdza czy poszczególna wartość jest pustym ciągiem znaków. Jeżeli warunek ten jest spełniony (co jest równoznaczne z tym, że użytkownik nie wpisał jej w formularzu), wywoływane jest polecenie Exit Function, które wymusza natychmiastowe opuszczenie funkcji. Jeżeli kompilator został zmuszony do takiego nagłego wyjścia z funkcji, wynikiem działania tej funkcji jest wartość, jaką posiadała ona w momencie jej opuszczania - w tym przypadku jest to więc ciągle domyślna wartość False, gdyż dotychczas w żadnym miejscu kodu nie przypisano do tej funkcji innej wartości. Jest to oczywiście zgodne z początkowym założeniem, ponieważ jeśli jakakolwiek wartość nie została uzupełniona w formularzu, funkcja miała zwracać wartość False.

Jeżeli kompilator sprawdzi wszystkie 10 instrukcji warunkowych i przy żadnej z nich nie wywoła polecenia Exit Function (co oznacza, że nie brakuje żadnej wartości), wykonywanie kodu dochodzi do wiersza 16, w którym do funkcji zostaje przypisana wartość True. Kolejnym wierszem jest już zakończenie całej funkcji, więc jej ostatecznym wynikiem będzie True, co oczywiście również jest zgodne z założeniem (nie brakuje żadnej wartości, więc funkcja zwraca wartość True).

W tym przypadku zastosowanie polecenia Exit Function niesie więc ze sobą wiele korzyści, ponieważ funkcja, nie dość że jest zdecydowanie krótsza i bardziej przejrzysta, to jeszcze działa trochę szybciej niż jej wersja wykorzystująca zagnieżdzone instrukcje warunkowe.

Język VBA posiada również polecenie End, umożliwiające natychmiastowe zatrzymanie i zakończenie całej działającej aktualnie aplikacji.

Wróć do przykładu znajdującego się na początku tego podrozdziału, w którym w przypadku wpisania przez użytkownika nieprawidłowego hasła wykonywanie kodu natrafiało na polecenie Exit Sub, co oznaczało natychmiastowe opuszczenie tej procedury:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Sub sprawdzanieHasla()
    Dim haslo As String                'prawidłowe hasło
    Dim podaneHaslo As String          'hasło podane przez użytkownika

    haslo = "qwerty"
    podaneHaslo = InputBox("Podaj hasło", "Hasło")

    If haslo <> podaneHaslo Then Exit Sub

    'dalsze instrukcje wykonywane przez makro
    'Call podprocedura_1
    '(...)
    'Call podprocedura_n
End Sub

W przykładzie tym zasadnicza część makra (wiersze 10-13) polegała na wywoływaniu podprocedur wykonujących poszczególne zadania. Zauważ, że część poświęcona weryfikacji użytkownika (wiersz 2-6) jako jedyna nie została przeniesiona do oddzielnej procedury, lecz umieszczona w głównej procedurze.

Gdyby przenieść część weryfikacyjną do oddzielnej procedury, kod wyglądałby tak:
1
2
3
4
5
6
Sub aplikacja()
    Call weryfikacja
    'Call podprocedura_1
    '(...)
    'Call podprocedura_n
End Sub
1
2
3
4
5
6
7
8
9
Sub weryfikacja()
    Dim haslo As String                'prawidłowe hasło
    Dim podaneHaslo As String          'hasło podane przez użytkownika

    haslo = "qwerty"
    podaneHaslo = InputBox("Podaj hasło", "Hasło")

    If haslo <> podaneHaslo Then Exit Sub
End Sub

Po uruchomieniu makra jako pierwsza uruchamiana jest podprocedura weryfikacja, która ma sprawdzać czy użytkownik jest w ogóle uprawniony do korzystania z makra. Wykonywanie kodu zostaje więc przeniesione z procedury aplikacja do procedury weryfikacja. W procedurze weryfikacja odbywa się proces, który został już omówiony wcześniej (przy okazji omawiania polecenia Exit Sub), czyli przypisanie prawidłowego hasła, pobranie hasła od użytkownika i sprawdzanie zgodności obu tych haseł.

Załóżmy teraz, że użytkownik wpisał nieprawidłowe hasło - w takiej sytuacji makro powinno zakończyć działanie i nie wykonywać już żadnych innych czynności. Tak się jednak nie stanie. Gdy kompilator stwierdzi, że hasło systemowe (qwerty) oraz hasło podane przez użytkownika (przechowywane w zmiennej podaneHaslo) nie zgadzają się ze sobą (wiersz 8 procedury weryfikacja) to wywoła polecenie Exit Sub, oznaczające natychmiastowe opuszczenie aktualnej procedury. Jeżeli więc opuszczona zostanie aktualna procedura (czyli weryfikacja), to wykonywanie kodu powróci do procedury dla niej nadrzędnej, która ją wywołała (a więc aplikacja) i będzie kontynuowała wykonywanie zawartych w niej poleceń. W związku z tym, mimo że hasło podane przez użytkownika było nieprawidłowe, makro jak gdyby nigdy nic wykona wszystkie przewidziane w nim operacje: podprocedura_1, podprocedura_2, itd.!

Jedynym sposobem na ominięcie tego problemu jest zastąpienie polecenia Exit Sub poleceniem End:
1
2
3
4
5
6
7
8
9
Sub weryfikacja()
    Dim haslo As String                'prawidłowe hasło
    Dim podaneHaslo As String          'hasło podane przez użytkownika

    haslo = "qwerty"
    podaneHaslo = InputBox("Podaj hasło", "Hasło")

    If haslo <> podaneHaslo Then End
End Sub

W takiej postaci makra, jeśli kompilator stwierdzi, że hasło podane przez użytkownika nie zgadza się z hasłem systemowym, wywołane zostanie polecenie End, które oznacza natychmiastowe zakończenie działania całej aplikacji, niezależnie od tego, w której procedurze znajduje się aktualnie wykonywanie kodu.

Mimo że przedstawiony powyżej kod, korzystający z polecenia End, z formalnego punktu widzenia jest całkowicie poprawny, to trzeba zaznaczyć, że używanie tego polecenia nie należy do dobrych praktyk programistycznych.

Głównym powodem jest to, że w przypadku natrafienia kodu na instrukcję End, oprócz zakończenia działania makra, resetowane są wartości wszystkich zmiennych publicznych. Na razie nie poznałeś jeszcze nawet pojęcia zmiennej publicznej, więc zapewne nie widzisz nic złego i niepokojącego w tym, że ich wartość mogłaby być wyzerowana. Temat zmiennych publicznych zostanie dokładnie omówiony w jednej z kolejnych lekcji, jednak już przy tej okazji można wspomnieć, że zmienne publiczne powinny zachowywać swoją wartość od momentu uruchomienia aplikacji, aż do czasu całkowitego zamknięcia Excela. Ich zresetowanie poprzez użycie polecenia End sprawia więc, że tracą one swoją główną zaletę, jaką jest długotrwałe przechowywanie wartości.

Zdarza się też, że jakiś niewielki błąd w kodzie aplikacji prowadzi do niepożądanego wywołania użytego w tym kodzie polecenia End, przez co makro nieoczekiwanie kończy swoje działanie, irytując użytkowników końcowych. Odnalezienie miejsca w kodzie, w którym znajduje się to polecenie End, jest bardzo trudne, nawet z użyciem skrótu klawiaturowego Ctrl + F służącego do przeszukiwania całego kodu, ponieważ oprócz poleceń End w kodzie znajduje się o wiele więcej słów End, które stanowią część takich poleceń jak np. End Sub , End Function czy End Enum.

Przenoszenie wykonywania makra do dowolnego miejsca kodu

W normalnych sytuacjach kompilator wykonuje poszczególne linijki kodu w takiej kolejności, w jakiej znajdują się one w edytorze VBA. Najpierw wykonywana jest linijka 1, potem 2 itd. Zdążyłeś już jednak poznać kilka instrukcji (a w zasadzie większość z nich), które pozwalają ingerować w kolejność wykonywania linii kodu, np. po natrafieniu w kodzie na instrukcję warunkową If ... Then, w której warunek jest spełniony, kompilator ominie te linijki, które opisują operacje dla niespełnionych warunków, a po natrafieniu na pętlę For ... Next, a konkretnie na wiersz jej zamknięcia (Next), kompilator wróci o kilka linijek kodu do wiersza jej otwarcia.

W VBA istnieje ponadto polecenie GoTo, które również pozwala wymuszać zmianę kolejności wykonywania wierszy i umożliwia cofnięcie się lub przeskoczenie o kilka linijek kodu do przodu.

Już na wstępie warto jednak zaznaczyć, że korzystanie z GoTo powinno być ograniczone do minimum i w większości przypadków może, a nawet powinno być zastąpione którąś z wcześniej omówionych instrukcji sterujących. Jedyną sytuacją, gdzie użycie polecenia GoTo nie budzi wątpliwości i nie ma żadnej alternatywy, jest obsługa błędów, która będzie tematem jednej z kolejnych lekcji.

Mimo iż korzystanie z tego polecenia jest zalecane tylko w niewielu sytuacjach, należy je omówić, ponieważ będziesz go czasem potrzebował przy wspomnianej obsłudze błędów, a poza tym może się w przyszłości zdarzyć, że będziesz musiał pracować z aplikacją stworzoną przez inną osobę, chętnie korzystającą z GoTo.

Poniżej znajduje się przykład prostego makra wykorzystującego instrukcję GoTo (chociaż optymalnym rozwiązaniem byłoby tu zastąpienie jej instrukcją If ... Then):
1
2
3
4
5
6
7
Sub wykorzystanieGoTo(i As Long)
    If i <= 0 Then GoTo koniec
    Call MsgBox("Podana liczba jest większa od 0")

koniec:
    Call MsgBox("Koniec działania makra")
End Sub

Procedura z powyższego przykładu wymaga podania jednego argumentu wejściowego typu Long. W trzeciej linijce kodu znajduje się instrukcja warunkowa sprawdzająca, czy liczba podana jako argument jest mniejsza lub równa 0. Jeżeli warunek ten jest spełniony wywoływane jest polecenie GoTo koniec.

Zwróć uwagę, że taka sama nazwa - koniec - tyle, że z dwukropkiem, znajduje się w piątym wierszu omawianego przykładu. Nazwa znajdująca się w piątym wierszu to tzw. etykieta, czyli tak jakby adres tego miejsca w kodzie, do którego można potem przeskoczyć za pomocą instrukcji GoTo.

Etykietą mogą być liczby lub teksty (oczywiście z zachowaniem zasad nadawania nazw omówionych w lekcji drugiej). Jeżeli jako etykieta wykorzystany jest tekst, trzeba po nim postawić dwukropek, gdyż w przeciwnym przypadku traktowany jest on jako nazwa zmiennej lub procedury do wywołania. Liczby, użyte jako etykiety, mogą ale nie muszą posiadać na końcu dwukropka.

Poniżej znajduje się kilka przykładowych etykiet wraz z komentarzem:

ETYKIETAKOMENTARZ
koniec:prawidłowa nazwa etykiety - etykieta jest tekstem, więc zawiera na końcu dwukropek
0:prawidłowa nazwa etykiety - etykieta będąca liczbą może, ale nie musi zawierać na końcu dwukropek
koniecnieprawidłowa nazwa etykiety - etykieta jest tekstem, a nie ma po niej dwukropka
0prawidłowa nazwa etykiety - etykieta będąca liczbą może, ale nie musi zawierać na końcu dwukropek

Edytor VBA traktuje jako etykietę każdy wiersz, w którym znajduje się pojedyncza liczba (z dwukropkiem na końcu lub bez niego) oraz wszystkie wiersze, w których znajdują się tylko pojedyncze nazwy bez cudzysłowów, ale z dwukropkiem na końcu.

W każdej procedurze lub funkcji może znaleźć się dowolna liczba etykiet, nie mogą się one jednak powtarzać w obrębie pojedynczej procedury lub funkcji.

Za każdym razem, kiedy umieścisz w kodzie etykietę, edytor VBA automatycznie wyrówna ją do lewej strony i nie ma możliwości przesunięcia jej w prawo.

Wróćmy z powrotem do analizy kodu. W momencie, w którym kompilator natrafił na polecenie GoTo koniec, wyszukuje on w tej procedurze etykietę o takiej właśnie nazwie i przenosi wykonywanie kodu do linijki znajdującej się bezpośrednio za tą etykietą. W omawianym przypadku etykieta koniec: znajduje się w wierszu 5, więc wykonywanie kodu zostanie przeniesione do wiersza 6, w którym znajduje się operacja wyświetlenia na ekranie okna MsgBox z komunikatem Koniec działania makra.

Gdyby w drugim wierszu powyższego kodu warunek i <= 0 nie został spełniony, kompilator pominąłby polecenie GoTo i wykonywanie kodu dalej biegłoby swoim zwykłym torem. Nie oznacza to bynajmniej, że etykieta koniec: i to, co znajduje się po niej zostałoby w takiej sytuacji pominięte. Jeżeli kompilator natrafi w toku działania na wiersz etykiety, traktuje go po prostu jak komentarz i wykonuje operacje znajdujące się w dalszej części kodu. W związku z tym, jeżeli w omawianym przykładzie zmienna i miała wartość większą od zera, najpierw na ekranie pojawiłby się komunikat Podana liczba jest większa od 0, a następnie komunikat Koniec działania makra.

Jeżeli po słowie kluczowym GoTo wpiszesz nazwę etykiety, która nie występuje w danej procedurze lub funkcji, to w momencie, kiedy wykonywanie kodu dojdzie do tej funkcji lub procedury, wyświetlony zostanie błąd: Compile error: label not defined.

Jak wspomniano jednak na wstępie, polecenie GoTo jest zazwyczaj marnym substytutem innych instrukcji sterujących i powyższa procedura powinna raczej wykorzystywać instrukcję warunkową If ... Then, zamiast GoTo:
1
2
3
4
5
6
7
Sub IfThen_Zamiast_GoTo(i As Long)
    If i > 0 Then
        Call MsgBox("Podana liczba jest większa od 0")
    End If

    Call MsgBox("Koniec działania makra")
End Sub

Poniżej znajduje się jeszcze jeden przykład korzystający z polecenia GoTo.

Oczywiście, również tutaj istnieje możliwość zastąpienia go inną instrukcją sterującą (a konkretnie pętlą Do ... Loop), jednak jest to jeden z nielicznych przypadków, w którym użycie GoTo jest całkiem rozsądnym pomysłem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sub pobieranieNazwy()
    Dim nazwa As String

retry:
    nazwa = InputBox("Podaj nazwę użytkownika." & vbCrLf & _
        "Nazwa musi liczyć co najmniej 4 znaki" , "Nazwa")

    If Len(nazwa) < 4 Then
        Call MsgBox("Nazwa musi liczyć co najmniej 4 znaki", vbExclamation, "Za krótka nazwa")
        GoTo retry
   End If

    'dalsze operacje, np. zapisanie nazwy do bazy danych

End Sub

Działanie procedury polega na pobraniu od użytkownika nazwy, która musi liczyć co najmniej 4 znaki, a następnie wykonanie z tą nazwą jakiejś operacji (np. zapisanie jej do bazy danych; tak naprawdę w kontekście tego przykładu i omawiania instrukcji GoTo nie ma to najmniejszego znaczenia). Jeżeli użytkownik poda za krótką nazwę makro powinno wyświetlić odpowiedni komunikat i ponowić prośbę o wpisanie nazwy.

Procedura posiada jedną zmienną tekstową - nazwa, do której w trakcie działania makra przypisywana jest wartość wpisana przez użytkownika.

W czwartym wierszu kompilator napotyka na etykietę retry:. Jednak, jak wcześniej wspomniano, napotkanie przez kompilator etykiety nie wiążę się z wykonywaniem żadnych operacji, ponieważ etykieta nie jest żadnym poleceniem, lecz czymś w rodzaju adresu w kodzie.

W kolejnym wierszu wywoływana jest funkcja InputBox, w której użytkownik musi wprowadzić nazwę użytkownika, licząca co najmniej 4 znaki. Nazwa ta jest następnie przypisywana do zmiennej nazwa.

W wierszu ósmym znajduje się instrukcja warunkowa, która sprawdza czy podana przez użytkownika nazwa jest krótsza niż 4 znaki. Jeżeli kompilator stwierdzi, że wprowadzona nazwa rzeczywiście jest zbyt krótka, wyświetla najpierw okno z odpowiednim komunikatem (wiersz 9), a następnie wywołuje polecenie GoTo retry, które przenosi wykonywanie kodu z powrotem do wiersza 5. Użytkownik ponownie jest więc proszony o wpisanie nazwy. Schemat ten jest powtarzany tak długo, aż użytkownik poda w końcu nazwę liczącą co najmniej 4 znaki.

Jeżeli nazwa podana przez użytkownika będzie liczyła co najmniej 4 znaki, warunek przypisany do instrukcji warunkowej, z wiersza 8 będzie fałszywy i kompilator przejdzie do wykonywania operacji znajdujących się za tą instrukcją (w powyższym przykładzie operacje te zastąpiono jedynie komentarzem).

Gdyby w powyższym przykładzie instrukcja GoTo miała być zastąpiona pętlą Do ... Loop, kod wyglądałby tak, jak w poniższej ramce:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sub pobieranieNazwy()
    Dim nazwa As String

    Do
        nazwa = InputBox("Podaj nazwę użytkownika." & vbCrLf & _
                "Nazwa musi liczyć co najmniej 4 znaki" , "Nazwa")

        If Len(nazwa) < 4 Then
               Call MsgBox("Nazwa musi liczyć co najmniej 4 znaki", vbExclamation, "Za krótka nazwa")
        End If
    Loop While Len(nazwa) < 4

    'dalsze operacje, np. zapisanie nazwy do bazy danych

End Sub

Znajdująca się w tej procedurze pętla Do ... Loop zawiera warunek Len(nazwa) < 4 poprzedzony słowem kluczowym While, co oznacza, że jest ona wykonywana tak długo, jak zmienna nazwa przechowuje tekst krótszy niż 4 znaki.

Zauważ jednak, że jeśli makro ma wyświetlać komunikat informujący użytkownika o zbyt krótkiej nazwie, wewnątrz tej pętli trzeba umieścić instrukcję warunkową, sprawdzającą dokładnie ten sam warunek, który jest przypisany do pętli: Len(nazwa) < 4. Oznacza to, że przy każdym wywołaniu pętli warunek ten będzie sprawdzany dwukrotnie, co jest złamaniem podstawowej zasady, mówiącej o unikaniu powtarzania tych samych czynności.

Można byłoby wstawić wspomniany komunikat MsgBox w wierszu 5, przed przypisaniem do zmiennej nazwa wyniku funkcji InputBox. Jednak w takiej sytuacji komunikat o zbyt krótkiej nazwie pokazałby się na ekranie jeszcze zanim użytkownik zdążył podać jakąkolwiek nazwę, co jest również nie do przyjęcia.

Jeżeli komuś bardzo zależałoby na użyciu w powyższym przykładzie pętli Do ... Loop zamiast polecenia GoTo, mógłby spróbować rozwiązać to za pomocą nieskończonej pętli i wykorzystania polecenia Exit Do, jednak wydaje się, że użycie w tej sytuacji instrukcji GoTo jest całkowicie akceptowalnym rozwiązaniem.