poniedziałek, 24 lutego 2014

Kompilacja warunkowa #If...#End If

Tworząc określoną rozbudowaną aplikację w VBA naszym celem może być stworzenie jej w taki sposób, aby zależnie od zdefiniowanych parametrów jej przebieg był odmienny. Możliwe przykłady takich działań:

a) wersja testowa i ostateczna
b) wersja uruchamiana przez administratora i pozostałych użytkowników
c) wersja dla nowszych i starszych wersji aplikacji MS Excel/MS Office

W w/w i podobnych sytuacjach zasadne może się okazać wykorzystanie narzędzia kompilacji warunkowej.

Pierwszym krokiem będzie wskazanie elementów, parametrów kompilacji.
W tym celu w edytorze VBA należy uruchomić poniższe okno znajdujące się w Menu >> Tools >> VBAProject Properties... >> zakładka General. Następnie w ostatniej pozycji tego okna, "Conditional Compilation Arguments:", należy zdefiniować wszystkie niezbędne zmienne/warunki rozdzielając je symbolem dwukropka. Co ważne- nasze zmienne warunkowe mogą przyjmować tylko wartości liczbowe, tekst nie jest dopuszczalny.



Kolejny krok to umiejętne wykorzystanie stworzonych zmiennych warunkowych w naszym kodzie.
Kluczowe w tym momencie będzie wykorzystanie znaku # (sharp/hash), który musi wystąpić przed każdą linią instrukcji warunkowej #If...#ElseIf...#Else...#End If.

Przykład A) Zmienna warunkowa compVersion ma za zadanie wskazywać ten fragment kodu, który zostanie wykonany zależnie od zadeklarowanej wersji aplikacji.

Przykład B) W przypadku kompilacji wykonywanej przez administratora należy w powyższym oknie ustawić wartość compAdmin =1, kompilacja warunkowa przypisze zmiennej Pass hasło na hasło administratora, w innym wariancie poprosi użytkownika o podanie hasła

Przykład C) niepoprawny. Proszę zwrócić uwagę, że poniższy kod nie jest poprawny ze względu na brak symboli # na początku każdej linii instrukcji warunkowej. Przy próbie wywołania poniższego kodu uzyskamy informację o braku zadeklarowanej zmiennej compUser. Samo zdefiniowanie tej zmiennej w pierwszym kroku nie jest więc wystarczające.

Wskazówka! Stałe, parametry kompilacji można również zdefiniować wewnątrz kodu VBA zamiast w oknie VBAProject Properties zaprezentowanym w pierwszym kroku powyżej. W tym celu również niezbędne będzie wykorzystanie znaku # w celu utworzenia np. takiego fragmentu kodu:

poniedziałek, 17 lutego 2014

Sortowanie alfabetyczne arkuszy

Kilkukrotnie spotkałem się ze skoroszytami składającymi się z dziesiątek, wręcz setek arkuszy. W kilku przypadkach autorzy tego typu skoroszytów mieli problem z utrzymaniem układu arkuszy w porządku alfabetycznym (na czym bardzo im zależało). Prosty kod VBA potrafi wykonać tą operację w... ułamku sekundy. Prezentując metody sortowania arkuszy chciałbym jednak zwrócić uwagę na umiejętność wykorzystania technik arkuszowych w celu przyspieszenia tego typu rozwiązania.

Podejście 1. 
Problem sortowania czegokolwiek to szerokie zagadnienie. Możemy zastosować kilka różnych metod sortujących zależnie od sytuacji. Wyobraźmy sobie jednak, że nie znamy się na sortowaniu bąbelkowym, zliczającym, itp.,  ale potrafimy stworzyć prosty mechanizm logiczny, który ujmę w następujący algorytm:

a. dla kolejnych arkuszy sprawdź, czy arkusz następny nie powinien być przed arkuszem sprawdzanym
b. jeżeli tak to arkusz następny przenieś przed arkusz sprawdzany
c. rozpocznij weryfikację od początku

Rozwiązanie powyższe przedstawia prosty poniższy kod:


Podejście 2.
Jedną z najbardziej wydajnych technik sortowania jest ta, która znamy z procesu sortowanie komórek. W tym podejściu wykorzystamy tą technikę. Kolejne kroki algorytmu to:

a. utworzymy tymczasowy arkusz i zapiszemy do niego nazwy wszystkich arkuszy naszego skoroszytu
b. posortujemy listę uzyskaną w powyższym kroku
c. kolejno ułożymy arkusze w porządku zgodnym z posortowaną listą z punktu b
d. a na koniec wykasujemy nasz tymczasowy arkusz z punktu a.

Powyższy algorytm prezentuje poniższy kod. Z pewnością na pierwszy rzut oka widać różnicę w długości kodu. Proszę jednak zapoznać się z podsumowaniem na końcu niniejszego postu.


