wtorek, 28 stycznia 2014

Okno wyboru folderu

W czasie prowadzonych szkoleń z podstaw VBA dla MS Excel prezentuję jedną z podstawowych technik wyboru folderu z wykorzystaniem wbudowanego okna FileDialogs. Szybko może przypomnę tą technikę w trzech krokach:

1. kod VBA

1Dim A As FileDialog
2On Error Resume Next
3Set A = Application.FileDialog(msoFileDialogFolderPicker)
4A.Show
5Debug.Print A.SelectedItems(1)
6On Error GoTo 0

2. którego efektem dziania jest następujące okno wyświetlane użytkownikowi



3. w efekcie wybrania przez użytkownika określonego folderu zwracany jest wynik w postaci ścieżki do tego folderu:

1C:\Downloads\music

Alternatywne rozwiązanie opiera się  o wykorzystanie zewnętrznej biblioteki Shell.  W wyniku wywołania odpowiednich funkcji wybór folderu odbywa się w oparciu o inne okno, bardziej znane ze środowiska Windows niż samego pakietu Office.


W jaki sposób wygląda kod do tego typu okna. Ponieważ naszym celem jest uzyskanie informacji o wskazanej przez użytkownika ścieżce dlatego też samo rozwiązanie ukrywamy najczęściej w funkcji VBA a nie procedurze. Oto przykładowa funkcja wyświetlająca powyższe okno:
01Function GetFolderPath(Optional OpenAt As Variant) As Variant
02 
03    Dim ShellLib As Object
04    Set ShellLib = CreateObject("Shell.Application"). _
05                BrowseForFolder(0, "Proszę wskazać folder!", 0, OpenAt)
06 
07    On Error Resume Next
08    BrowseForFolder = ShellLib.self.Path
09    On Error GoTo 0
10 
11    Set ShellLib = Nothing
12 
13End Function

Powyższa funkcja nie jest mojego autorstwa lecz uwzględnia moje modyfikacje i uproszczenia. Wiele wariantów tego rozwiązania można odnaleźć w sieci. Stosuję jednak to rozwiązanie o wiele chętniej niż wariant standardowo wbudowany w środowisko VBA prezentowany na początku niniejszego postu.

Pozostała jeszcze kwestia wywołania tej funkcji. W tym celu wystarczy nam poniższy fragment kodu:

