piątek, 27 lutego 2015

Debugowanie kodu- prosta sztuczka

Zdobywając coraz większą wiedzę i doświadczenie w obszarze VBA zaczynamy tworzyć coraz bardziej rozbudwoane linie kodu. Szczególnie referencje do złożonych obiektów przestają być problematyczne dzięki coraz lepszemu rozumieniu hierarchii obiektów i samych obiektów i kolekcji. W pewnych sytuacjach zdarzyć się może, że określona linia kodu będzie mieć następującą schematyczną postać:
1Dim myObject as Object
2Set myObject = Object.Object.Object.Object.Object
lub w konkretnym przykładzie:
1Dim Komentarz As Object
2Set Komentarz = ActiveCell.Comment.Shape.TextFrame2.TextRange
Jeżeli nasza referencja jest błędna zwrócony zostanie błąd 445: `Object doesn’t support this action`. Pytanie jednak- której ‘akcji’? jeżeli wszystko wydaje się być poprawne czasem trudno o szybkie odnalezienie błędu. Najprostszym rozwiązaniem jest więc rozbicie poszczególnych elementów składowych naszego kodu na kolejne zmienne. W tym wariancie błąd zostanie zwrócony w linii ze wskazaniem konkretnej właściwości/obiektu, który jest błędny. Poniżej dwa przykłady takiego ‘sprytnego debugowania’- wariant krótki bez deklaracji konkretnego oczekiwanego typu obiektu oraz wariant rozbudowany definiujący konkretne typy obiektów (zalecany).
01Sub SprytneDebugowanie()
02 
03    Dim Komentarz As Object
04    Set Komentarz = ActiveCell.Comment.Shape.TextFrame2.TextRange
05     
06    'wariant szybki
07    Dim A, B, C, D 'tu As Variant, opcjonalnie As Object
08    Set A = ActiveCell.Comment
09    Set B = A.Shape
10    Set C = B.TextFrame2
11    Set D = C.TextRange
12     
13End Sub
14 
15Sub SprytneDebugowanie2()
16     
17    Dim Komentarz As Object
18    Set Komentarz = ActiveCell.Comment.Shape.TextFrame2.TextRange
19 
20    'wariant dokładny
21    Dim A As Comment
22    Dim B As Shape
23    Dim C As TextFrame2
24    Dim D As TextRange2
25    Set A = ActiveCell.Comment
26    Set B = A.Shape
27    Set C = B.TextFrame2
28    Set D = C.TextRange
29         
30End Sub

piątek, 20 lutego 2015

Jak odnaleźć źródło danych dla wykresu?

Utworzenie wykresu z wykorzystaniem kodu VBA jest relatywnie łatwe, zawsze można wspomóc się rejestratorem makr co szczególnie dotyczy początkujących adeptów programowania. W jaki sposób odnaleźć źródło danych dla wskazanego wykresu, a więc zakres komórek, na bazie których nasz wykres rysuje linie czy kolumny? W tym zadaniu nie pomoże rejestrator makr, całą koncepcję musimy oprzeć na obiektach powiązanych z wykresem wykorzystując precyzyjnie hierarchię tych obiektów i odpowiednie właściwości.

Jako punkt odniesienia przyjmijmy następujący wykres liniowy posiadający jedną serię danych.



W rozwiązaniu, które poniżej musimy wykorzystać następujące obiekty i ich właściwości:

1. Obiekt Chart, czyli wykres, i jego właściwość:
2. Series, czyli serię danych wykresu, oraz jej właściwość
3. Formula, zwracającą formułę tworzącą referencję do zakresu danych.

Właściwy kod niezbędny do odnalezienia poszukiwanego zakresu wraz z dodatkowymi komentarzami znajduje się poniżej.
01Sub GetSourceCellsAddress()
02     
03    'wykorzystujemy zmienną obiektową,
04    'tworzymy referencję do pierwszego wykresu w arkuszu
05    Dim CH As Chart
06    Set CH = ActiveSheet.ChartObjects(1).Chart
07     
08    'zmienna obiektowa wskazująca serię danych wykresu
09    '(pierwszą i w naszym wypadku jedyną)
10    Dim SC As Series
11    Set SC = CH.SeriesCollection(1)
12     
13    'to czego tak na prawdę teraz poszukujemy to
14    'formuła dla serii danych
15    Dim strFormula As String
16        strFormula = SC.Formula
17        'test formuły:
18        Debug.Print strFormula
19        'wynik dla przykładu:
20        '=SERIE(Arkusz1!$B$1;Arkusz1!$A$2:$A$7;Arkusz1!$B$2:$B$7;1)
21         
22    'w praktyce poszukujemy fragmentu formuły, trzeciego parametru
23    'który odpowiada za obszar danych, ty wykorzystamy kilka
24    'podstawowych tekstowych funkcji VBA
25    Dim strRangeFromFormula As String
26        strRangeFromFormula = Mid(strFormula, _
27                        InStrRev(strFormula, "!") + 1, _
28                        InStrRev(strFormula, ",") - InStrRev(strFormula, "!") - 1)
29        'test poszukiwań
30        Debug.Print strRangeFromFormula
31        'wynik: $B$2:$B$7
32     
33    'poszukiwany zakres zaznaczymy więc tak
34    Range(strRangeFromFormula).Select
35     
36    '*kod bonusowy- wyjaśnienie poniżej
37  
38End Sub

