Pchełki VBA – odcinek 11: Odbijany

W dzisiejszym numerze "Pchełek" pokażę jak w prosty sposób zrobić "odbijankę". A więc: prostokąt X na Y pikseli, zaczynamy z górnego lewego rogu, rysujemy linię ukośną w dół w prawo aż trafimy na bok prostokąta, wtedy "odbijamy" się na zasadzie promienia świetlnego i tak w kółko Macieju aż dotrzemy do któregokolwiek z narożników.

Aha, i chcemy, żeby to było widoczne dla użytkownika, a więc nie kończymy zabawy w 10 milisekund tylko chcemy widzieć jak się ów "promień" przemieszcza.

Dla niecierpliwych, najpierw gotowe rozwiązanie. Szczegóły omówię, jak zwykle, później.

W osobnym module:

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Const BottomRight As String = "BF47"

Sub Preformatuj()
  Range("A1:" & BottomRight).ColumnWidth = 1
  Range("A1:" & BottomRight).RowHeight = 8
  Range("A1:" & BottomRight).ClearFormats
  Range("A1:" & BottomRight).ClearContents
  Range("A1:" & BottomRight).BorderAround LineStyle:=xlContinuous, Weight:=xlMedium, Color:=RGB(200, 100, 100)
End Sub

Sub Lissa()
  Dim x As Integer, y As Integer, maxx As Integer, maxy As Integer, stepx As Integer, stepy As Integer

  maxx = Range(BottomRight).Column - 1
  maxy = Range(BottomRight).Row - 1

  x = 0
  y = 0

  stepx = 1
  stepy = 1

  Do
    x = x + stepx
    y = y + stepy
    If x = maxx Then stepx = -1
    If x = 0 Then stepx = 1
    If y = maxy Then stepy = -1
    If y = 0 Then stepy = 1
    Range("A1").Offset(y, x).Value = "."
    Range("A1").Offset(y, x).Interior.Color = RGB(10,20,100)
    Sleep 10
    DoEvents
  Loop While Not ((x = 0 Or x = maxx) And (y = 0 Or y = maxy))
End Sub

W module Skoroszytu ("ThisWorkbook"):

Private Sub Workbook_Open()
  Preformatuj
  Lissa
End Sub

Zapisujemy to jako Macro-enabled workbook (czyli .xlsxm chyba że ktoś ma starszego Excela wtedy standardowe .xls)

Zamykamy dokument, otwieramy, podziwiamy 😉

Teraz wyjaśnienie krok po kroku:

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Ta linia kodu wymaga odrobiny uwagi. Otóż, uwaga, deklarujemy tutaj procedurę o nazwie Sleep, z jednym parametrem dwMilliseconds typu Long (duży, całkowitoliczbowy), a także wskazujemy skąd definicja owej procedury pochodzi: z biblioteki kernel32.dll, która jest biblioteką systemową Windows.

Jeszcze raz: importujemy tutaj zewnętrzną względem naszego kodu procedurę, która została zdefiniowana w systemowej bibliotece DLL o nazwie kernel32, i która przyjmuje na wejściu jeden parametr typu liczba całkowita.

Tu objawia się cała magia VBA, a właściwie nawet nie VBA tylko modelu COM (Common Objects Model). Otóż można w ten sposób zaimportować do własnego kodu dowolną funkcję bądź procedurę z zewnętrznej biblioteki, a następnie używać jaj we własnym kodzie. Piękna sprawa.

Procedura Sleep czeka zadaną ilość milisekund. Przyda się potem przy "spowalnianiu" naszego odbijanego "promienia światła".

Const BottomRight As String = "BF47"

Deklarujemy tutaj adres dolnego prawego narożnika naszego prostokąta. Górny lewy narożnik to zawsze komórka A1.

Sub Preformatuj()
  Range("A1:" & BottomRight).ColumnWidth = 1
  Range("A1:" & BottomRight).RowHeight = 8
  Range("A1:" & BottomRight).ClearFormats
  Range("A1:" & BottomRight).ClearContents
  Range("A1:" & BottomRight).BorderAround LineStyle:=xlContinuous, Weight:=xlMedium, Color:=RGB(200, 100, 100)
