Blog (31)
Komentarze (1k)
Recenzje (4)
@kaisuj Automatyzacja pracy z Wordem — import danych adresowych z Excela

Automatyzacja pracy z Wordem — import danych adresowych z Excela

W dzisiejszym wpisie chciałbym pokazać w jaki sposób można pobierać i wstawiać do Worda dane zewnętrzne. Niniejszy przykład jest z pewnością zaledwie jednym z wielu możliwych sposobów na zrealizowanie takiego zdania. Ma on swoje ograniczenia (dobry do stosunkowo niewielkiej ilości danych). Zaznaczam również, że nie należy traktować moich wpisów na zasadzie sztywnej instrukcji obsługi VBA. Pragnę jedynie zademonstrować szerokie spektrum zastosowań tego niedocenianego składnika Office'a, a przy okazji podpowiedzieć jak się można zabrać za wykonanie pewnych rzeczy. Na każdym etapie podawanego przeze mnie rozwiązania, można zastosować jakiś inny sposób i dostosować całość do swoich potrzeb.

Przygotowanie dokumentu do importu danych

Na początek przygotuję w dokumencie Worda miejsce, do którego będę wstawiać dane. W tym celu, w miejscu gdzie mają się znajdować dane adresata, z zakładki Deweloper wstawiam formant zawartości. Ja użyłem list rozwijanych. Aby edytować poszczególne listy trzeba włączyć tryb projektowania na wstążce a następnie stojąc na wybranej liście nacisnąć ze wstążki właściwości. W zależności do potrzeb i typu importowanych danych można nadać tytuły poszczególnym listom. Ja swoje nazwałem: adresat, ulica, miejscowość. W dalszej części pokażę w jaki sposób manipulować zawartością tych pól z poziomu VBA,

Listy wyboru wstawione w miejscu danych adresata
Listy wyboru wstawione w miejscu danych adresata

Przygotowanie danych w Excelu

Po stronie Excela wprowadzam dane kilku przykładowych adresatów. Dla uproszczenia kodu VBA, każdą cząstkę danych, która ma zostać wstawiona do danej listy rozwijanej Worda, wpisuję w oddzielną kolumnę w Excelu. Zapisuję tak przygotowany skoroszyt.

Baza adresowa w skoroszycie Excela
Baza adresowa w skoroszycie Excela

Uzyskanie dostępu do danych ze skoroszytu

Zanim zaczniemy cokolwiek pisać w edytorze VBA musimy uzupełnić brakujące zależności. W tym celu z okna Tools edytora VBA wybieramy "References..." i tam zaznaczamy Microsoft Excel Library. Bez tej biblioteki nie będziemy w stanie kontrolować aplikacji Excela.

Wskazanie biblioteki za pomocą której można sterować działaniem Excela
Wskazanie biblioteki za pomocą której można sterować działaniem Excela

Teraz nasz model obiektowy wzbogacił się o klasy reprezentujące Excela i jego elementy. W kodzie zaczniemy od zdefiniowania tych elementów. Będzie to sama aplikacja Excela oraz skoroszyt i arkusz. Warto też zapisać sobie do zmiennej ścieżkę do skoroszytu z adresami.

Dim xlApp As New Excel.Application
Dim wb As Excel.Workbook
Dim sh1 As Excel.Worksheet
Dim wbPath As String

Set xlApp = CreateObject("Excel.Application")
xlApp.Visible = False
wbPath = "C:\Users\MojaFirma\Desktop\adresy.xlsx"
Set wb = xlApp.Workbooks.Open(wbPath)
Set sh1 = wb.Sheets(1)

Aplikacja Excela jest ustawiona jako niewidoczna. Oznacza to, że jeśli makro VBA będzie otwierać skoroszyt Excela, to nie zobaczymy tego (co najwyżej w procesach). Dzięki temu całość wykona się wielokrotnie szybciej. Zbudowanie interfejsu graficznego zajmuje masę czasu w porównaniu z samym otworzeniem dokumentu i odczytaniem z niego danych. Używany arkusz zdefiniowałem odwołując się do numeracji arkuszy w skoroszycie, ale równie dobrze można wskazywać arkusze za pomocą ich nazw.

Przygotowanie do iteracji na wierszach

Tworzymy kilka zmiennych pomocniczych, którymi posłużymy się przy odczycie danych ze skoroszytu:

LastRow - będzie przechowywać ilość niepustych wierszy w arkuszu - dzięki temu będziemy mieli pewność, że odczytujemy tylko te wiersze, w których są zapisane dane;