A co jeżeli chcielibyśmy odnaleźć kolejne elementy, punkty naszego wykresu i referencję do zakresu dla każdego z nich? W tym celu w miejscu oznaczonym gwiazdką w powyższym przykładzie musimy wstawić poniższy kod.
1'w celu odczytania kolejnych punktów wykresu w naszym zakresie komórek
2'-wykonamy tą operację z wykorzystaniem prostej pętli
3Dim rngPT As Range
4For Each rngPT In Range(strRangeFromFormula)
5    Debug.Print rngPT.Address
6    'wynik to kolejno $B$2, $B$3, $B$4, itd...
7Next rngPT

piątek, 6 lutego 2015

Zaokrąglanie liczb- funkcja Round vs. UDF

W dobie operacji finansowych wykonywanych dla jednostek wyrażanych w czterech, pięciu miejscach po przecinku zaokrąglanie wyników może sprawiać pewien problem. Szczególny problem pojawia się w momencie, gdy sięgamy po standardową, wbudowaną funkcję VBA Round. Okazuje się bowiem, że funkcja ta nie zaokrągla w sposób jaki oczekujemy. Zobrazuje to poniższy przykładowy kod:
1Debug.Print Round(0.515, 2) '>> 0,52
2Debug.Print Round(0.525, 2) '>> 0,52
3Debug.Print Round(0.535, 2) '>> 0,54
4Debug.Print Round(0.545, 2) '>> 0,55
5Debug.Print Round(0.555, 2) '>> 0,56
6Debug.Print Round(0.565, 2) '>> 0,56
7Debug.Print Round(0.575, 2) '>> 0,57
8Debug.Print Round(0.585, 2) '>> 0,58
Jak widać zwracane wynik są w niektórych sytuacjach lekko zaskakujące. Teoretycznie bowiem obowiązuje tu zasada, iż w sytuacji, gdy zaokrąglana wartość wynosi 5 (jak w każdym powyższym przypadku) to zaokrąglenie w górę nastąpi o ile poprzednia liczba jest nieparzysta, oraz w dół- gdy poprzednia liczba jest parzysta. Jak widać nie w każdym przypadku jest to prawdziwe.

Jeżeli więc potrzebujemy klasycznej techniki zaokrąglania to niezbędne jest stworzenie własnej funkcji zaokrąglającej. Poniżej przykład takie funkcji:
01Public Function RoundUDF(Wartosc As Double, _
02                    Optional MiejscPoPrzecinku As Integer = 0) As Double
03         
04On Error GoTo ErrHandler
05     
06    RoundUDF = Round((Wartosc * (10 ^ MiejscPoPrzecinku)) + 0.5, 0) / _
07                               (10 ^ MiejscPoPrzecinku)
08 
09Exit Function
10ErrHandler:
11    MsgBox Err.Number & vbNewLine & Err.Description
12       
13End Function


Wyniki działania naszej funkcji zaokrąglającej prezentuje poniższy przykład:
1Debug.Print RoundUDF(0.515, 2) '>> 0,52
2Debug.Print RoundUDF(0.525, 2) '>> 0,53
3Debug.Print RoundUDF(0.535, 2) '>> 0,54
4Debug.Print RoundUDF(0.545, 2) '>> 0,55
5Debug.Print RoundUDF(0.555, 2) '>> 0,56
6Debug.Print RoundUDF(0.565, 2) '>> 0,57
7Debug.Print RoundUDF(0.575, 2) '>> 0,58
8Debug.Print RoundUDF(0.585, 2) '>> 0,59

poniedziałek, 2 lutego 2015

Łączenie tekstów- ampersand (&) vs. plus (+)

Łączenie tekstów to powszechne działanie w VBA. Teoretycznie do dyspozycji mamy dwa znaki złączające: ampersand: & oraz plus:+. W praktyce jednak nie każdy zdaje sobie sprawę z faktu, że w pewnych okolicznościach zamienne zastosowanie łączników da odmienne wyniki. I choć zagadnienie to dotyczy szczególnie operacji w środowisku Access VBA myślę, że warto pamiętać o różnicach szczególnie gdy pracować będziemy ze zmiennymi.

Ampersand łączyć będzie tekst w każdych okolicznościach i niezależnie od bieżącej wartości przechowywanej w zmiennej.

Plus łączyć będzie teksty tak długo jak zmienne będą przechowywać jakąkolwiek wartość, jeżeli jednak jedna ze zmiennych przechowywać będzie wartość Null to wynikiem złączenia z wykorzystaniem operatora plus również będzie Null.

Różnicę w działaniu operatorów najprościej będzie zaprezentować na poniższym przykładzie gdzie wykorzystane zostały znaki łączenia tekstu w pracy ze zmiennymi, w tym ze zmienną o wartości Null.
01Sub LaczenieTekstow()
02 
03    Dim A As String
04    Dim B As String
05    Dim C As Variant
06     
07    A = "Imię"
08    B = "Nazwisko"
09     
10    Debug.Print A & B   '>>ImięNazwisko
11    Debug.Print A + B   '>>ImięNazwisko
12    Debug.Print A & C   '>>Imię
13    Debug.Print A + C   '>>Imię
14     
15    'do tego momentu C miało domyślną wartość równą vbNullString
16     
17    C = Null
18    Debug.Print A & C   '>>Imię
19     
20    Debug.Print A + C   '>>Null !!!
21    Debug.Print C + A   '>>Null !!!
22     
23    Debug.Print A & B & C   '>>ImięNazwisko
24    Debug.Print A + B & C   '>>ImięNazwisko
25    Debug.Print A & B + C   '>>Imię
26     
27    Debug.Print C + A + B   '>>Null !!!
28 
29End Sub