1Dim JakiFolder As String
2 
3'opcjonalnie możemy wskazać folder, który _
4 zostanie wskazany przy otwarciu okna
5JakiFolder = GetFolderPath("C:\")
6Debug.Print JakiFolder

Sam wynik, jak łatwo się domyślić, będzie identyczny jak w pierwszym przypadku:

1C:\Downloads\music

czwartek, 23 stycznia 2014

Wypełnianie tablicy Array określoną wartością

Tablice Array, podobnie zresztą jak wszystkie zmienne, w momencie ich deklaracji uzyskują wartość domyślną dla danego typu. I tak np. poniższe tablice przyjmą następujące wartości początkowe:

a. liczby całkowite, wartość początkowa równa 0 (zero)
1Dim intTBL(10) As Byte
2Debug.Print intTBL(0), TypeName(intTBL)   '>> wynik:  0 Integer()

b. ciągi tekstowe, wartość początkowa- pusty ciąg tekstowy
1Dim strTBL(10) As String
2Debug.Print strTBL(0), TypeName(strTBL)   '>> wynik:   String()

c. tablica wariant, wartość początkowa- pusta (Empty)
1Dim varTBL(10)
2Debug.Print varTBL(0); TypeName(varTBL)   '>> wynik: Empty Variant()

Co należałoby zrobić aby utworzyć tablicę, której elementy zostaną zainicjowane z inną niż domyślna (zerowa) wartość przyjmując jednocześnie założenie, że nie chcemy w tym celu korzystać z pętli? Poniżej kilka z możliwych rozwiązań.

1. Funkcja API FillMemory dla tablic typu Byte
Jeżeli naszą tablicę chcemy wypełnić wartościami z przedziału - do 255 możemy posłużyć się następującym rozwiązaniem opartym o funkcję API:
01'deklaracja funkcji w górnej części modułu
02Private Declare Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" _
03        (dest As Any, ByVal size As Long, ByVal fill As Byte)
04 
05'wypełnienie tablicy wewnątrz procedury, dla tablicy jednowymiarowej każdy element wypełniamy wartością 10, dla tablicy dwuwymiarowej każdy jej element wypełniamy wartością 200
06    Dim byTBL(10) As Byte
07        FillMemory byTBL(0), 11, 10
08        Debug.Print byTBL(10)
09    
10    Dim loTBL(1, 10) As Byte
11        FillMemory loTBL(0, 0), 2 * 11, 200
12        Debug.Print loTBL(1, 1)

Składnia funkcji FillMemory:

FillMemory Destination, Length, Fill
gdzie:
Destination to punkt startowy w pamięci deklarowany jako dolny indeks (indeksy) naszej tablicy,
Length to ilość elementów tablicy
Fill to wartość jaką będziemy wypełniać tablicę

2. Evaluate i funkcja arkuszowa
Dla każdego innego rozwiązania możemy posłużyć się poniższą kombinacją funkcji Evaluate, funkcji arkuszowych i deklaracji tablicowych. Poszczególne kroki tej metody zostały opisane w postaci komentarzy w poniższym kodzie.
Uwaga! Rozwiązanie to tworzy tablice z dolnym indeksem równym 1 (a nie 0, co jest wartością domyślną)

01    'tablica liczb rzeczywistych
02Dim sngTBL() As Variant
03    'tablicę jednowymiarową tworzy zakres wskazany w _
04     relacji do komórek A1:A10.
05    'elementy tablicy wypełniamy wartościami 99.99
06sngTBL = Evaluate("=IF(ISERROR(Transpose(A1:a10)), 99.99, 99.99)")
07    'testowanie funkcji
08Debug.Print sngTBL(1), sngTBL(10)
09    'wynik>> 99.99  99.99
10 
11 
12Dim strTBL() As Variant
13    'tablicę dwuwymiarową tworzy zakres wskazany w _
14     relacji do komórek A1:C10.
15    'elementy tablicy wypełniamy wartościami 99.99
16strTBL = Evaluate("=IF(ISERROR(A1:C100), ""test"", ""test"")")
17    'testowanie funkcji
18Debug.Print strTBL(1, 1), strTBL(10, 3)
19    'wynik>> test   test

3. Kombinacja funkcji arkuszowej i funkcji VBA
W wariancie tym tworzymy tymczasowy ciąg tekstowy składający się z zadanej ilości powtórzeń. Następnie konwertujemy ten ciąg na tablice z wykorzystaniem funkcji Split.
1Dim tmpVal As Variant
2tmpVal = WorksheetFunction.Rept("99.99,", 100)
3tmpVal = Left(tmpVal, Len(tmpVal) - 1)
4 
5Dim tmpArr As Variant
6tmpArr = Split(tmpVal, ",")
7 
8Debug.Print tmpArr(1), tmpArr(10)

Uwaga końcowa! Po powyższe rozwiązania warto sięgać w sytuacji, kiedy ilość elementów w tablicy przekracza około 10 tyś. W przeciwnym wypadku wystarczy posłużyć się zwykłą pętlą co, choć nie jest efektowne, będzie rozwiązaniem efektywnym.

piątek, 17 stycznia 2014

Metoda Find w aplikacji MS Word- Przykład 2

Około 3 miesięcy temu opublikowałem pierwszy przykład wykorzystania metody .Find w aplikacji Word. Teraz chciałbym zaprezentować kolejny z przykładów pochodzący z konkretnej 'życiowej' sytuacji.

Niekiedy zdarza się, że importujemy do dokumentu MS Word treść innego dokumentu pochodzącego z innego systemu lub aplikacji. W wyniku takiej operacji utworzony dokument zawiera szereg wpisów (linii, paragrafów), które stanowią symbole podziału sekcji, stron, znaki specjalne pochodzące z tamtego systemu, itp. Generalnie uznać je można za błędne wpisy, które chcemy szybko i skutecznie usunąć. Jak pozbyć się niechcianych paragrafów szybko i skutecznie? Z pomocą przyjdzie nam oczywiście metoda .Find.

Krok 1. Utwórzmy nowy losowy dokument zgodnie z opisem z poprzedniego posta wpisując na jego początku =Lorem(30,1) i wciskając enter. Dodajmy kilka linijek zaczynających się od symboli specjalnych, np. [+], [-]. Całość mogłaby prezentować się następująco:



Krok 2. Określmy parametry początku linii, które chcemy usunąć i umieśćmy je w tablicy Array naszego dokumentu. W naszym przykładzie chcemy usunąć: wszystkie linie (paragrafy) zaczynające się od słowa "Donec", liter "In" oraz symboli "[+]" i "[-]". W rozwiązaniu, które wykona tą operację szczególnie warto zwrócić uwagę na rzędy, w których występują znaki typu []()+- itp. Są to symbole specjalne stosowane w technice Find i dlatego w sposób szczególny musimy je wyróżnić. W tym celu przed każdym z tych znaków musimy wstawić symbol backslash: "\".

Poniższe makro wykona za nas operację usuwania wskazanych, niechcianych wierszy. Dodatkowe uwagi umieszczone zostały w postaci komentarzy w treści kodu.
01Sub Usuwanie_linii()
02 
03    Dim arrRemove As Variant
04        'wszystkie inne kryteria początku linii dodajemy _
05        jako kolejne elementy tablicy
06        arrRemove = Array("Donec", "In", "\[+\]", "\[\-\]")
07 
08    Dim i!
09    For i = 0 To UBound(arrRemove)
10        'aby usuwanie kolejnych elementów było skuteczne _
11         wracamy na początek dokumentu dla każdego _
12         elementu naszej tablicy
13        ActiveDocument.Range(0, 0).Select
14        
15        Selection.Find.ClearFormatting
16        With Selection.Find
17            .Text = arrRemove(i) & "*^13"
18            .Replacement.Text = ""  'replace with nothing
19            
20            .Forward = True
21            .Wrap = wdFindContinue
22            .MatchCase = False
23            .MatchWildcards = True
24        End With
25        Selection.Find.Execute Replace:=wdReplaceAll
26        
27    Next i
28    
29End Sub

poniedziałek, 13 stycznia 2014

Evaluate czyli... (2/2)

Metoda Evaluate raz jeszcze... Tym razem zaprezentuję, jak myślę, zaskakujące dla wielu czytelników zastosowaniu tej instrukcji, a więc metoda Evaluate jako sposób na skróconą definicję tablic Array.

Skrócona metoda tworzenia tablicy jedno- lub wielowymiarowej opiera się na stworzeniu ciągu tekstu gdzie:
a) symbole {} oznaczają definicję tablicy
b) każdy przecinek rozdziela elementy tablicy należące do tego samego jej wymiaru
d) każdy średnik rozdziela wymiary tablicy.
Skrócona metoda ma swoje źródło w sposobie definiowana tablic Array po stronie komórki Excela. Tam właśnie wykorzystujemy w/w symbole i technikę. Evaluate pozwoli nam więc na przeniesienie rozwiązania znanego z aplikacji Excel do środowiska VBA.