i - zmienna sterująca pętlą, której zadaniem będzie przeskakiwanie po wierszach arkusza;

Adresaci - tablica zawierające odczytane dane z kolumny adresatów;

Ulice - analogicznie tablica z odczytanymi ulicami;

Miejscowosci - odczytane miejscowosci;

Aby ustalić ilość niepustych wierszy użyjemy następującego kodu:

Dim LastRow As Long
Dim i As Integer
Dim Adresaci() As String
Dim Ulice() As String
Dim Miejscowosci() As String

LastRow = Range("A" & Rows.count).End(xlUp).Row
If (LastRow = 1) Then
   xlApp.Quit
   Set wb = Nothing
   Set xlApp = Nothing
   Exit Sub
End If

Przy okazji sprawdzamy ile jest tych wierszy. Jeśli tylko jeden, to oznacza, że poza wierszem nagłówka nie ma żadnych danych i dalsze wykonywanie instrukcji nie ma sensu. Zamykamy Excela, czyścimy zmienne, aby nie zajmowały niepotrzebnie pamięci i przerywamy dalsze działanie makra.

Określenie rozmiaru tablic dynamicznych

Tablice, w których będziemy zapisywać odczytane dane zostały zadeklarowane jako tablice dynamiczne. Oznacza to, że nie podaliśmy ich rozmiaru (ilości przechowywanych elementów). W VBA istnieje możliwość zmiany wielkości tablicy w trakcie wykonywania kodu. Ponieważ obecnie wiemy, ile wierszy odczytamy, możemy ustalić rozmiar naszych tablic. Jest to konieczne przed rozpoczęciem iteracji, ponieważ nie ma możliwości odnoszenia się do tablic, które nie mają zdefiniowanego rozmiaru.

ReDim Adresaci(1 To LastRow - 1) As String
ReDim Ulice(1 To LastRow - 1) As String
ReDim Miejscowosci(1 To LastRow - 1) As String

Rozmiar tablic jest mniejsza o 1 od ilości zapisanych wierszy, ponieważ pomijamy pierwszy wiersz, będący nagłówkiem. Oczywiście jeśli ktoś nie chce używać nagłówka, możemy nie odejmować tego jednego elementu.

Iteracja i odczyt danych

Naszą pętle ustawiamy tak, aby pierwszy odczytywany wiersz miał indeks = 2. Pomijamy w ten sposób pierwszy wiersz, będący nagłówkiem.

For i = 2 To LastRow
   Adresaci(i - 1) = sh1.Cells(i, 1).Value
   Ulice(i - 1) = sh1.Cells(i, 2).Value
   Miejscowosci(i - 1) = sh1.Cells(i, 3).Value
Next i

Obiekt Cells reprezentuje komórki arkusza. Tak naprawdę jest to dwuwymiarowa tablica komórek arkusza (tablica, której elementami są jednowymiarowe tablice takie, jak np. Adresaci). Aby wskazać konkretną komórkę skoroszytu podajemy kolejno numer wiersza i numer kolumny. W miejsce numeru wiersza wstawiamy zmienną sterującą pętli "i", która dla każdego przebiegu pętli ma wartość zwiększaną o 1, począwszy od 2, a skończywszy na wartości równej ilości niepustych wierszy w arkuszu.

Jeśli chcemy sprawdzić działanie naszej pętli, zanim przejdziemy do wstawiania danych do dokumentu Worda, możemy dopisać do pętli instrukcję wyświetlenia zawartości naszych tablic do immediate window (włączanego w edytorze VBA skrótem ALT+G).

Debug.Print (Adresaci(i - 1))
Debug.Print (Ulice(i - 1))
Debug.Print (Miejscowosci(i - 1))

Gdy już mamy wszystkie dane, możemy zamknąć Excela tym samym kodem, którego używaliśmy wcześniej. Przechodzimy teraz do sterowania listami rozwijanymi w Wordzie.

Przypisanie list rozwijanych do zmiennych VBA

Aby zapełnić danymi poszczególne listy, najpierw trzeba je sobie zdefiniować w kodzie VBA, aby zyskać do nich dostęp i kontrolę. Wszystkie formanty zawartości są w VBA reprezentowane przez obiekty klasy ContentControl. Zatem:

Dim PoleAdresata As ContentControl
Dim PoleUlicy As ContentControl
Dim PoleMiejscowosci As ContentControl

