Spis treści:

Kategoria:C#Word


Tworzenie dokumentu Word w C# - I

Wstęp i wymagania

Dzisiejszy artykuł rozpoczyna pewną serię wpisów związanych z obsługą dokumentów Word, widzianą od strony programistycznej. Teoretyczne rozważania zostawię na później. Na ten moment wspomnę jedynie, że powszechnie stosuje się dwie metody pracy z dokumentami Word: poprzez model COM i w postaci plików XML. Który z nich jest lepszy? O tym też napiszę już po zademonstrowaniu kilku przykładów.

Jak postanowiłem, tak zrobię. Aby uruchomić poniższy przykład trzeba mieć, co chyba nie powinno dziwić, zainstalowaną aplikację Word. We wszystkich zaprezentowanych przykładach zakładam, że jest to wersja 12 lub wyższa. Wersja 12 to Word z pakietu Office 2008.

W większości przykładów będę korzystał ze zwykłej, prostej aplikacji konsolowej. Wszystkie techniki można z powodzeniem stosować również w aplikacjach okienkowych. Trzeba jednak pamiętać o tym, że wywołania funkcji Word blokują wykonywanie się wątku (wątku głównego lub tego, w którym wywołujemy operacje) w momencie przetwarzania. Problemy związane ze współbieżnością opiszę innym razem. Najpierw słynne Hello World!

Przykładowy dokument Word w C#

Aby utworzyć prosty dokument Word w C# należy skorzystać z funkcji COM udostępnianych przez obiekt Word.Application. Znowu napiszę, w co nie będę się, póki co, zagłębiał - w zawiłości modelu COM. Na tym etapie napiszę, że do projektu Visual Studio należy dodać referencję do biblioteki .NET Microsoft.Office.Interop.Word (prawy przycisk na References, Add reference..., zakładka .NET, Microsoft.Office.Interop.Word w wersji 12.0.0.0).

Tyle w kwestii przygotowań. Korzystając z dobrodziejstw Word nie wykonujemy żadnych specjalnych czynności. Dostajemy klasy i interfejsy, dzięki którym komunikujemy się z aplikacją (tak prawdę mówiąc to nie my tworzymy dokument Word - dokument tworzony jest przez proces WINWORD.exe - my tylko instruujemy ją, wywołując metody/polecenia). Takie podejście ma swoje konsekwencje. Po pierwsze WINWORD.exe to oddzielny proces, więc bardzo łatwo doprowadzić do sytuacji, w której nawet po zamknięciu naszej aplikacji coś w pamięci zostaje. Coś, czyli wspomniany WINWORD.exe. Dopóki sami nie nakażemu mu zakończyć pracy, proces WINWORD tego nie zrobi. Po drugie - każde wywołanie funkcji to pociąga za sobą konieczność komunikacji międzyprocesowej, połączony z konwersją typów .NET na typy Variant modelu COM. Jak się wkrótce przekonamy, dość mocno cierpi na tym wydajność. Po trzecie... więcej szczegółów innym razem.

Po dodaniu referencji Microsoft.Office.Interop.Word w wersji 12.0.0.0. można już przystąpić do pisania. Przykład kodu, który pozwala stworzyć prosty dokument Word, pokazany jest na poniższym listingu:

using System;
//Upraszczamy i przezywamy przestrzeń, aby łatwiej
//zidentyfikować klasy Word
using Word = Microsoft.Office.Interop.Word;

class Program
{
    static void Main(string[] args)
    {
        object missing = Type.Missing;
        Word._Application wordApplication = new Word.Application();
        Word._Document wordDocument = wordApplication.Documents.Add(
            ref missing, ref missing, ref missing, ref missing);

        wordApplication.Visible = true;

        Console.WriteLine("Press Enter...");
        Console.ReadLine();

        wordDocument.Close(ref missing, ref missing, ref missing);
        wordApplication.Quit(ref missing, ref missing, ref missing);
    }
}

Pokazany kod jest pewnego rodzaju schematem: tworzymy obiekt aplikacji (_Application), a następnie dodajemy do niej obiekty dokumentów (_Document). To dokument zawiera metody i interfejsy służące do wstawiania tekstu, tabel oraz operacji związanych ze stylem i formatowaniem. Schemat połączenia pokazany jest na poniższej ilustracji:

Struktura głównych obiektów Word
Rys. 1. Struktura głównych obiektów Word.