Oto przykład tworzenia tablicy Array:
1Dim xString As String
2Dim xArray As Variant
3 
4xString = "{1,2,3,4,5,6}"
5xArray = Evaluate(xString)
6Range("A1").Resize(1, 6).Value = xArray

Stwórzmy teraz tablicę dwuwymiarową w krótszym zapisie:
1xArray = Evaluate("{1,2,3;4,5,6}")
2Range("A3").Resize(2, 3).Value = xArray

Stosując skrócony zapis funkcji Evaluate możemy powyższy przykład skrócić do absolutnego minimum:
1Range("A6").Resize(2, 3).Value = [{1,2,3;4,5,6}]

Wykonanie powyższych przykładów i zwrócenie wartości do arkusza Excel obrazuje poniższy zrzut ekranu.




Metoda Evaluate jest metodą szybką, wydajną, efektywną. Umożliwia stosowanie skróconego zapisu w wielu sytuacjach. Jednym z problemów z jakim się spotkamy w pracy z Evaluate to proces debugowania tej instrukcji. Zagadnienia tego nie będę omawiał przyjmując założenie, że stosowanie Evaluate nie sprawi nikomu problemu. :)

wtorek, 7 stycznia 2014

Evaluate czyli... (1/2)

Zgodnie ze słownikową definicją 'evaluate' znaczy 'oceniać, szacować'. Zgodnie z definicją metody Evaluate języka VBA dodać należy do polskich odpowiedników także słowo 'konwertować, zamieniać'. A czym w praktyce jest metoda Evaluate i w jakich przypadkach warto i należy po nią sięgnąć? Spójrzmy na tą metodę przez pryzmat przykładów.