Podsumowanie.
Powyższe dwie procedury są doskonałym sposobem na porównanie wydajności różnych technik. Choć wydaje się, że wykonując znacznie więcej kroków w podejściu 2 kod może wykonywać się dłużej to wcale tak nie jest. Otóż procedura 2 pozwala na wykonanie zadania w czasie około 5-7 razy krótszym niż wariant 1.

Ciekawostka.
Gdybyśmy chcieli zmienić kolejność sortowania na malejące to w obu zaprezentowanych wariantach wystarczy dosłownie wstawić lub zamienić po jednym znaku:

a. w Podejściu 1 o kierunku sortowania decyduje znak >< porównujący nazwy arkuszy
b. w Podejściu 2 o kierunku sortowania decyduje obecność lub brak pojedynczego przecinka co prezentują poniższe linie kodu:

poniedziałek, 10 lutego 2014

Przycisk w relacji do komórki wynikowej

Wyobraźmy sobie sytuację, w której w arkuszu, w określonej kolumnie umieszczamy szereg przycisków. Ich rola niech będzie banalna- zwiększenie/zmniejszanie wartości określonej komórki. Przyjmijmy jednak założenie, że komórka wynikowa znajduje się w określonej relacji do przycisku- np. jest to komórka bezpośrednio po lewej stronie względem naszego przycisku. Całość, w lekko rozbudowanym wariancie, niech zobrazuje poniższy schemat.



Chcąc zachować względną relację pomiędzy komórką wynikową a komórką nad którą znajdują się przyciski warto sięgnąć po .TopLeftCell. Właściwość ta występuje w przypadku większości obiektów warstwy rysunkowej i wskazuje komórkę (obiekt Range), nad którą znajduje się lewy górny róg naszego obiektu.

Kod dla przycisku czerwonego, który zmniejsza ilość elementów w kolumnie B będzie więc następujący:

Kod dla przycisku zielonego, który zwiększa ilość elementów w kolumnie B będzie więc następujący:

Zaletą stosowania takiego rozwiązanie jest wspomniana wyżej relacja w położeniu pomiędzy przyciskiem a kolumną wynikową. Pozwala nam to na wstawienie kilku kolumn po lewej stronie od kolumny B oraz  wierszy powyżej naszego przykładowego rzędu. W obu przypadkach przyciski nadal będą działać w prawidłowej relacji do komórki wynikowej.

poniedziałek, 3 lutego 2014

Przekazywanie danych do/ze schowka

W jaki sposób wykorzystać schowek (clipboard) w celu przekazania informacji pomiędzy różnymi aplikacjami, obszarami czy obiektami arkusza czy skoroszytu? W większości przypadków do dyspozycji mamy metody typu .Copy >> .Paste >> .PasteSpecial, które występują w modelu obiektowym całego pakietu Microsoft Office. Techniki te umożliwiają przekazywanie (kopiowanie) tabel, obiektów warstwy rysunkowej, tekstu, itp.

Istnieje jednak specjalna technika, która pozwala na zapełnienie schowka określonym tekstem, a także umożliwia pobranie aktualnych wartości ze schowka o ile dotyczą tekstu (lub wartości liczbowych). Prześledźmy to rozwiązanie w kolejnych krokach zaprezentowanych poniżej.

1. Niezbędne referencje
Metoda ta wymaga ustanowienia referencji do biblioteki 'Microsoft Forms 2.0 Object Library'. W tym celu w edytorze VBA wybieramy kolejno Menu >> Tools >> References >> odszukujemy i zaznaczamy w/w bibliotekę.
Wskazówka! Jeżeli w naszym projekcie utworzony jest choćby jeden UserForm oznaczać to będzie, że odpowiednie referencje zostały ustanowione.

2. Tworzenie instancji obiektu DataObject
Praca ze schowkiem wymagać będzie utworzenia obiektu, który umożliwi przekazywanie wartości. Poniższe linie tworzą tzw. wiązanie wczesne (early binding) do obiektu DataObject.


3. Dodanie tekstu do schowka
To pierwsza sytuacja, w której wykorzystamy uprzednio utworzony obiekt. W pierwszej kolejności zdefiniujemy jaki tekst ma trafić do schowka, ale dopiero kolejna linia procedury skieruje strumień tekstu w tą przestrzeń pamięci.


4. Pobranie wartości ze schowka
Także i w tym przypadku  niezbędne będą 2 linie kodu- pierwsza pobiera zawartość schowka do naszego obiektu, a kolejne linie prezentują różne warianty skierowania strumienia tekstu: do okna immediate, do komórki czy też do zakresu komórek.


5. Uwagi końcowe. 
Wykorzystanie obiektu DataObject umożliwia, przynajmniej na chwilę obecną, pracę wyłącznie z tekstem. Zawartość tego obiektu przechowywana jest tak długo jak długo istnieje obiekt. Zawartość przekazana do schowka pozostaje w nim także po wykasowaniu instancji obiektu DataObject.