End Sub

Ta mała procedura ustawia szerokości kolumn i wysokości wierszy w zakresie naszego prostokątnego "pudełka", a także usuwa wszelkie wartości i formaty komórek. Czyli taki "reset" systemu przed początkiem właściwym.

Sub Lissa()

Procedurę nazwałem Lissa (bez parametrów) bo początkowo chciałem napisać pchełkę rysującą krzywe Lissajou. Niestety, wrodzone lenistwo, a także brak czasu, spowodowały, że poszedłem po najmniejszej linii oporu i zamiast tego zrobiłem "odbijanego".

Dim x As Integer, y As Integer, maxx As Integer, maxy As Integer, stepx As Integer, stepy As Integer

Sześć zmiennych, wszystkie typu całkowitoliczbowego. Po kolei:
x,y - aktualna pozycja
maxx, maxy - ograniczniki (czyli prawa oraz dolna krawędź prostokąta - krawędzie lewa i górna to zawsze pierwszy wiersz i pierwsza kolumna arkusza)
stepx, stepy - odpowiednio 1 lub -1, czyli kierunek przemieszczania się "promienia" w wymiarze poziomym (lewo/prawo) i pionowym (góra / dół).

maxx = Range(BottomRight).Column - 1
maxy = Range(BottomRight).Row - 1

Ustawiamy prawą krawędź "pudełka" na numer kolumny, w której znajduje się komórka "BF47", a dolną na odpowiedni numer wiersza. Odejmujemy jedynkę, a więc numeracja wierszy / kolumn będzie zaczynać się od zera.

x = 0
y = 0

Startujemy z górnego lewego narożnika.

stepx = 1
stepy = 1

Początkowo przemieszczamy się po skosie w dół i w prawo.

Do
  ...
Loop While Not ((x = 0 Or x = maxx) And (y = 0 Or y = maxy))

Główna pętla. Rysujemy "promień" dopóki nie trafimy na narożnik.

A w pętli...

x = x + stepx
y = y + stepy

Przesuń x o jeden w pionie (w dół lub w górę, w zależności od bieżącego kierunku), przesuń y o jeden w poziomie (w lewo lub w prawo, zależy od bieżącego kierunku)

If x = maxx Then stepx = -1
If x = 0 Then stepx = 1
If y = maxy Then stepy = -1
If y = 0 Then stepy = 1

Jeżeli trafiłeś na krawędź, zmień kierunek ruchu.

Range("A1").Offset(y, x).Value = "."
Range("A1").Offset(y, x).Interior.Color = RGB(10,20,100)

Wstaw kropkę oraz sformatuj tło na niebiesko. Przy okazji, funkcja RGB generuje liczbę całkowitą odpowiadającą 24-bitowemu kolorowi o składowych ośmiobitowych odpowiednio R (składowa czerwona, pierwszy parametr), G (zielona składowa, drugi parametr) i B (niebieska składowa, trzeci parametr). Tak więc RGB(255,0,0) to "czysta czerwień na maksa", RGB(0,128,0) to "zieleń na pół gwizdka" a RGB(120, 120, 0) to typowa "gęsia kupa", czyli mieszanka zieleni z czerwienią.

Sleep 10

Tutaj wołamy zaimportowaną uprzednio funkcję systemową Sleep, czyli wstrzymujemy wykonywanie kodu na 10 milisekund. Większa wartość spowolni "promień światła", mniejsza przyspieszy go.

DoEvents

Procedura DoEvents powoduje chwilowe przerwanie wykonywania kodu oraz wykonanie "zaległych" zdarzeń aplikacji, takich jak kliknięcia myszą, zdarzenia timerów i tak dalej. Dzięki temu DoEvents możemy w trakcie wykonywania naszego kodu zaznaczać inne komórki arkusza, wybierać opcje z menu aplikacji i tak dalej.

Wreszcie:

Private Sub Workbook_Open()
  Preformatuj
  Lissa
End Sub

Tutaj obsługujemy zdarzenie _Open naszego skoroszytu, w którym wołamy obydwie uprzednio pracowicie napisane procedury. A więc, "odbijanka" zadziała zaraz po otwarciu dokumentu.