Jeśli nasz dokument zawiera tylko te 3 listy, to możemy się do nich dostać po indeksach (są one kolejno numerowane):

PoleAdresata = ActiveDocument.ContentControls.Item(1)
PoleUlicy = ActiveDocument.ContentControls.Item(2)
PoleMiejscowosci = ActiveDocument.ContentControls.Item(3)

Jeśli jednak korzystamy w dokumencie z innych elementów tego typu, to lepiej nie ryzykować pomieszania numeracji i zidentyfikować nasze listy po tytule, który wcześniej nadaliśmy. W tym celu wykonamy pętlę po wszystkich elementach typu ContentControl i dla każdego z osobna sprawdzimy jego tytuł. Jeśli natrafimy na element o pasującym tytule, przypiszemy go do naszej zmiennej. Jest to bardziej rozbudowana metoda, ale zyskujemy pewność, że nasze zmienne odnoszą się do właściwych elementów w dokumencie. Aby skonstruować pętlę for, musimy ustalić ilość elementów ContentControl w dokumencie. Najwygodniej jest zapisać tę informację do zmiennej.

Dim IlePol As Integer
IlePol = ActiveDocument.ContentControls.count

Teraz sprawdzamy wszystkie formanty:

Dim Tytul As String
For i = 1 To IlePol
   Tytul = ActiveDocument.ContentControls.Item(i).Title
   Select Case Tytul
      Case "Adresat"
         Set PoleAdresata = ActiveDocument.ContentControls.Item(i)
      Case "Ulica"
         Set PoleUlicy = ActiveDocument.ContentControls.Item(i)
      Case "Miejscowość"
         Set PoleMiejscowosci = ActiveDocument.ContentControls.Item(i)
   End Select
Next i

W każdym kroku pętli zmiennej tytuł przypisujemy tytuł kolejnego sprawdzanego elementu typu ContentControl. Następnie korzystamy z konstrukcji Select Case. Jest to odpowiednik instrukcji warunkowej If Then Else, która jest łatwiejsza w stosowaniu, jeśli mamy do czynienia z wieloma przypadkami do rozpatrzenia. Odpowiednikiem warunku, który sprawdzamy w konstrukcji If, jest to, co stoi po Select Case. W naszym przypadku jest to Tytul. Każdy Case poniżej wskazuje na możliwą wartość zmiennej Tytul, jaką może ona przyjąć oraz instrukcję jaka ma być wykonana, jeśli sprawdzana wartość zmiennej tytuł będzie się zgadzać z tą podaną w danym Case. Jeśli więc napotkamy na element ContentControl o tytule "Adresat" to wykonana zostanie instrukcja przypisania tego elementu do zmiennej Adresat. W ten sposób możemy wyłapać wszystkie nasze elementy po tytule, bez względu na to jaki mają swój "numerek" w zbiorze formantów zawartości (ContentControls).

"Zaludnianie" list rozwijanych

Wypełnianie danymi różnych elementów, np. listy rozwijanej jest zwane w VBA zaludnianiem. Warto znać to określenie, ponieważ są w VBA polecenia, które dosłownie tak brzmią. Mając już zmienne przechowujące odpowiednie listy możemy stworzyć pętlę, która wypełni nam te listy danymi. Pozostaje nam jeszcze tylko upewnić się, że listy nie przechowują jakichś starych danych. W tym celu należy je wyczyścić.

PoleAdresata.DropdownListEntries.Clear
PoleUlicy.DropdownListEntries.Clear
PoleMiejscowosci.DropdownListEntries.Clear

Wyczyszczone listy są gotowe do zapełnienia.

For i = 1 To UBound(Adresaci)
   PoleAdresata.DropdownListEntries.Add Adresaci(i)
   PoleUlicy.DropdownListEntries.Add Ulice(i)
   PoleMiejscowosci.DropdownListEntries.Add Miejscowosci(i)
Next i

Funkcja Ubound (Upper Bound) zwraca nam ilość elementów tablicy. Jako parametr przyjmuje tablicę, której ilość elementów ma policzyć. Dzięki temu możemy ustawić odpowiednią (zgodną z ilością odczytanych wierszy w Excelu) ilość kroków pętli.

Optymalizowanie kodu

W tym przykładzie celowo użyłem zmiennych, które są tak naprawdę zbędne, a wiele operacji można było wykonać w jednej pętli zamiast w kilku. Zrobiłem tak, aby łatwiej można było zrozumieć co się dzieje w poszczególnych miejscach i nie robić zbyt wiele na raz. Zapełnianie list można zrobić od razu w pętli odczytu danych z Excela bez konieczności tworzenia tablic, w których te dane są przechowywane. Poniżej zamieszczam cały kod makra, w skróconej wersji.

