Budujemy własną wstążkę w programie Microsoft Word
19.11.2014 | aktual.: 20.11.2014 11:04
W poprzednich kilku wpisach zaprezentowałem przykłady zastosowania VBA w Wordzie i Excelu. Nie pokazywałem jednak w jaki sposób można wygodnie uruchamiać własne skrypty VBA. Standardową metodą uruchamiania makr jest otworzenie okna z listą dostępnych makr VBA (ALT+F8), a następnie wybranie makra i uruchomienie go przyciskiem. Nie jest to zbyt wygodna metoda, zwłaszcza jeśli z jakiegoś makra będziemy korzystać często. W dzisiejszym wpisie chciałbym Wam pokazać chyba najprostszą metodę przebudowy wstążki (RibbonX), dodawania do niej nowych elementów oraz tworzenia Własnych przycisków, którymi będziemy mogli uruchamiać własne makra. Temat jest dosyć obszerny, dlatego skupię się na podstawach, które powinny wystarczyć każdemu do prostej edycji wstążki. Zaprezentuję też dokumentację i materiały, które będą bardzo pomocne przy dalszej eksploracji tej techniki.
Wymagania wstępne
Przede wszystkim będziemy potrzebować edytora XML, który wykorzystamy do edycji pliku normal.dotm. Plik ten jest standardowym szablonem dokumentów, przechowującym nasze makra VBA. Służy on również do przechowywania wstążki. Standardowo jest zlokalizowany w: [code=java]"C:\Users\NazwaUżytkownika\AppData\Roaming\Microsoft\Szablony"[/code]
Dedykowany edytor Microsoftu (Custom UI Editor) można pobrać np. z tej strony. Testowałem go na wersjach 2007 i 2010, a pisząc ten wpis, sprawdzę czy jest kompatybilny także z wersją 2013. Poza samym edytorem potrzebna będzie nam dokumentacja, zawierająca nazwy poszczególnych elementów na wstążce. Każda wersja Office'a ma nieco inny zestaw elementów. Dokumentację do wersji 2013 znajdziecie na stronie Microsoftu tutaj. Na koniec równie ważny dokument, zawierający galerię ikonek wykorzystywanych w Wordzie. Możemy je przeglądać i zapoznać się z ich nazwami, poprzez otworzenie pliku, który można pobrać tutaj.
Na początek coś prostego
Uruchamiamy edytor XML i otwieramy w nim plik normal.dotm. Plik wydaje się być pusty. Z punktu widzenia tego edytora tak jest, ponieważ nie znajduje się w nim żadna niestandardowa konfiguracja wstążki. [img=CustomUIEditor] Na początek proponuję wkleić poniższy kod:
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> <tabs> <tab id="CustomTab" label="Custom Tab"> <group id="CustomGroup" label="Custom Group"> <button id="CustomButton" label="Custom Button" size="large" imageMso="HappyFace" onAction="test" /> </group> </tab> </tabs > </ribbon> </customUI>
Jest to nieco uproszczona wersja przykładu z MSDN Pomoże nam zrozumieć ogólne zasady budowania wstążki i przypisywania makr VBA do własnych elementów wstążki. W efekcie otrzymujemy własną kartę z przyciskiem.
Schemat pliku xml
Pierwszy tag (customUI) reprezentuje własny wygląd wstążki. Jest potrzebny do zdefiniowania przestrzeni nazw atrybutem xmlns. Dalej mamy tag <ribbon>, który obejmuje samą wstążkę. Wewnątrz tagu <ribbon> mamy tag <tabs> reprezentujący zbiór kart, a w nim wiele tagów <tab>, czyli poszczególne karty. Każda karta może składać się z wielu grup <group>. Wewnątrz grupy umieszczamy poszczególne elementy. Ten prosty przykład tworzy nową kartę o nazwie "Custom Tab". Po zapisaniu pliku i uruchomieniu Worda, nasza karta pojawi się na końcu wstążki, jako ostatnia. Będzie się w niej znajdowała jedna grupa. Atrybut label decyduje o napisie, jaki pojawi się na karcie. Label jest ogólnie używane do podpisywania wszystkich elementów. Mamy więc label też jako nazwę grupy, a także napis na przycisku.
Dodawanie ikonek
Atrybut imageMso określa ikonkę przycisku ze zbioru predefiniowanych ikonek. Wygląd i nazwę ikonki możemy sprawdzić w pliku, który podałem we wstępie. Po otwarciu dokumentu, należy w Wordzie 2013 przejść do menu plik i na dole wybrać "ImageMso 0".
Istnieje możliwość wstawiania własnych ikon. W tym celu musimy posiadać plik w formacie .png. Jeśli już mamy plik ikony, to w edytorze xml z menu "Insert" wybieramy "Icons" i wskazujemy pliki ikony. Następnie w miejscu atrybutu "imageMso" wpisujemy "image", a jako wartość (nazwę) podajemy nazwę widoczną przy ikonce z lewej strony edytora. W moim przypadku jest to "dobreprogramy" (ikonka dobrych programów).
Efekt zmiany widoczny na zrzucie ekranu poniżej:
Podpinanie makra VBA pod własny przycisk
Aby podpiąć nasz przycisk do makra, używamy atrybutu onAction, którego wartość musi być dokładną nazwą makra, które ma zostać uruchomione po wciśnięciu przycisku. Aby przetestować atrybut onAction, w Wordzie musimy napisać jakieś makro o nazwie podanej w pliku normal.dotm z poziomu edytora XML. W moim przypadku jest to "test".
Jest to najprostsze makro, jakie można wymyślić. Wyświetla okienko z komunikatem. Pokazuje ono jednak jeden bardzo ważną rzecz, o której trzeba pamiętać. Nasze makro (procedura) przyjmuje jako parametr jeden argument - obiekt klasy IRibbonControl. Dzięki temu nasz kod VBA dostaje dostęp i kontrolę nad wstążką. Poniżej efekt działania makra wywołanego przez wciśnięcie przycisku z ikonką DP.
Dalsze modyfikacje wstążki
W wielu przypadkach utworzenie własnej karty na wstążce z kilkoma przyciskami uruchamiającymi nasze makra w zupełności wystarczy. Jest to najprostsza rzecz jaką można zrobić ze wstążką. Jednakże apetyt rośnie w miarę jedzenia. Gdy oswoimy się nieco z pisanie makr, a nasza biblioteka skryptów zacznie rosnąć, możemy odczuwać potrzebę dalszych modyfikacji. Sięganie po przyciski na ostatniej karcie jest niewygodne. Może lepiej byłoby je przenieść na początek? A może w ogóle przebudować całą wstążkę od podstaw? To wariant dla twardzieli, bardzo czasochłonny, ale dający najlepsze dopasowanie do naszych potrzeb i nawyków.
Ukrywanie standardowych kart
Relatywnie łatwo możemy poukrywać niektóre karty. Co prawda to samo można jeszcze łatwiej uzyskać bezpośrednio z poziomu Worda w opcjach dostosowywania wstążki, ale warto zapoznać się również z tą techniką, ponieważ będzie ona przydatna przy bardziej złożonych manipulacjach. Starsze wersje Worda nie miały tak rozbudowanych narzędzi dostosowywania wstążki i dla nich edycja pliku edytorem XML była jedynym rozwiązaniem. Poza tym, o ile możliwe jest z poziomu opcji dostosowywania wstążki położenie przycisku uruchamiającego własne makro, to nie da się w ten sposób dodawać innych elementów niż przyciski, połączone z makrami, a ilość ikonek jakie można przypisać jest mocno ograniczona.
Powiedzmy, że chcemy wyłączyć pierwszą kartę narzędzi głównych. W pliku z wykazem komponentów musimy odszukać angielską nazwę tej wstążki. W kolumnie ControlType włączamy sobie filtrowanie tak, aby pozostawić tylko elementy typu "tab". Są one wypisane w kolejności zgodnej z kolejnością wyświetlania na wstążce, co ułatwia identyfikację. Same nazwy mogą być w niektórych przypadkach mylące. Przykładowo "Narzędzia główne" nazywają się "TabHome". Gdy już znamy nazwę karty, którą chcemy ukryć, w kodzie XML, wewnątrz tagu <tabs> wstawiamy:
<tab idMso="TabHome" visible="false" />
Atrybut visible określa czy karta ma być wyświetlana czy nie. Wartość false ukrywa kartę.
Nasza karta pierwsza na wstążce
Zróbmy teraz lekką wariację i zamiast ukrywać domyślne wstążki, przesuńmy naszą wstążkę na sam początek. W tym celu wykorzystamy atrybut insertBeforeMso. Nasz tag wstawiający naszą kartę powinien wyglądać następująco:
<tab id="CustomTab" label="Custom Tab" insertBeforeMso="TabHome">
Tab Home domyślnie jest pierwszą kartą, dlatego jeśli podamy ją jako wartość dla atrybutu insertBeforeMso, nasza karta wyświetli się jako pierwsza na liście.
Częściowo zmieniona wstążka standardowa
Stosunkowo łatwy iw miarę niezbyt czasochłonny sposób modyfikacji wstążki to ukrycie jednej lub dwóch grup ze standardowej karty i wstawienie w ich miejsce swojej grupy ze standardowymi przyciskami ułożonymi w pasujący nam sposób. Standardowe grupy może i nie są złe, aczkolwiek w codziennej pracy przeważnie wykorzystujemy tylko część umieszczonych przycisków. Jednocześnie zdarza nam się stosunkowo często korzystać z opcji ukrytych, pojawiających się we wstążkach kontekstowych, albo w ogóle niedostępnych z poziomu wstążki. W taki sytuacjach, miło jest położyć je w miejsce tych nieużywanych. Można też zastąpić standardowe przyciski, przyciskami, które zasadniczo robią to samo, ale w nieco inny sposób za pomocą naszych skryptów VBA. Przykładowo zamiast standardowego drukowania, możemy pod ikonkę drukowania, wstawić nasze makro, które drukuje dokumenty w jakiś zmieniony sposób (np. same parzyste strony). Możemy w ten sposób zmodyfikować działanie wielu domyślnych opcji.
Poniżej zamieszczam kod znaleziony na stronie Gregory K. Maxey'a, którą gorąco polecam! Była mi bardzo pomocna przy stawianiu pierwszych kroków z edycją wstążki.
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui"> <ribbon> <tabs> <tab idMso="TabHome"> <group idMso="GroupEditing" visible="false" /> <group idMso="GroupClipboard" visible="false" /> <group id="CustomGroup" label="Editing and Clipboard" insertBeforeMso="GroupFont"> <splitButton idMso="PasteMenu" size="large" /> <button idMso="Cut" /> <button idMso="Copy" /> <control idMso="FormatPainter" /> <separator id="Sep1" /> <button idMso="FindDialog" /> <button idMso="ReplaceDialog" /> <menu idMso="SelectMenu" /> <dialogBoxLauncher > <button idMso="ShowClipboard" /> </dialogBoxLauncher> </group> </tab> </tabs> </ribbon> </customUI>
Powyższy kod zmienia wygląd standardowej karty Home (Narzędzia główne). Pierwsze dwa wiersze wewnątrz karty ukrywają grupę schowka (pierwsza grupa) i edycji (ostatnia grupa). W ich miejsce tworzona jest nowa grupa o nazwie "Editing and Clipboard", która ma połączyć te dwie standardowe grupy w całość. Wstawiana jest ona przed grupą czcionki, a więc po schowaniu grupy schowka, przed pierwszą grupą.
SplitButton jest przyciskiem z rozwijaną listą opcji. Separator dzieli pionową kreską grupę na podgrupy. Menu to rozwijana lista podobna do splitButton'a, ale jej kliknięcie samo w sobie nie wywołuje żadnej akcji, poza rozwinięciem listy. DialogBoxLauncher to mały kwadracik w prawym dolnym rogu grupy, po kliknięciu którego pojawia się okno lub inny element z opcjami.
Zróbmy krok dalej
Manipulowanie gotowymi komponentami takimi, jak karty, grupy czy poszczególne kontrolki jest stosunkowo proste. Znacznie trudniej jest sterować działaniem własnych kontrolek. W pierwszej części wpisu pokazałem jak połączyć makro VBA z własnym przyciskiem. Teraz chciałbym wyjaśnić jak pracować z rozwijanymi listami w sposób dynamiczny, tzn. taki, że ostateczny kształt naszej niestandardowej kontrolki zależy od wyniku wykonywanego kodu.
Statyczna lista rozwijana
Jeśli nie czujemy się na siłach aby zapanować nad kodem sterującym listę w sposób dynamiczny, możemy umieścić na wstążce statyczną listę rozwijaną. Używając pojęcia "statyczna" mam na myśli listę, której elementy są na sztywno zdefiniowane w pliku normal.dotm i za każdym razem lista ta będzie na wstążce zawierać te same elementy. Listę taką tworzymy przy pomocy tagu <dropDown>.
<dropDown id="dropDown" label="DropDown" onAction="PokazInfo"> <item id="item1" label="Item 1" /> <item id="item2" label="Item 2" /> <item id="item3" label="Item 3" /> </dropDown>
Tag <item> to poszczególne elementy w rozwijanej liście. onAction reaguje na wybór elementu z listy. Pokażę teraz jak skonstruować makro, reagujące na takie zdarzenie.
Sub PokazInfo(ByVal control As IRibbonControl, selectedID As String, selectedIndex As Integer) Select Case selectedIndex Case 0 Makro1 Case 1 Makro2 Case 2 Makro3 End Select End Sub
Tym razem procedura przyjmuje aż 3 argumenty, choć w kodzie korzystamy tylko z jednego - selectedIndex. Wszystkie muszą być jednak wpisane, aby kontrolka działała prawidłowo. Konstrukcję Select Case omawiałem z jednym z poprzednich wpisów na temat VBA. Sprawdzamy tutaj, który element z rozwijanej listy został wybrany. Numeracja elementów zaczyna się od 0, więc pierwszy element ma wartość indeksu równą 0. Makro1, 2 i 3 to nazwy procedur VBA, które mają zostać uruchomione po wyborze danego elementu z rozwijanej listy. Możemy sobie zdefiniować np. kilka różnych sposobów wklejania zawartości schowka, np.: normalny, z pogrubioną czcionką, tekst pisany dużymi literami.
Dynamiczna lista rozwijana
Dynamiczna lista rozwijana daje dużo większe możliwości. Elementy listy mogą być przykładowo pobrane z bazy danych i wstawione do listy przy wczytywaniu wstążki. Niestety obsługa takiej listy jest odrobinę bardziej skomplikowana. Po pierwsze musimy zadbać o to by wraz z wczytywaniem wstążki były uruchamiane jakieś makro. Możemy je wykorzystać np. do pobrania danych z bazy, ale także do utworzenia instancji obiektu klasy IRibbonUI, który pozwoli na modyfikowanie wyglądu w trakcie pracy użytkownika. Po stronie pliku normal.dotm w tagu <customUI> musimy dodać atrybut onLoad, który przyjmuje nazwę makra, mającego się uruchamiać wraz z wczytywaniem się wstążki. Ja zazwyczaj nazywam je po prostu Onload. Po stronie kodu VBA tworzymy to makro:
Public myRibbon As IRibbonUI Sub Onload(ribbon As IRibbonUI) Set myRibbon = ribbon End Sub
Public służy do deklarowania zmiennych publicznych. Są to zmienne, które są dostępne wewnątrz całego modułu, a więc można się do nich odwoływać z każdej procedury wewnątrz modułu. Deklarujemy zmienną interfejsu wstążki, a następnie wewnątrz procedury Onload przypisujemy jej wstążkę przekazaną z pliku normal.dotm jako parametr procedury. Może się to wydawać trochę niejasne, ale generalnie chodzi o to, żeby wczytana wstążka była dostępna w kodzie VBA dla każdej procedury. Za chwilę wyjaśnię na konkretnym przykładzie jak można to wykorzystać. Teraz w pliku normal.dotm musimy stworzyć dropDown w inny sposób niż poprzednio.
<dropDown id="myDropDown" label="DropDown" onAction="PokazInfo" getItemCount="ItemCount" getItemLabel="ItemLabel" getSelectedItemIndex="ItemIndex"> </dropDown>
Jak łatwo się zorientować nie ma tutaj zdefiniowanych elementów listy. Są za to trzy inne tajemnicze atrybuty. Pozwalają one na przekazanie do procedur VBA takich parametrów dropDown'a jak ilość elementów listy, etykiety poszczególnych elementów oraz domyślnie wybrany element. Dzięki temu możemy sobie zbudować dropDowna z poziomu kodu VBA. Poniżej wkleję kod tych trzech procedur: "ItemCount", "ItemLabel", "ItemIndex". Ich nazwy są dowolne.
Sub ItemCount(ByVal control As IRibbonControl, ByRef count) count = 4 End Sub Sub ItemLabel(ByVal control As IRibbonControl, Index As Integer, ByRef label) label = Choose(Index + 1, "Wybierz element...", "Element1", "Element2", "Element3") End Sub Sub ItemIndex(ByVal control As IRibbonControl, ByRef Index) If (control.ID = "myDropDown") Then Index = 0 End If End Sub
Zmienna count w procedurze ItemCount przechowuje informację o ilości elementów rozwijanej listy. Domyślnie jest to wartość 0. Dlatego jeśli chcemy utworzyć jakieś elementy, musimy znać ich ilość i podać w tej procedurze przypisując tę wartość do zmiennej count. Ustawiam 4 elementy, ponieważ chcę, aby pierwszy element był poświęcony na etykietę typu "Wybierz odpowiednią wartość z listy".
Druga procedura może siać dezorientację. Spróbuję wyjaśnić w miarę łopatologicznie co się tutaj dzieje. Otóż procedura ta jest wywoływana tyle razy, ile elementów ma lista. Za każdym razem wartość zmiennej Index się zmienia, począwszy od 0, rosnąc o 1, zupełnie jakby procedura była wywoływana w pętli. Funkcja Choose zwraca element o zadanym indeksie z listy elementów, przy czym numeracja tej listy zaczyna się od 1, a nie od 0. Ot tak, dla zmylenia przeciwnika. Dlatego do zmiennej Index dodajemy 1, aby zniwelować tę różnicę. Kolejne argumenty funkcji Choose to etykiety naszych elementów rozwijanej listy, gdzie pierwszy, jak już wspomniałem, jest tylko informacją dla użytkownika, a nie wartością do wybrania przez niego. Tak wybraną wartość z listy elementów przypisujemy do zmiennej label, która przechowuje etykietę elementu listy rozwijanej.
Ostatnia procedura ustawia wartość zmiennej Index, która wskazuje na element, który ma być wybrany z rozwijanej listy w momencie wczytania się wstążki. Domyślnie jest to 0, więc w naszym przypadku użycie tej procedury jest zbędne, aczkolwiek pokazuję ją, bo czasem może się zdarzyć, że domyślnie chcemy wyświetlić inny element listy jako widoczny. Pozostaje nam nieco zmodyfikowana wersja procedury, która wywołuje inne procedury w zależności od indeksu wybranego elementu rozwijanej listy.
Sub PokazInfo(ByVal control As IRibbonControl, selectedID As String, selectedIndex As Integer) Select Case selectedIndex Case 0 'Nic nie robimy bo zerowy element jest tylko etykietą Case 1 Makro1 Case 2 Makro2 Case 3 Makro3 End Select myRibbon.InvalidateControl control.ID End Sub
Ostatnia linijka w procedurze powoduje "zaktualizowanie" wyglądu wstążki, a konkretnie to wskazanego elementu wstążki. Element ten wskazujemy przez id kontrolki. Odświeżenie sprawi, że indeks ponownie zostanie ustawiony na 0. Technikę tę możemy wykorzystywać do aktualizowania stanu każdej własnej kontrolki. W ten sposób możemy umieszczać na wstążce komunikaty jako reakcję na zachowanie użytkownika. Z własnego doświadczenia wiem, że odświeżanie stanu wstążki czasem może powodować wywalanie się kodu VBA, ale generalnie działa to dobrze.
Kod XML i VBA
Na koniec zamieszczam cały kod XML i VBA, gdyby ktoś chciał sobie potestować bez czytania dokładnie całości wpisu.
XML
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="Onload"> <ribbon> <tabs> <tab id="CustomTab" label="Makra VBA"> <group id="CustGrp" label="Imieniny" > <dropDown id="myDropDown" label="DropDown" onAction="Items" getItemCount="ItemCount" getItemLabel="ItemLabel" getSelectedItemIndex="ItemIndex"> </dropDown> </group> </tab> </tabs> </ribbon> </customUI>
VBA
Option Explicit Public myRibbon As IRibbonUI Sub Onload(ribbon As IRibbonUI) Set myRibbon = ribbon End Sub Sub Items(ByVal control As IRibbonControl, selectedID As String, selectedIndex As Integer) Select Case selectedIndex Case 0 'nic nie robimy Case 1 Makro1 Case 2 Makro2 Case 3 Makro3 End Select myRibbon.InvalidateControl control.ID End Sub Sub ItemCount(ByVal control As IRibbonControl, ByRef count) Debug.Print count count = 4 End Sub Sub ItemLabel(ByVal control As IRibbonControl, Index As Integer, ByRef label) label = Choose(Index + 1, "Wybierz element...", "Element 1", "Element 2", "Element 3") End Sub Sub ItemIndex(ByVal control As IRibbonControl, ByRef Index) Debug.Print Index If (control.ID = "myDropDown") Then Index = 0 End If End Sub Sub Makro1() MsgBox "Uruchomiono Makro1" End Sub Sub Makro2() MsgBox "Uruchomiono Makro2" End Sub Sub Makro3() MsgBox "Uruchomiono Makro3" End Sub
Podsumowanie
Modyfikowanie wstążki daje ogromne możliwości. Z jednej strony możemy poprawić układ istniejących kontrolek, dostosowując wstążkę pod własne preferencje i przyzwyczajenia, a także specyfikę pracy z Wordem. Z drugiej możemy wyposażyć wstążkę we własne narzędzia, których funkcjonalność zależy tylko od naszych umiejętności programowania w VBA oraz pomysłowości. Nawet taką prostą rozwijaną listę możemy podpiąć pod skrypty, które wykonują skomplikowane zadania, łącząc się z bazą danych. Możemy przykładowo wyświetlić w takiej liście imiona pracowników, którzy po rozpoczęciu pracy z dokumentem, wybierają z listy swoje imię, co skutkuje zapisanie w firmowej bazie danych informacji o edytowanym dokumencie i czasie rozpoczęcia pracy. Aby zabezpieczyć się przed przypadkowymi i niepowoływanymi zapisami do bazy, możemy stworzyć prosty formularz, w którym użytkownik podaje swoje hasło, a skrypt dodatkowo przed wysłaniem danych sprawdza MAC adres karty sieciowej.
Niniejszy wpis obejmuje jedynie wąski zakres możliwości modyfikacji wstążki, celem uświadomienia jak wiele można osiągnąć przy pomocy VBA oraz jak bardzo można zmienić zarówno wygląd jak i sposób pracy z Wordem.