Nudy, panie...

14 komentarzy

  1. ..mnie nie działa.. 😀

    ale już późno, jutro pomyślę, gdzie zrobiłEŚ błąd 😀 znaczy się dziś, ale za dnia ;]

    pozdrawiam 🙂

    1. No tak, ale z rysowaniem takiej “odbijanej” linii tak zawsze będzie: jeżeli długości boków “świata” są liczbami względnie pierwszymi, prędzej czy później powstanie właśnie “szachownica”.

      1. No już doszedłem do tego co jest nie tak. Po prostu całość rysowała mi się podczas otwierania pliku i wadziłem efekt końcowy. Zrobiłem sobie uruchamianie pod przyciskiem i teraz mogę obserwować pracę 🙂

        Przy okazji mam pytanie: Czy dało by się zrobić coś podobnego na obiektach np. Kształtach (Shapes) lub własnych obiektach (zdefiniowanych przez siebie)? Konkretnie chodzi mi o to żeby kształty się widziały i jeżeli w danym miejscu jest już jakiś kształt to następny ustawiał by się obok albo krzyczał że tam już zajęte.

        1. Wszystko się da, ale zagadnienie, które poruszasz, ma niewiele wspólnego z “odbijanym”. Tu już trzeba by zaprząc do pracy cięższe działa, ponieważ kształty mogą być nieregularne i wykrywanie ich “nachodzenia” na siebie nie jest trywialne. No chyba żeby potraktować każdy kształt jako prostokąt i operować tylko na obszarach prostokątnych, wtedy można to uprościć.

          1. W sumie mogły by być prostokąty. np. przemieszczanie jednego prostokąta tak aby nie wchodził na inne.

            1. To już jest względnie proste. Może znajdę wolną chwilę i coś spłodzę w międzyczasie, chociaż w VBA nie pisałem już wieki…

                1. Pamiętam, pamiętam. Tylko za leniwy jestem, żeby coś od razu napisać, a za bardzo honorowy, żeby szukać gotowców w necie 😉 Ale co się odwlecze to nie uciecze, spokojna głowa.

                  1. Czekam z niecierpliwością. Ostatnio pierwszy raz użyłem własnych obiektów w kodzie. Miałem jednak problem z tym aby obiekty się “widziały” wzajemnie i nie nachodziły na siebie. Znasz może jakieś sposobu na to? Jak np. w grach postacie (które są chyba obiektami) nie wchodzą na ściany czy drzewa (które chyba też są obiektami). Nigdy wcześniej się nad tym nie zastanawiałem. Czy da się tak zakodować obiekty aby wiedziały o sobie?

                    1. Nie jestem zawodowym programistą więc nie wiem, jak to robią fachowcy, ale wyobrażam sobie, że po każdej klatce trzeba wykonać jakąś pętlę, w której każda para obiektów przechodzi przez prostą operację arytmetyczną sprawdzającą, czy przy danych prędkościach ich powierzchnie się w kolejnej klatce ze sobą zetkną (lub przenikną) i jeżeli tak, odpowiednio zareagować (czyli pozmieniać ich wektory prędkości na takie, jakie wynikają z ewentualnego odbicia).

                      W przypadku obiektów stricte prostokątnych (lub jeszcze lepiej: kulistych) taka arytmetyka jest dość banalna. W przypadku bardziej zróżnicowanych kształtów już niekoniecznie, acz wszystko jest osiągalne. Pozostaje jeszcze kwestia czy bawimy się w 2d czy w 3d, czy obiekty są sprężyste, czy ich powierzchnie mają współczynniki tarcia, czy uwzględniamy ich masy (a tym samym przekazywanie energii) oraz straty energii i tak dalej. Można zbudować dowolnie skomplikowany model docelowo… A i tak nigdy nie uwzględni się wszystkiego.

                    2. Hmmm o tym nie pomyślałem. Na razie lecę na urlop ale jeśli pozwolisz odezwę się w tej sprawie za tydzień.

Leave a Comment

Twój adres e-mail nie zostanie opublikowany.