1. Zwracanie wyniku funkcji arkuszowych i obliczeń matematycznych
Z pewnością znany jest czytelnikom dostęp do funkcji arkuszowych z wykorzystaniem obiektu WorksheetFunction. Poniższe zapisy zwracają wartość sumy dla obszaru A1:A2
1Dim A As Variant
2A = Application.Sum(Range("A1:A2"))
3A = WorksheetFunction.Sum(Range("A1:A2"))
4    Debug.Print A

Alternatywnie podobną operację wykonamy z pomocą metody Evaluate:

1A = Evaluate("sum(A1:A2)")
2    Debug.Print A

Wobec faktu, że nawiasy kwadratowe są odpowiednikiem instrukcji Evaluate także i ten zapis będzie odpowiadał powyższemu przykładowi:

1A = [sum(A1:a2)]
2    Debug.Print A

Skoro zaś Evaluate dokonuje obliczeń matematycznych dlatego też możemy rozszerzyć nasz powyższy przykład:
1A = [3+sum(A1:a2)+10+Sin(90)]
2     Debug.Print A

2. Odwołanie do zakresu komórek- konwersja tekstu na obiekt
Metoda Evaluate pozwala na stosowanie krótkiego zapisu adresu komórek. Poniższe trzy odwołania są poprawne:
1Range("A1:A2").Select
2Evaluate("B1:B2").Select
3[C1:C2].Select

Co ciekawe, zgodnie z informacjami znajdującymi się w sieci ostatnie z odwołań należy do najbardziej efektywnych (najszybszych). Podobnie zresztą metody Evaluate wykona szybciej obliczenia w porównaniu do zastosowania obiektu WorksheetFunction z przykładu 1.

Evaluate możemy również wykorzystać w pracy z obszarami nazwanymi. Oto przykłady zarówno w odniesieniu do wykonywania obliczeń jak i pracy z zaznaczeniem.

1[Obszar].Select
2A = [sum(Obszar)]
3Debug.Print A

W sieci znaleźć można szereg opinii na temat tego, że instrukcja Evaluate nie jest...doceniana. Chyba się z tym zgodzę i przyznam, że sam niezmiernie rzadko z niej korzystam. Nie sposób jednak nie wiedzieć o jej istnieniu. Z pewnością należy mieć też na uwadze, że wprowadzenie skróconego zapisu opartego o kwadratowe nawiasy może nie być czytelne dla szeregu początkujących programistów. Warto pokusić się o odpowiedni komentarz w naszym kodzie przy pierwszym wystąpieniu skróconego zapisu metody Evaluate.

Metoda Evaluate ma jeszcze jedno ciekawe i zaskakujący wykorzystanie. Ale o tym w osobnym- kolejnym- poście...