Rozdział 12
Raporty
W rozdziale tym będzie kontynuowana faza konstrukcyjna projektu RENTMAN
(praca nad systemem raportów). Zaprojektujemy trzy sprawozdania i połączymy je
z aplikacją. Będą to konkretnie:
Raport w postaci formularzy do drukowania zleceń prac.
Raport kolumnowy, prezentujący listę nieruchomości z tabeli PROPERTY.
Raport kolumnowy, zawierający listy z zestawieniami zadań dla pracowników.
Metody konstruowania raportów przy pomocy Delphi
Delphi udostępnia trzy podstawowe metody budowy raportów: konstruowanie
formularzy nadających się do drukowania, tworzenie sprawozdań przy pomocy
komponentu
QuickReport
oraz pisanie oprogramowania w języku Object
Pascal-a, którego efektem będzie powstanie odpowiedniego raportu.
W omówionym przypadku wykorzystamy wszystkie trzy sposoby.
UWAGA
Na liście narzędzi do tworzenia raportów brak czwartej kategorii, a mianowicie
komercyjnych kreatorów raportów typu Crystal Reports, R&R czy ReportSmith.
Przyczyną jest ich nieobecność w pakiecie Delphi. Borland postanowił wyposażyć
Delphi we własne narzędzia do tworzenia raportów. Uważa się, że najnowszy
QuickReports jest porównywalny z
programem ReportSmith, jeśli chodzi
o prostotę użycia, natomiast pod względem mocy produkowane raporty są szybsze
i silniejsze.
Rodzaje raportów
Sprawozdania tworzone dla potrzeb różnych organizacji można podzielić na cztery
typy podstawowe: formularze, etykiety, raporty kolumnowe i
zestawienia
krzyżowe.
386
Część II
Sprawozdania w postaci formularza drukują tylko jeden rekord na stronie (lub
jeden rekord główny i korespondujące z nim rekordy szczegółowe). Przykładem
tego typu jest faktura - na jednej stronie drukowany jest tylko jeden rachunek.
Raport kolumnowy jest tradycyjnym sprawozdaniem. Zawiera listę danych w serii
równoległych kolumn. Zazwyczaj na jednej stronie znajduje się wiele rekordów.
Ten typ zestawień może grupować dane oraz sumować zawartości kolumn.
Przykładem takiego raportu jest lista pracowników przedsiębiorstwa.
Trzeci typ raportów to wydruki etykiet, które są przeznaczone do masowego
adresowania lub podobnych celów. Użytkownik określa rodzaj etykiety i ich liczbę
na stronie. Zazwyczaj etykiety są drukowane na specjalnym papierze,
umożliwiającym łatwe przylepianie. Dobrym przykładem są wydruki tabeli
CUSTOMER dostarczające klientom rozliczenia w postaci wyciągów. Próbkę
etykiet pocztowych, powstałych przy pomocy narzędzia QuickReports, można
znaleźć w składnicy obiektów Delphi. Aby się do nich dostać, wystarczy wybrać
opcję
File\New\Forms
.
Zestawienia krzyżowe obrazują dane w sposób wykorzystywany w arkuszach
kalkulacyjnych. Różnica między tym typem sprawozdania a
raportem
kolumnowym jest istotna. Przykładem może być zestawienie sprzedaży
poszczególnych oddziałów przedsiębiorstwa w rozbiciu na kolejne miesiące.
Rubryki pionowe przedstawiają sprzedaż wszystkich oddziałów, a
wiersze
dokumentują wyniki kolejnych miesięcy. Informacje o osiągnięciach konkretnego
oddziału w określonym miesiącu znajdujemy na przecięciu odpowiednich linii
pionowych i poziomych.
Poniższe przykłady pozwolą zapoznać się z metodami tworzenia wszystkich
rodzajów raportów. Zestawienie prac zaprojektujemy jako raport w postaci
formularza (wydrukowanie blankietu fmRWORMED0). Nauczymy się drukować
proste sprawozdania tego typu, jedynie przy użyciu formularzy Delphi
i standardowych komponentów.
Wykaz nieruchomości z tabeli PROPERTY sporządzimy przy pomocy raportu
kolumnowego, używając do jego budowy narzędzia QuickReport. Z pewnością
większość sprawozdań będzie sporządzana przez użytkowników Delphi przy
pomocy tego komponentu.
W rozdziale 19 skonstruujemy zestawienie krzyżowe, stosując do jego budowy
element Delphi o nazwie
DecisionCube
.
Niniejszy rozdział zakończymy zbudowaniem listy zleceń, wykorzystując jedynie
kod Object Pascala. Zaznajomimy się w ten sposób z tradycyjnym sposobem
sporządzania raportów. Do formatowania i
drukowania sprawozdania
wykorzystamy proste konstrukcje pętli i instrukcje obsługujące wyjścia terminala.
Raporty
387
Raport Work Order
Pracę z
raportami rozpoczniemy od przygotowania do druku formularza
kombinowanego
fmRWORMDE0
.
Najpierw wczytujemy do projektanta formularzy główne okno dialogowe aplikacji
RENTMAN-
fmRSYSMAN0
. Na formularzu umieszczamy komponent
PrinterSetupDialog
i nadajemy mu nazwę
ps_RENTMAN
. W menu
File
aplikacji RENTMAN klikamy podwójnie na opcji
Print Setup
i wpisujemy linię
kodu:
ps_RENTMAN.Execute;
Następnie powracamy do projektanta i łączymy klawisz szybkiego dostępu
sbPrintSetup
z metodą obsługującą zdarzenie
OnClick
właściwej pozycji
menu (
Print setup
). Okno dialogowe
Print setup
będzie wyświetlane zarówno
wskutek wciśnięcia przycisku, jak i dzięki wyborowi właściwej propozycji z menu.
Okno dialogowe
Print Setup
pozwala między innymi na zmianę drukarki
przeznaczonej do drukowania zlecenia. Wszystkie aplikacje dla Windows, które
umożliwiają drukowanie, zawierają okno do konfigurowania drukarek.
Konfiguracja opcji Work Order w menu Report
Połączymy teraz opcję
Work Order
z menu raportów z odpowiednią pozycją menu
Tables. Formularz kombinowany będzie miał własne narzędzia umożliwiające jego
wydruk, więc wybór opcji
Work Order
z oferty
Report
ma spowodować jedynie
wyświetlenie formularza wyboru-
fmRWORGRD0
. Ponieważ wywołanie
formularza umożliwia już właściwa pozycja menu
Tables
, to wystarczy połączyć
odpowiadające sobie opcje obu menu.
Przycisk drukowania
Otwieramy
fmRWORDMD0
w projektancie formularzy i bezpośrednio po lewej
stronie przycisku
OK
umieszczamy komponent
BitBn
(na górnym panelu
formularza). Zmieniamy nazwę klawisza na
bbPrint
, nagłówek na
oraz
dołączamy ikonkę Images\Buttons\print.bmp (katalog Images powinien znajdować
się w głównej kartotece Delphi). Dwukrotnie klikamy na przycisku, aby wpisać
kod obsługujący zdarzenie
OnClick
. Przy pomocy edytora kodu należy przepisać
zawartość listingu 12.1.
Listing 12.1. Drukowanie formularza fmRWORDMD0.
Tag := Longint(WindowState);
WindowState:=wsMaximized;
for I:=0 to ComponentCount-1 do
388
Część II
If (Components[I] is TButton) or
(Components[I] is TDBNavigator) or
(Components[I] is TSpeedButton) then
With Components[I] as TControl do begin
Tag:=Longint(Visible);
Visible:=False;
end;
Print;
for I:=0 to ComponentCount-1 do
If (Components[I] is TButton) or
(Components[I] is TDBNavigator) or
(Components[I] is TSpeedButton) then
With Components[I] as TControl do
Visible:=Boolean(Tag);
WindowState:=TWindowState(Tag);
Należy się upewnić, że zmienna
I
została zadeklarowana w nagłówku procedury
jako wartość typu Integer. Prawidłowa definicja może wyglądać następująco:
procedure TfmRWORDMD0.bbPrintClick (Sender: Tobject);
var
I: Integer;
Procedura ma trzy podstawowe zadania. Pierwsze polega na ukryciu komponentów
formularza, których nie chcemy drukować w
raporcie (wydrukowanie
w sprawozdaniu przycisków nie miałoby najmniejszego sensu). Drugi cel to
wydrukowanie formularza przy pomocy metody
, która wysyła do kolejki
drukarki kopię formularza wyświetlanego na ekranie. Ostatecznie ukryte
komponenty należy przywrócić z powrotem do poprzedniej postaci.
Zwróćmy uwagę na klika cech napisanego kodu. Zacznijmy od omówienia
istotnego wykorzystania własności
Tag
. Jest to zmienna typu Long Integer,
należąca do wielu komponentów Delphi, której nie jest przypisane żadne zadanie.
Możemy ją wykorzystać w dowolny sposób. Nasz podprogram używa zmiennej na
dwóch różnych poziomach: do zapisania/odtworzenia stanu okna
(
WindowState
) oraz do zapisywania/odtwarzania własności
Visible
każdego
z komponentów. Chociaż w
wielu przypadkach można by się obejść bez
zachowywania i
odświeżania poszczególnych elementów, to w
niektórych
sytuacjach skończyłoby się to otrzymaniem nieoczekiwanej postaci formularza.
Nie zawsze bowiem
WindowState
będzie ustawiona w czasie inicjalizacji jako
wsNormal
, gdyż możemy uznać, że lepiej jest powiększyć okno do pełnego
ekranu. Nie zawsze również, po uruchomieniu formularza, wszystkie jego
komponenty muszą być widoczne, gdyż z jakiegoś powodu możemy chcieć ukryć
niektóre z nich. Jeśli przyjmiemy założenie, że wszystkie komponenty są
Raporty
389
widoczne, to po wydrukowaniu formularza i odtworzeniu ukrytych elementów,
zobaczymy na ekranie również te, których nie oczekujemy. Rozwiązanie przyjęte
w listingu 12.1 jest zdecydowanie lepsze.
Innym ważnym aspektem procedury jest wykorzystanie własności
Components
.
Components
jest indeksowaną własnością sterowników, które mogą zawierać
inne elementy, na przykład:
TForm
. Napisany kod używa jej do przejrzenia
wszystkich komponentów formularza, określenia tych, które powinny być
wydrukowane i
odpowiedniego ustawienia ich własności
Visible
.
Components
można wykorzystywać do szybkiego konfigurowania własności
grupy sterowników, w czasie pracy programu.
Trzecim istotnym elementem przedstawionego programu jest używanie informacji
o typie danych w czasie działania programu (RTTI- Runtime Type Information),
między innymi w konstrukcjach
is
oraz
with ... as
. Mimo że kompilator
Delphi wytwarza kod maszynowy procesora, to w czasie pracy programu mamy
możliwość określania typów danych jego specyficznych klas. Nie jest to oczywiste
w przypadku kompilatorów kodu procesora. Informacje RTTI były kiedyś
wyłączną domeną interpreterów, takich jak Visual Basic. Programy generujące kod
procesora nie dają zwykle możliwości sprawdzania typu danych poszczególnych
elementów, ponieważ język maszynowy nie zawiera takich informacji. Zmienne są
redukowane do adresów względnych (offsets) i podczas tłumaczenia zamieniane na
kod wynikowy. Zdolność Delphi do wykorzystywania informacji o typie danych
pozwala nam korzystać z możliwości obu systemów: dysponujemy jednocześnie
skompilowanym kodem procesora oraz przydatnymi informacjami o typie danych
w czasie wykonywania programu. Dzięki temu jesteśmy w stanie sprawdzać
komponenty formularza i odpowiednio je modyfikować.
Czwartym, godnym odnotowania aspektem napisanej procedury jest selekcja
wykonywana przed ustawianiem własności
Visible
każdego elementu. Nie
wszystkie komponenty mają własność
Visible
(na przykład żaden
z komponentów niewidzialnych). Nie można zatem napisać po prostu:
with Components[I] do begin
Tag:=Longint(Visible);
Visible:=False;
end;
Ponieważ podstawowa klasa komponentu nie ma własności
Visible
, wcześniej
napisany kod nie powoduje ustawienia własności
Visible
wszystkich
elementów
Components[I]
. Gdybyśmy tak postąpili, to zaśmiecilibyśmy
następny poziom, który jest klasą formularza, gdyż działalibyśmy wewnątrz
metody formularza. Rezultatem byłoby ustawienie własności
Visible
formularza, zupełnie przez nas niepożądane.
390
Część II
Przeglądarka obiektów
Aby uniknąć błędów, powinniśmy znaleźć klasę rodzicielską, która definiuje
własność
Visible
. Pierwszym wspólnym przodkiem wizualnych sterowników
Delphi określającym własność
Visible
jest klasa komponentu
TControl
.
Można się o tym przekonać przeglądając hierarchię klas Delphi. Z dwóch metod
stosowanych do tego celu łatwiejsza polega na skorzystaniu z przeglądarki
obiektów (Object Browser). Narzędzie, które jest dostępne po pierwszym
skompilowaniu projektu, uruchamiamy wybierając opcję
Browser
z menu
View
głównego menu Delphi. Szukamy interesujących nas obiektów albo poprzez
przewijanie struktury klas w
lewym oknie przeglądarki, albo korzystając
z możliwości wyszukiwania według nazwy. Po przewinięciu lewego okna do
miejsca umożliwiającego obserwację komponentu
Tbutton
, możemy na
przykład odnotować, że dziedziczy własności bezpośrednio z
klasy
TButtonControl
, natomiast w
drugiej kolejności (jest „wnuczkiem”)
z
TWinControl
. Ta z kolei jest bezpośrednio klasą potomną
TControl
.
Przeglądając prawe okno możemy znaleźć własność
Visible
obiektów klasy
TControl
.
Inny sposób wyśledzenia miejsca w hierarchii interesującej nas klasy polega na
analizie kodu źródłowego VCL. Chociaż jest on trudniejszy od poprzedniego, to
w trakcie realizacji można się wiele nauczyć i uzyskać dużo więcej informacji.
Rozpocząć należy od modułu, który definiuje klasę i podążać wstecz. Do
lokalizacji modułów, określających konkretne komponenty, przydaje się efektywne
narzędzie wyszukujące na podstawie tekstu. Na szczęście nomenklatura modułów
stosowana przez firmę Borland jest bardzo poręczna, więc praca nie jest tak
trudna, jak może się wydawać. Aby wyszukać odpowiedniego rodzica, należy
zwracać uwagę na definicję typu każdej klasy. Informacja zawarta jest w pierwszej
linii każdej klasy. Na przykład pierwszy wiersz definicji klasy Tbutton jest
następujący:
TButton = class( TButtoControl )
Oznacza to, że
Tbutton
dziedziczy własności klasy
TButtonControl
.
Następnie w definicji klasy
TButtonControl
znajdujemy:
TButtonControl = class (TWinControl )
Klasą rodzicielską dla
TButtonControl
jest zatem
TWinControl
. Zarówno
Tbutton
, jak i
TButtonControl
są zdefiniowane w module
Stdctrls
.
Uważna analiza obu definicji dowodzi, że żadna z nich nie określa własności
Visible
, co oznacza, że wędrówkę trzeba kontynuować dalej.
TWinControl
jest zdefiniowana w module
Controls
. Odpowiednia linia kodu wygląda
następująco:
TWinControl = class ( TControl )
Raporty
391
Jak się przekonamy, również ta klasa nie określa własności
Visible
. Dopiero
w jej klasie rodzicielskiej, to znaczy w
TControl
, znajdziemy interesującą nas
definicję. Wyjaśnia to, dlaczego wykorzystaliśmy klasę
Tcontrol
do typowania
komponentów formularza wymagających zmiany własności
Visible
.
UWAGA
Metoda rzutowania, zastosowana w kodzie do drukowania formularza, nosi nazwę
rzutowania kontrolowanego (Checked typecasts). Sprawdzenie odbywa się
w czasie pracy programu. Jeśli rzutujemy obiekt klasy - używając typu klasy, który
nie jest typem klasy naszego obiektu, ani żadnym typem jego klas rodzicielskich,
zostanie wygenerowany wyjątek.
Odmienną metodą jest rzutowanie niekontrolowane, które można zaimplemen-
tować w następujący sposób:
With TControl ( Components [ i ] ) do
Przy takim sposobie rzutowania, jeśli pomylimy obiekt klasy, to najczęstszym
skutkiem będzie wygenerowanie próby zabronionego dostępu i aplikacja zostanie
„zabita” przez system. Rzutowanie kontrolowane jest zarówno funkcjonalne, jak
i bezpieczne; należy je stosować zawsze, kiedy to jest możliwe.
Innym interesującym aspektem kodu do drukowania formularza jest brak na liście
komponentów przeznaczonych do rzutowania klasy
TBitBtn
. Elementy
wymienionej klasy nie powinny być drukowane, a wcześniej na formularzu
umieściliśmy przycisk
bbPrint
. Sprawdźmy, w jaki sposób następujący kod
traktuje komponenty klasy
TBitBtn
.
If ( Components [ I ] is Tbutton ) or
( Components [ I ] is TDBNavigator ) or
( Components [ I ] is TSpeedButton ) then
Odpowiedź ukryta jest w hierarchii klas. RTTI jest operatorem, który zwraca
wartość True, jeśli sprawdza obiekt używając własnej klasy obiektu lub
którejkolwiek z klas rodzicielskich. Ostatnie zdanie jest bardzo ważne. Ponieważ
TBitBtn
dziedziczy z klasy
Tbutton
, to analiza tego typu obejmuje również
TBitBtn
. Niestety,
TSpeedButton
nie jest typem potomnym
Tbutton
dlatego musi być sprawdzany oddzielnie.
Droga na skróty?
Można postawić pytanie, czy zamiast analizować wszystkie komponenty
formularza, nie lepiej po prostu ukryć ręcznie te z nich, które nie powinny być
widoczne. Podobnie można zaprojektować formularz zajmujący pełny ekran,
392
Część II
pozbywając się problemu jego rozmiaru przy drukowaniu w czasie działania
programu. Można przykładowo napisać krótką procedurę:
DBNavigator1.Visible:=False;
bbOK.Visible:=False;
bbCancel.Visible:=False;
bbPrint.Visible:=False;
Napisany kod jest z pewnością krótszy od poprzedniego, ale więcej jest
problemów z nim związanych . Po pierwsze, w przeciwieństwie do wcześniejszego
rozwiązania, dodanie do formularza nawet jednego dodatkowego przycisku będzie
wymagać zaktualizowania procedury. Po drugie, dodanie jakiegokolwiek
komponentu, który nie powinien być drukowany (na przykład przycisku), do
jakiegokolwiek formularza rodzicielskiego będzie powodowało ten sam problem.
Po trzecie, powyższy styl programowania nie pozwala, aby wybrany komponent
mógł być wcześniej ukryty. Dokładniej, jeśli w rozważanej procedurze zechcemy
uwzględnić możliwość ukrywania przycisków w czasie inicjowania formularza, to
przed każdą linią dotyczącą konkretnego komponentu należałoby umieścić
polecenie, zachowujące stan jego własności
Visible
przed dokonaniem zmiany.
Należałoby również zaprogramować odtwarzanie własności
Visible
po
wydrukowaniu raportu. Wkrótce otrzymamy co najmniej tyle samo linii programu,
co wcześniej, mając w zamian rozwiązanie dużo mniej elastyczne. Powiększanie
formularza może być pożyteczne z punktu widzenia potrzeby drukowania, ale
niekoniecznie ze względu na wygląd formularza na monitorze. Z różnych
powodów możemy nie chcieć, aby formularz zajmował nam cały ekran. W takim
przypadku konieczność zapewnienia dobrego wydruku nie powinna ograniczać
naszej swobody projektowania.
Zawarty w listingu 12.1 kod metody
OnClick
przycisku
bbPrint
może być
wykorzystany we wszystkich aplikacjach Delphi oraz we wszystkich formularzach.
Możemy stosować to rozwiązanie w następnych, własnych aplikacjach. Można
nawet skopiować przycisk i jego program
OnClick
do nadrzędnej klasy
formularzy -
fmAnyForm
, dodając w ten sposób zdolność drukowania do
wszystkich formularzy systemu RENTMAN.
Drukowanie wielu rekordów
Bardzo łatwo zaprogramować drukowanie wielu wierszy tabeli. Aby móc
drukować wszystkie zlecenia w pliku, wystarczy napisać procedurę:
With taWorder do begin
First;
While not (EOF) do begin
fmRWORMDE0.bbPrintClick (Self);
Next;
end;
Raporty
393
end;
Formularze drukowania
Zapamiętajmy, że łatwo jest zaprojektować formularze, których jedynym
przeznaczeniem będzie wydrukowanie. Możemy, na przykład, skonstruować raport
dziedziczący własności formularza kombinowanego work-order, przeznaczony
wyłącznie do drukowania. Możemy, stosując różne elementy, ukształtować
zestawienie w sposób podnoszący jego czytelność, dobierając w szczególności
dobrze wyglądające na papierze zestawienia kolorów (niektóre popularne
kombinacje, które sprawdzają się na ekranie, takie jak czarny tekst na szarym tle,
powinny być zabronione), wykorzystując różne style ramek itp. Już w czasie
projektowania ukrywamy komponenty, które nie powinny być widoczne na
wydruku, nie martwiąc się, jak to zrobić w trakcie pracy programu. W tym
rozdziale nie planujemy budowy formularzy przeznaczonych wyłącznie do
drukowania, ale można rozważyć samodzielne wykonanie takiego projektu.
UWAGA
Tworzenie raportów poprzez drukowanie specjalnych formularzy jest najmniej
efektywnym sposobem wyprowadzania danych w postaci wydruku. Obraz
formularza przesyłany do wydruku jest dużą mapą bitową, co wymaga od drukarki
posiadania dużej ilości pamięci. Niektóre urządzenia mogą nie być w stanie
wydrukować dokumentu w całości. Drukowanie formularzy tą metodą trwa
również dłużej, niż podobnych raportów przy użyciu innych metod. Należy
również uwzględnić, że ponieważ wydruk jest w zasadzie odwzorowaniem obrazu
na ekranie, to na jego jakość ma wpływ relatywnie niska rozdzielczość monitora
oraz stosunkowo wysoka rozdzielczość drukarki.
Alternatywne metody tworzenia raportów formularzowych
Raporty mające kształt formularzy mogą być również projektowane przy użyciu
komponentów programu narzędziowego Delphi- QuickReport lub komercyjnych
pakietów do tworzenia raportów. Wielu użytkowników może uznać, że korzystanie
z tych narzędzi jest łatwiejsze niż drukowanie formularzy Delphi
wykorzystywanych na monitorze. Dla prostych raportów tego typu,
przeznaczonych do wewnętrznego użytku, zastosowanie metody
obiektu
TForm
, wydaje się zupełnie wystarczające, jednak rodzina komponentów
QuickReport
zawiera narzędzia, mogące zaspokoić zarówno proste, jak
i bardziej wyrafinowane potrzeby.
394
Część II
Testowanie raportu w postaci formularza
Jesteśmy gotowi do sprawdzenia nowego raportu. Zachowujemy projekt
i uruchamiamy aplikację. Wybieramy opcję
Reports\Work Order
(lub wciskamy
klawisz F7). Na ekranie powinien ukazać się formularz do wyboru wykazu prac.
Wybieramy jakieś zestawienie i wciskamy przycisk
Edit
. Następnie klikamy
przycisk
. Powinniśmy ujrzeć formularz powiększony do pełnego ekranu
z ukrytymi przyciskami sterowników. Po wydrukowaniu zestawienie powinno
wrócić do normalnego wyglądu. Gdy zakończymy próby, zamykamy program
i wracamy do Delphi.
UWAGA
Zwróćmy uwagę na możliwość wykorzystania właściwości
PrintScale
obiektu
Tform
do sterowania sposobem, w jaki formularz jest obsługiwany przez swoją
metodę
. Domyślne ustawienie ma wartość
poProportional
, co
powoduje drukowanie formularza bardzo podobnego do swojego odbicia na
ekranie. Zachowana jest ta sama rozdzielczość wyrażona w liczbie pikseli na jeden
cal. Ustawienie wartości
poPrintToFit
skutkuje wydrukiem formularza,
z zachowaniem odpowiednich proporcji, ale z wymiarami zmienionymi, by
dopasować go do strony wydruku. Wreszcie ustawienie
poNone
, które wyłącza
wszelkie specjalne zasady skalowania i wymiarowania. Drukowanie formularza
różniącego się zasadniczo od swojego odpowiednika ekranowego może dać dobre
wyniki, gdyż rozdzielczość współczesnych monitorów nie nadąża za parametrami
większości drukarek.
Kolejny raport skonstruujemy wykorzystując komponenty programu Quickreport.
Przekonamy się, że korzystanie z tego narzędzia jest tylko trochę bardziej
skomplikowane od stosowania standardowych elementów ekranowych.
Komponenty programu nadają się do konstruowania wielu rodzajów raportów,
a narzędzia QuicReport zawierają szybkie procedury nadające się do
konstruowania profesjonalnych raportów w komercyjnych aplikacjach tworzonych
przy użyciu Delphi.
Zestawienie kolumnowe- Lista nieruchomości
Następnym raportem, który zbudujemy, będzie lista elementów tabeli
PROPERTY. Do szybkiej konstrukcji zestawienia wykorzystamy nowy kreator
Delphi- Quickreport.
Wybieramy opcję
File\New
, a następnie pozycję
Busines
w oknie dialogowym
New
Item
. Aby rozpocząć pracę z kreatorem, dwukrotnie klikamy
Quickreport
Wizard
Raporty
395
Keator będzie nam zadawał kilka pytań i konstruował podstawowy raport
w oparciu o udzielone odpowiedzi.
Najpierw zostaniemy poproszeni o wskazanie katalogu lub aliasu bazy danych,
z którą będziemy pracować. Należy znaleźć i wybrać na liście alias
dbRENTMAN
.
Na dole ekranu pojawi się zapytanie o tabelę, która jest podstawą raportu.
Wybieramy z listy tabelę PROPERTY i klikamy na przycisku
Next
.
Następnie będziemy proszeni o wskazania pól tabeli, które mają być uwidocznione
na zestawieniu. Wybieramy: PROPERTY_NUMBER, ADDRESS, CITY, STATE,
ZIP, ADDITION, I SCHOOLDISTRICT. Możemy przemieszczać pola między
listami
Avaliable
(dostępne) i
Selected
(wybrane) - poprzez dwukrotne kliknięcie
odpowiedniej pozycji lub dwuetapowo: najpierw zaznaczając element, a następnie
wciskając odpowiedni przycisk ze strzałką. Po wytypowaniu wszystkich pól,
należy kliknąć przycisk
Next
.
Ostatnie pytanie dotyczy tytułu raportu. Należy wpisać odpowiednią nazwę, na
przykład „Lista nieruchomości”, a następnie zdecydować się na odpowiedni krój
czcionki, jaką będzie napisana ( Arial wydaje się dobrym wyborem).
Kończymy pracę wciskając przycisk
Finish
, a kreator wygeneruje podstawowy
raport, który można zmodyfikować według swojego uznania.
Pierwsze ujęcie zestawienia, chociaż mało skomplikowane, wymaga z pewnością
pewnych modyfikacji. Przełączamy na
True
własność
Active
komponentu
Table
i wybieramy
Preview -
aby ujrzeć, jak będzie wyglądał raport w czasie
pracy programu. Rysunek 12.1. przedstawia obraz, który powinniśmy zobaczyć.
Zmodyfikowane zestawienie narzędzi podnoszących czytelność i estetykę raportu
ilustruje rysunek 12.2.
Oto kilka najważniejszych zmian wprowadzonych w sprawozdaniu:
Podsumowania kolumn - zauważmy umieszczoną na dole raportu liczbę
wszystkich nieruchomości. QuickReport umożliwia grupowanie, sortowanie
wszystkich grup itp.
Kolory obiektów i pasków - w przykładzie wykorzystano kolory pasków, aby
nieco skontrastować szczegóły.
Pola systemowe - zwróćmy uwagę na datę i czas sporządzenia raportu
umieszczone w lewym górnym rogu zestawienia. Pola zostały utworzone przy
użyciu komponentu
QRSysData
programu Quickreport.
396
Część II
WSKAZÓWKA
Służący do tworzenia wyrażeń komponent
QRExpr
programu QuickReport nie
umożliwia korzystania ze składni ANSI SQL do wyprowadzania danych
sumarycznych. Aby uzyskać liczbę rekordów tabeli, nie należy używać wyrażenia
COUNT(*).
Nie działa również
COUNT(PROPERTY_ NUMBERS)
.
QRExpr
dopuszcza wyłącznie słowo
COUNT
, bez nawiasów i na pewno nie zezwala na
stosowanie składni ANSI SQL.
Rysunek 12.1.
Pierwsze ujęcie
zestawienia
nieruchomości,
utworzonego przy
pomocy kreatora
QuickReport.
Rysunek 12.2.
To samo
sprawozdanie po
małej kosmetyce.
Raporty
397
UWAGA
Dołączony do książki CD-ROM zawiera kody źródłowe wszystkich raportów
omawianych w tym rozdziale.
Po zakończeniu przekształceń zestawienia utworzonego przez kreator, należy
nadać mu nazwę frRPROLST0 i zapisać na dysku jako
RPROLST0.PAS
.
Połączenie raportu z aplikacją
Aby w pełni sprawdzić działanie raportu, musimy połączyć go z aplikacją
RENTMAN. W tym celu powinniśmy:
Zadeklarować moduł formularza raportu w
głównym module aplikacji,
RSYSMAN0.
Dodać odpowiednią pozycję do menu oraz odpowiednio zaprogramować
wywołanie nowego raportu w głównym menu programu.
Realizację powyższych zamierzeń rozpoczniemy od otwarcia
fmRSYSMAN
w projektancie formularzy. Wybieramy opcję
Use
Unit
w menu
File
i dwukrotnie
klikamy na pozycji RPROLST0. Dzięki temu możemy odwoływać się do
formularza raportu zawartego w module RPROLST0.
Kolejno: klikamy dwukrotnie komponent
MainMenu
i wybieramy czystą pozycję
w menu
Reports
. Wprowadzamy nagłówek: &Property List i określamy klawisz
skrótu: F11. Dwukrotnie klikamy na modyfikowanej pozycji menu i przy pomocy
edytora kodu piszemy:
frRPROLST0.QuickRep1.Preview;
Dzięki temu poleceniu nowy raport będzie wyświetlany na ekranie w momencie
drukowania, zachowywania lub unieważniania.
Po wykonaniu wszystkich wymienionych czynności jesteśmy gotowi do
sprawdzenia raportu podczas pracy programu. Zachowujemy projekt
i uruchamiamy aplikację. Rysunek 12.3. ilustruje wygląd raportu podczas działania
programu.
398
Część II
Raport przedstawiający zestawienie zadań
Ostatni raport w tym rozdziale zostanie skonstruowany wyłącznie przy użyciu
języka Object Pascal. Jeśli kiedykolwiek bowiem spotkamy się z raportem
wymagającym specjalnych środków, niemożliwych do osiągnięcia przy użyciu
komponentów
QuickReport
, zawsze możemy się uciec do ręcznego
zaprogramowania raportu, używając do tego wyłącznie kodu Object Pascal-a.
Ponieważ Borland Pascal został zaprojektowany jako język programowania
ogólnego przeznaczenia, posiada wszystkie narzędzia, których można oczekiwać
od języka programowania trzeciej generacji takiego jak C, Basic czy Pascal.
Object Pascal zaimplementowany w Delphi jest dostatecznie silnym narzędziem,
by przy jego pomocy utworzyć raport na miarę każdych oczekiwań.
Umieszczamy nowy komponent
Query
w
module danych
dmRENTMAN
.
Nadajemy mu nazwę
quTaskList
i wprowadzamy następujący kod SQL:
SELECT E.Name, P.Address, P.City, P.Addition, T.Description,
➥
T.TaskDuration
FROM EMPLOYEE E,
WORDER W,
PROPERTY P,
WODETAIL D,
WORKTYPE T,
WHERE E.EMPLOYEE_NUMBER=W.EMPLOYEE_NUMBER
and W.WORDER_NUMBER=D.WORDER_NUMBER
and W.PROPERTY_NUMBER=P.PROPERTY_NUMBER
and D.WORK_TYPE_CODE=T.WORK_TYPE_CODE
and W.StartDate<=:ListDate
Rysunek 12.3.
Raport
przedstawiający
zestawienie
nieruchomości-
podczas pracy
programu.
Raporty
399
and W.EndDate>=: ListDate
ORDER BY E.Name, P.Address, P.City, P.Addition, T.Description
Powyższe zapytanie będzie łączyć tabele EMPLOYEE, WORDER, PROPERTY,
WODETAIL i
WORKTYPE - celem stworzenia poszerzonej listy zadań
związanych z określoną datą. Zauważmy, że zapytanie definiuje pojedynczy
parametr :
ListDate
, który musi być określony, aby procedura działała
prawidłowo. Wykorzystując edytor właściwości do skonfigurowania własności
Params
komponentu
Query
, ustawiamy typ parametru
ListDate
na
Date
.
Następnie określamy
DatabaseName
jako
dbRENTMAN
, dwukrotnie klikamy
komponent i dodajemy wszystkie jego pola jako elementy
TField
.
W tym momencie jesteśmy gotowi do napisania kodu służącego do utworzenia
raportu. Do projektanta formularzy wczytujemy główny formularz aplikacji -
fmRSYSMAN0 i dwukrotnie klikamy na komponencie
MainMenu
, aby otworzyć
narzędzie do projektowania menu. Wybieramy opcję
Reports\Task List
i ustawiamy klawisz skrótu na F10. Dwukrotnie klikamy na opcji
Task
List
i zmieniamy obsługę zdarzenia
OnClick
zgodnie z listingiem 12.2.
Listing 12.2. Procedura obsługi zdarzenia TaskList1Click.
procedure TfmRSYSMAN0.TaskList1Click(Sender: TObject);
var
LastName : String;
CurrentLine : Byte;
procedure
PrintColumnHeadings;
begin
Writeln(PrintFile,' '+Pad('Address',30),
➥
Pad('City',20), Pad('Addition',20),Pad('Work',30),
➥
Pad('Time (Days)',15));
end;
begin
inherited;
If (MessageDlg('Print the Task List report?',
➥
mtConfirmation,mbYesNoCancel,0)<>mrYes) then Exit;
try
Cursor:=crHourGlass;
With dmRENTMAN, quTaskList do begin
ParamByName('ListDate').AsDate:=StrToDate(‘04/05/97’);
//Supply a valid date here based on your tests data
// ParamByName('ListDate').AsDate:=Date;
Open;
try
BeginReport(poPortrait,Caption);
try
While not eof do begin
CurrentLine:=9;
400
Część II
PrintHeader('RTSKLST0',Caption,'Employee Task
➥
List',ParamByName('ListDate').AsString);
PrintColumnHeadings;
While (not eof) and (CurrentLine<>PageLength) do
begin
Writeln(PrintFile);
Writeln(PrintFile,'EMPLOYEE:'+quTaskListName.
➥
AsString);
Inc(CurrentLine,2);
Repeat
LastName:=quTaskListName.AsString;
Writeln(PrintFile,'
'+Pad(quTaskListAddress.
AsString,30),Pad(quTaskListCity.AsString,20),
Pad(quTaskListAddition.AsString,20),
Pad(quTaskListDescription.AsString,30),
➥
Pad(quTaskListTaskDuration.AsString,15));
Next;
Inc(CurrentLine);
Until (eof) or (CurrentLine=PageLength) or
(quTaskListName.AsString<>LastName);
end;
end;
finally
EndReport;
end;
finally
quTaskList.Close;
end;
end;
finally
Cursor:=crDefault;
MessageDlg(‘Task List Report Finished’,mtInformation,
➥
[mbOK],0);
end;
end;
Elementy napisanego wyżej programu wymagają wielu innych funkcji i procedur.
W module
RSYSMAN0
, bezpośrednio przed procedurą
TaskList1.Click
,
należy wpisać kod zawarty w listingu 12.3.
Listing 12.3. Funkcje narzędziowe potrzebne do raportu Task
List.
function Pad(InStr : String; TotalLen : Integer) : String;
begin
Result:=InStr;
While (Length(Result)<TotalLen) do Result:=Result+' ';
end;
function LPad(InStr : String; TotalLen : Integer) : String;
begin
Raporty
401
Result:=InStr;
While (Length(Result)<TotalLen) do Result:=' '+Result;
end;
function Center(InStr : String; TotalLen : Integer) : String;
var
NumSpace : Integer;
Temp : String;
begin
NumSpace := (TotalLen-Length(InStr)) div 2;
Temp:='';
While Length(Temp)<NumSpace do Temp:=Temp+' ';
Result:=Temp+InStr+Temp;
While Length(Result)<TotalLen do Result:=Result+' ';
end;
procedure PrintHeader(ReportName,SystemTitle,ReportTitle,
➥
Criteria : String);
begin
If (Printer.PageNumber <> 1) then Write(PrintFile,^L);
Writeln(PrintFile,Pad('Report:
'+ReportName,25),
➥
Center(SystemTitle,LineLength-45),LPad('Page:
➥
'+IntToStr(Printer.PageNumber),25));
Writeln(PrintFile);
Writeln(PrintFile,Pad('Print date: '+DateToStr(Date),25),
➥
Center(ReportTitle,LineLength-45),LPad('Print time:
➥
'+TimeToStr(Time),25));
Writeln(PrintFile);
Writeln(PrintFile,Pad('User name: '+UserName,25),
➥
Center('For: '+Criteria,LineLength-45));
Writeln(PrintFile);
end;
procedure BeginReport(Orientation : TPrinterOrientation;
➥
Title :String);
begin
Printer.Orientation:=Orientation;
Printer.Title:=Title;
AssignPrn(PrintFile);
Rewrite(PrintFile);
With Printer.Canvas.Font do begin
Name:='Courier
New';
Height:=-13;
Style:=[];
end;
LineLength:=(Printer.PageWidth div Printer.Canvas.
➥
TextWidth('X'))-5;
402
Część II
PageLength:=(Printer.PageHeight div Printer.Canvas.
➥
TextHeight('X'))-2;
end;
procedure EndReport;
begin
CloseFile(PrintFile);
end;
Napisane wyżej funkcje i procedury korzystają ze zmiennych, które powinny być
zadeklarowane jako globalne w module RSYSMAN0. W bloku deklaracji var,
bezpośrednio przed linią
{$R *.DFM}
, należy umieścić odpowiednie zapowiedzi
zmiennych np.:
var
PrintFile: Text;
LineLength: Integer;
PageLength: Integer;
UserName: String;
Musimy również uwzględnić, że napisany przez nas program wymaga użycia
modułu
Printers
. Zawarty w Delphi moduł
Printers
pozwala wykorzystać
podstawowy interface drukowania systemu Windows. Dodajemy zatem deklarację
Printers
do linii deklaracji modułów
Uses
...
Działanie metody TaskList1Click
Kilka punktów procedury
TaskList1Click
zasługuje na dyskusję. Zauważmy
najpierw wykorzystanie standardowej procedury
Writeln
do wysyłania danych
na wyjście drukarki. Poprzez zastosowanie procedury
AssignPrn
odbywa się
skojarzenie pliku tekstowego z domyślnym urządzeniem drukującym Windows.
Otwierając i dopisując dane do pliku, powodujemy wysyłanie jego zawartości na
wyjście drukarki Windows.
Istnieją dwa sposoby drukowania tekstów przez aplikacje Windows.
Pierwszy utrzymuje, że do rozpoczęcia procesu drukowania (w celu wysłania
tekstu do drukarki) należy używać procedury
BeginDoc
i
procedury
TextOutput
. Wiele przemawia za tą metodą, a w szczególności możliwość
całkowitej kontroli nad wyglądem tekstu oraz zdolność do umieszczania go
dokładnie w zaplanowanym miejscu. Chociaż z drugiej strony płaci się za to
skomplikowanym sposobem konfiguracji i wykorzystania. Jeśli zachodzi potrzeba
tak dokładnego sterowania wydrukiem, lepiej chyba skorzystać z kompleksowych
rozwiązań udostępnianych przez specjalne narzędzia do tworzenia raportów. Są
Raporty
403
one zazwyczaj wyposażone w szeroką gamę środków do sterowania wyjściem na
drukarkę.
Drugi zaleca drukowanie tekstów w programach pod kontrolą Windows metodą
zastosowaną przez nas. Pomysł polega na stosowaniu środków najprostszych
spośród możliwych. Zadania wymagające precyzyjnie zaplanowanego wydruku
można bowiem wykonać przy pomocy specjalnych narzędzi do tworzenia
raportów. Używając do drukowania standardowych procedur obsługi wyjścia
korzystamy, bez zbędnych komplikacji, ze wszystkich ich udogodnień. Takie
proste podejście pozwala pisać proste i niedługie procedury.
Procedura BeginReport
Zbadamy teraz szczegółowo wszystkie części napisanego programu, zaczynając od
procedury
BeginReport
. Dla przypomnienia odpowiedni tekst kodu wygląda
następująco:
procedure BeginReport
➥
(Orientation : TPrinterOrientation; Title :String);
begin
Printer.Orientation:=Orientation;
Printer.Title:=Title;
AssignPrn(printFile);
Rewrite(PrintFile);
With Printer.Canvas.Font do begin
Name:=‘Courier
New’;
Height:=10;
Style:=[];
end;
LineLength:=(Printer.PageWidth div Printer.Canvas.
➥
TextWidth(‘X’))-5;
PageLength:=Printer.PageHeigth div Printer.Canvas.
➥
TextHeight(‘X’))-2
end;
Zauważmy, że orientacja wydruku raportu jest przekazywana do procedury
poprzez zmienną typu
TPrinterOrientation
. Powyższy typ jest
zdefiniowany w module
Printers
i ma dwie możliwe wartości
poPortrait
i
poLandscape
. Ponieważ orientacja strony jest zwykle zależna od procedury,
która ją wywołuje, to w chwili inicjowania zadania wydruku powinna być
określona.
Sekcja
With Printer.Canvas.Font
konfiguruje fonty używane do
drukowania. Zakładamy, że cały raport będzie drukowany tym samym krojem.
Ponadto przyjmujemy, że będziemy używać nieproporcjonalnego kroju True Type,
404
Część II
a
mianowicie Courier New. Na ogół nie zaleca się stosowania fontów
proporcjonalnych.
Zwróćmy uwagę na sposób kalkulowania szerokości i wysokości strony. Linia:
LineLength:=(Printer.PageWidth div Printer.Canvas.
➥
TextWidth(‘X’))-5;
dzieli liczbę pikseli na drukowanej stronie (obliczoną na podstawie aktualnego
rozmiaru papieru i orientacji strony) przez szerokość pojedynczego znaku „X”
aktualnie stosowanego fontu. Jest to jeszcze jeden powód, aby korzystać
z czcionek nieproporcjonalnych. Ponieważ wszystkie znaki mają w tym przypadku
tę samą szerokość, to kalkulacja wykorzystująca funkcję
TextWith
będzie
poprawna. W
przypadku korzystania z
czcionek proporcjonalnych proces
obliczania maksymalnej liczby znaków w jednej linii jest dużo bardziej złożony.
Od ilorazu powinno się odjąć liczbę pięć, aby umożliwić zastosowanie lewego
i prawego marginesu.
Przekazywany do procedury parametr
Title
jest wykorzystywany do ustawiania
nazwy (używanej w
buforze drukarki Windows i
na sieciowych stronach
nagłówkowych) zadania przeznaczonego do drukowania. Jeśli konfiguracja sieci
wymaga drukowania strony nagłówkowej przed każdym wydrukiem, to można
użyć własności
Title
z
TPrinter
do ustawienia tekstu, który ma tam zostać
wydrukowany.
Funkcje
Pad
,
Lpad
oraz
Center
pełnią podobne zadanie. Jedna
z podstawowych trudności związanych z formatowaniem wydruku polega na
odpowiednim ustawieniu szerokości kolumn, zawierających dane o
różnej
długości. Drugi problem to równomierne rozmieszczenie nagłówków kolumn
i danych, które je opisują. Jeśli nagłówek kolumny jest dłuższy od danych
zawartych w rubryce, to jej szerokość musi być dopasowana do nagłówka. Jeśli
dane są szersze od nagłówka, to tytuł kolumny musi być uzupełniony
o odpowiednią liczbę spacji. Wymienione wyżej funkcje mają za zadanie
dopełnianie rozmiaru elementów raportu w sposób ułatwiający wyrównanie
kolumn. Obok decyzji o użyciu fontów nieproporcjonalnych, logiczne dopełnianie
rozmiaru elementów kolumn jest najważniejszym aspektem osiągnięcia
poprawnych wydruków.
Procedura PrintHeader
Procedura
PrintHeader
została napisana modułowo, by nadawała się do
wykorzystania w innych programach. Chociaż drukowanie raportów wyłącznie
przy użyciu kodu programu nie jest metodą godną rekomendacji, to w razie
potrzeby można wykorzystać fragmenty programu napisane w tym rozdziale,
a w szczególności procedurę
PrintHeader
. Otrzymane jako parametry
Raporty
405
elementy:
ReportName
,
SystemTitle
,
ReportTitle
i
Criteria
,
procedura drukuje w nagłówku strony raportu. Taka elastyczność umożliwia
wykorzystanie kodu praktycznie we wszystkich rodzajach raportów.
Linia:
If ( Printer.PageNumber <> 1 ) then Write ( PrintFile,#12 );
powoduje sprawdzenie, czy procedura
PrintHeader
wywoływana jest po raz
pierwszy. Jeśli nie, to każdorazowo na drukarkę zostanie wysłany znak CTRL+L.
Znak ASCII o kodzie #12 jest praktycznie na każdej drukarce interpretowany jako
polecenie wysunięcia strony. Nie należy oczekiwać wysunięcia strony papieru przy
pierwszym wywołaniu procedury ani zapominać o uzupełnieniu papieru przed
drukowaniem raportu.
Funkcje „zwracające” datę i czas systemowy są aktualizowane przy każdym
wywołaniu. W czasie przepisywania kodu warto rozważyć możliwość zachowania
daty i czasu określonych w chwili rozpoczęcia wydruku i drukowanie w raporcie
zapamiętanych wartości.
Celem kalkulacji opartej o długość strony w wywołaniu funkcji
Center
dla tytułu
systemu, tytułu raportu i kryteriów pól jest umożliwienie zmiany orientacji strony.
Jeśli orientacja zostanie przełączona na
poPortrait
, to nagłówek zostanie
dynamicznie przemieszczony i prawidłowo wydrukowany.
Więcej o procedurze TaskList1Click
Linia:
ParamByName (‘ListDate’).AsDate:=Date
dostarcza aktualną datę systemową do zapytania
quTaskList
jako jego parametr
ListDate
. W napisanym programie polecenie zaznaczone jest jako komentarz.
Uaktywnienie go umożliwia drukowanie raportu zaopatrzonego w aktualną datę
(należy oczywiście zmienić na komentarz wcześniejsze polecenie podstawienia
stałej daty). Po wyznaczeniu parametru
ListDate
wywoływana jest metoda
Open
, która ma za zadanie zainicjowanie zapytania i dostarczenie programowi
jego wyników.
Bloki postaci
try...finally
wykorzystywane są do oprogramowania obsługi
wyjątków. W programie zastosowano trzy konstrukcje odpowiedzialne za reakcje
na problemy mogące wyniknąć w czasie działania procedury. Pierwsza odpowiada
za zamknięcie zapytania przez zakończeniem procedury, druga ma za zadanie
czuwać, aby wykorzystywany do drukowania plik tekstowy został zamknięty przed
zakończeniem pracy procedury bez względu na to, czy kończona jest normalnie,
406
Część II
czy wskutek błędu. Celem trzeciej jest przywrócenie wskaźnikowi kursora
normalnej postaci po zakończeniu wydruku.
Sekcja
Repeat... Until
(
quTaskListName.AsString<>LastName
)
dotyczy grupowania wydruków raportu. Pomysł polega na drukowaniu
oddzielnych list zadań dla każdego pracownika. Dzięki zastosowaniu konstrukcji
SQL:
ORDER BY
oraz logice pętli
Repeat
...
Until
zadanie jest wykonywane
prawidłowo: listy poleceń dla każdego pracownika drukowane są jedna po drugiej.
Wskaźnik myszy jest zamieniany na klepsydrę bezpośrednio przed rozpoczęciem
wydruku, a po zakończeniu przywracany do normalnej postaci. Ważne jest
zasygnalizowanie, że po uruchomieniu raportu coś zaczęło się dziać; im więcej
możliwości kontroli nad programem ma użytkownik, tym lepiej. W tym samym
celu można rozważyć inne rozwiązanie, polegające na wykorzystaniu do
ilustrowania postępu w
drukowaniu komponentu Delphi o
nazwie
TProgressBar
. Do aktualizacji wskaźnika można wykorzystać moment
zwracania wartości przez zapytanie.
Podgląd wydruku raportu
Innym udogodnieniem jest możliwość przejrzenia raportu przed wydrukowaniem.
Można to łatwo uzyskać przez wskazanie wyjścia przypisaniem
Assign
zamiast
AssignPrn
. Raport jest plikiem tekstowym, do przeglądu można zatem
wykorzystać metodę
LoadFromFile
komponentu
Memo
, która pozwala na
wyświetlenie pliku w oknie wyposażonym w możliwość przewijania tekstu. Chcąc
później wydrukować przeglądany raport, wystarczy zainicjować drukowanie
przypisaniem
AssignPrn
i wpisać wartość
Lines
własności
TMemo
obiektu
drukarki.
Zachowujemy i kompilujemy projekt, a następnie uruchamiamy program. Po
ukazaniu się głównego okna aplikacji wciskamy klawisz F10, aby wydrukować
formularz przy pomocy napisanego przed chwilą programu.