W pokazanym przykładzie tworzony jest jeden dokument, ale nic nie stoi na przeszkodzie, aby stworzyć ich więcej. Trzeba wiedzieć, że większość funkcji Word nie jest typowana - argumenty przekazywane są w postaci typu object. Trochę to utrudnia pracę, ale trzeba się do tego przyzwyczaić. Co więcej, parametry przekazywane są przez referencję! Przypomnę, że zgodnie z zaleceniami i przeznaczeniem słowo kluczowe ref stosuje się wtedy, gdy metoda może wewnątrz zmieniać wartość przekazywanej zmiennej. To też rodzaj ostrzeżenia przed taką możliwością. Tutaj nic takiego się nie dzieje, więc o zmienne można być spokojnym. Niedogodności związane z brakiem typowania można zniwelować pisząc swoje klasy, które wewnątrz realizują wymóg "referencyjności obiektów". Własne wersje metod przydadzą się również wtedy, gdy te, których będziemy potrzebować, mają kilka-kilkanaście parametrów, w większości niewykorzystywanych. Pokażę to w kolejnych częściach serii wpisów z zakresu Word.

Aplikacja zaprezentowana na listingu powinna otworzyć dokument Word i wyświetlić na konsoli komunikat "Press Enter...". Po wciśnięciu klawisza Enter dokument zostanie zamknięty.

Uwaga! To pułapka!

Wspomniałem już trochę o konsekwencjach tego, że WINWORD to oodzielny proces. Na tym etapie trzeba sobie zdać też sprawę z kilku pułapek, o których napiszę kilka zdań.

Tryb DEBUG i zabijanie procesu

Scenariusz tej pułapki wygląda następująco: uruchamiamy aplikację, czekamy na pokazanie się dokumentu Word, a następnie przerywamy proces debugowania (nie naciskamy Enter w oknie konsoli!). Co się stało? Dokument Word nie został zamknięty. Visual Studio zamknął tylko proces z oknem konsoli. Dokumentu Word nie zamknął, bo skąd miałby niby wiedzieć o jego istnieniu? WINWORD to obcy proces, który czeka na nasze instrukcje. Siłowe zamknięcie procesu sterującego (naszej aplikacji konsolowej) sprawi, że nie będzie miał kto nakazać procesowi WINWORD, żeby ten się zamknął. Będziemy go musieli zamknąć ręcznie.

WINWORD jako proces niewidoczny

Dość powszechnie stosowaną techniką jest takie sterowanie wydrukiem Word, aby ten pojawił się dopiero po wszystkich operacjach. Aby zaprezentować ten problem, wystarczy z zaprezentowanego kodu usunąć linię ustawiającą właściwość Visible obiektu wordApplication. Domyślnie aplikacja Word jest niewidoczna! Teraz, gdy na konsoli pojawi się komunikat "Press Enter...", wystarczy uruchomić Menadżera zadań i poszukać procesu WINWORD.exe. On tam jest. Teraz, gdy nie zamkniemy obiektu wordApplication, proces zostanie w pamięci. Jest to o tyle groźne, że przypadki takich, nazwijmy to, wycieków, mogą długo pozostać niezauważone. Pamiętajmy, że w trakcie wykonywania kodu tworzącego wydruk mogą pojawiać się wyjątki, które przenoszą sterowanie w miejsce, gdzie znajduje się najbliższy, właściwy dla danego wyjątku blok catch. Jeżeli skok spowoduje przeskoczenie instrukcji, które pokazują dokument Word lub zamykają proces WINWORD.exe, dojdzie do wspomnianych przecieków. Zdarza się też, że niewłaściwie umieszczone instrukcje powodujące przedwczesne opuszczenie metody generującej wydruk (return) także pominie kod sprzątający. Dlaczego o tym wspominam? Bo nawet jeden tego rodzaju błąd potrafi bardzo namieszać.

Podczas pracy z modelem obiektowym Word warto raz na jakiś czas otworzyć Menadżera zadań i sprawdzić, czy gdzieś tam w tle nie działają zapomniane już procesy WINWORD.exe.

Podsumowanie

Zaprezentowany przykład pokazuje, że możliwa jest współpraca aplikacji napisanej w języku C# z aplikacją Word. Współpraca ta nie jest łatwa, bo i Word łatwy nie jest. To, że Word ma mnóstwo opcji, funkcji i sposobów formatowania sprawia, że obiektowy model i interfejsy COM dla tych operacji są złożone i skomplikowane. Mam nadzieję, że kolejne wpisy nieco ten trudny stan rzeczy rozjaśnią.

Kategoria:C#Word

, 2013-12-20

Brak komentarzy - bądź pierwszy

Dodaj komentarz
Wyślij
Ostatnie komentarze
Dzieki za te informacje. były kluczowe
Dobrze wyjaśnione, dzięki !
a z innej strony - co gdybym ciąg znaków chciał mieć rozbity nie na wiersze a na kolumny? Czyli ciąg ABCD: 1. kolumna: A, 2. kolumna: B, 3. kolumna: C, 4 kolumna: D?
Ciekawy artykuł.
Czy można za pomocą EF wysłać swoje zapytanie?
Czy lepiej do tego użyć ADO.net i DataTable?