Sub ImportExcel()
'-----ZMIENNE DO EXCELA------------
Dim xlApp As New Excel.Application
Dim wb As Excel.Workbook
Dim sh1 As Excel.Worksheet
Dim wbPath As String
Dim LastRow As Long
Dim i As Integer
'-----ZMIENNE DO WORDA------------
Dim PoleAdresata As ContentControl
Dim PoleUlicy As ContentControl
Dim PoleMiejscowosci As ContentControl
Dim IlePol As Integer
Dim Tytul As String

'-----PRZYGOTOWANIE LIST DO ZAPELNIENIA----------------------

IlePol = ActiveDocument.ContentControls.count

For i = 1 To IlePol
   Tytul = ActiveDocument.ContentControls.Item(i).Title
   Select Case Tytul
      Case "Adresat"
         Set PoleAdresata = ActiveDocument.ContentControls.Item(i)
      Case "Ulica"
         Set PoleUlicy = ActiveDocument.ContentControls.Item(i)
      Case "Miejscowość"
         Set PoleMiejscowosci = ActiveDocument.ContentControls.Item(i)
   End Select
Next i

PoleAdresata.DropdownListEntries.Clear
PoleUlicy.DropdownListEntries.Clear
PoleMiejscowosci.DropdownListEntries.Clear

'-----OTWORZENIE SKOROSZYTU I SPRAWDZENIE ILOSCI WIERSZY---------

Set xlApp = CreateObject("Excel.Application")
wbPath = "C:\Users\Jarosław\Desktop\adresy.xlsx"
Set wb = xlApp.Workbooks.Open(wbPath)
xlApp.Visible = False
Set sh1 = wb.Sheets(1)

LastRow = Range("A" & Rows.count).End(xlUp).Row

If (LastRow = 1) Then
   xlApp.Quit
   Set wb = Nothing
   Set xlApp = Nothing
   Exit Sub
End If

'-----ODCZYT DANYCH I ZAPIS DO LIST W WORDZIE-------------

ReDim Adresaci(1 To LastRow - 1) As String
ReDim Ulice(1 To LastRow - 1) As String
ReDim Miejscowosci(1 To LastRow - 1) As String

For i = 2 To LastRow
   PoleAdresata.DropdownListEntries.Add sh1.Cells(i, 1).Value
   PoleUlicy.DropdownListEntries.Add sh1.Cells(i, 2).Value
   PoleMiejscowosci.DropdownListEntries.Add sh1.Cells(i, 3).Value
Next i

xlApp.Quit
Set wb = Nothing
Set xlApp = Nothing
   
End Sub

Jak widać na zrzucie ekranu poniżej, w treści dokumentu nie wyświetlają się zaimportowane dane. Są one widoczne dopiero po rozwinięciu listy. Dzieje się tak dlatego, że domyślnie ustawiony jest indeks 0, a więc żaden element listy nie jest wybrany.

Lista wyboru z zaimportowanymi danymi z Excela
Lista wyboru z zaimportowanymi danymi z Excela

Można to zmienić i ustawić np. pierwszy element w następujący sposób:

PoleAdresata.DropdownListEntries.Item(1).Select

Podsumowanie

W niniejszym wpisie pokazałem na przykładzie jak można pobierać dane z Excela i wstawiać je do Worda. Każdy może poeksperymentować i zmodyfikować podane przeze mnie rozwiązanie. Nie ma konieczności stosowania rozwijanych list. Są one jednak wygodne, ponieważ w łatwy sposób możemy zdefiniować je w kodzie VBA i wstawić dane dokładnie w to miejsce w tekście, które nas interesuje. Na wydruku listy rozwijane z wybranymi wartościami wyglądają jak zwykły tekst.

Podgląd wydruku dokumentu z rozwijanymi listami
Podgląd wydruku dokumentu z rozwijanymi listami

Bazę adresów w Excelu możemy na bieżąco aktualizować. Jeśli nasze makro wstawimy do makra AutoOpen lub Document_Open to nasze listy będą się aktualizować przy każdym otworzeniu dokumentu. Adresy możemy posortować alfabetycznie. Najprościej w samym Excelu, po dodaniu nowych wpisów, choć można to zrobić także z poziomu VBA.

Wybrane dla Ciebie
Komentarze (13)