Rozdział 19
Raporty
Delphi jest nie tylko wielce uzdolnionym„projektantem” formularzy, lecz i w pełni
profesjonalnym narzędziem do pisania raportów. Jego komponenty
QuickReport
oferują pełen zestaw udogodnień raportowych, z pomocą których
możemy tworzyć nawet najbardziej wyszukane sprawozdania finansowo-
ekonomiczne. Delphi zawiera również niezwykle efektywną kostkę decyzyjną
(Decision Cube) - komponent, dzięki któremu budowanie raportów krzyżowych
staje się drobnostką. A jeśli chcielibyśmy nasze raporty finansowe zilustrować
wykresami, to i tutaj Delphi jest najwłaściwszym partnerem. Diagramy tego
rodzaju możemy nawet zintegrować z kostkami decyzyjnymi i utworzyć wykresy
krzyżowe. A wszystko to można zrobić na moment nawet nie opuszczając
zintegrowanego środowiska programistycznego Delphi.
Rodzaje raportów
W rozdziale tym zbudujemy cztery typy raportów, korzystając z bazy danych
pracowników (Employee), dostarczanej wraz z Delphi Client/Server. Utworzymy
prosty raport listowy drukujący tablicę klientów, raport grupowy sortujący tablicę
klientów według krajów, złożony raport typu „pozycja główna/szczegóły”
(master/detail), wyszczególniający wartość sprzedaży według klientów, i w końcu
raport krzyżowy, podsumowujący wartość sprzedaży dla każdego kraju z osobna.
Narysujemy także diagram kołowy (pie chart), wizualnie ilustrujący udział
poszczególnych krajów w sprzedaży.
Baza danych pracowników
Zaimplementowana w
InterBase baza danych pracowników dołączona jest
zarówno do samego systemu InterBase, jak i
pakietu Delphi w
wydaniu
Client/Server. Zawiera ona kilka tablic i wykorzystywana jest jako pomoc do
podręcznika towarzyszącego systemowi InterBase. Rysunek 19.1 pokazuje
relacyjny schemat bazy danych pracowników.
582
Część IV
Już w swej „dziewiczej” postaci Delphi zawiera alias BDE o nazwie
IBLOCAL
,
specjalnie przeznaczony do współpracy z bazą danych pracowników. My zaś
IBLOCAL
użyjemy dla zbudowania raportów i wykresów ilustrujących dane
zgromadzone w tablicach bazy danych pracowników.
Listowy raport o klientach
By rozpocząć konstruowanie Raportu listowego dla bazy pracowników, należy
wykonać następującą procedurę:
UWAGA:
Metody budowania raportów, proponowane w tym rozdziale, nie korzystają
z
wbudowanych szablonów QuickReport ze składnicy obiektów Delphi.
Stwierdziłem bowiem, że są one nieco nieporęczne. Odmiennie niż inne składniki
karty
Forms
w repozytorium obiektów (Object Repository), elementy QuickReport
nie są w rzeczywistości formularzami - są one „w prostej linii” potomkami
TQuickReport.
Rysunek 19.1.
Schemat bazy
danych
pracowników
Employee.
Rozdział 19 Raporty
583
Oznacza to, jeśli w celu zaczęcia nowego raportu skopiujemy któryś z tych
szablonów i nawet otrzymamy coś, co wydaje się być nowym formularzem, to
w istocie otrzymamy element leżący gdzieś w połowie drogi między
TPanel
a
TForm
ze sporą dawką TQuickReport na dodatek. Skutek jest taki, że
formularze naszych raportów nie pojawia się we właściwości
Forms
komponentu
Screen
, a to z kolei sprawia, iż dostęp do nich stanie się nieco trudniejszy i nie
pojawią się one na liście raportów prezentowanej przez aplikację REPORTS, którą
zbudujemy w dalszej części rozdziału. W sumie można więc stwierdzić, że jeśli
chcemy mieć możliwość zmieniania naszych raportów en masse korzystając
z
wizualnego dziedziczenia formularzy i
polimorficznego manipulowania
z użyciem informacji o
typie w
fazie wykonania RTTI (Runtime Type
Information), to o wiele korzystniej jest tworzyć raporty z pomocą formularzy
i komponentów
TQuickReport
, które są od siebie niezależne.
1. Kliknięciem w
File\New Application
rozpocznij nową aplikację, służącą jedynie
do czasowego pomieszczenia budowanego raportu.
2. Zmień domyślną wartość
właściwości
Name
formularza na
CustomerReport
(zmieni to również właściwość
Caption
).
3. Umieść na domyślnym formularzu aplikacji komponent
TQuickReport
i jego właściwość
ReportTitle
ustaw na
Customer List Report
.
4. Umieść na formularzu komponent
TQuery
i zmień jego nazwę (
Name
) na
MasterQuery
.
5. Właściwość
DatabaseName
komponentu
MasterQuery
ustaw na
IBLOCAL
. Do właściwości
SQL
wpisz następujący kod:
SELECT * FROM
CUSTOMER
6. Otwórz komponent
MasterQuery
klikając dwukrotnie w jego właściwość
Active
(serwer InterBase musi przy tym pracować; należy pamiętać też, by
w odpowiedzi na monit podać właściwą nazwę użytkownika i hasło).
7. Ustaw właściwość
DataSet
komponentu
QuickRep
tak, by wskazywała na
nasz komponent
MasterQuery
.
8. Na komponencie
QuickRep
umieść cztery komponenty
QRBand
.
Właściwość
BandType
pierwszego
QRBand
ustaw na
rbTitle
, drugiego na
rbColumnHeader
, trzeciego na
rbDetail
, zaś czwartego na
rbPageFooter
.
9. W sekcji tytułowej raportu umieść komponent
QRSysData
i jego właściwości
Alignment
nadaj wartość
alCenter
. Jego właściwość
Data
ustaw na
qrsReportTitle
.
584
Część IV
10. W sekcji szczegółów raportu umieść cztery komponenty
QRDBText
i ustaw
ich właściwości
DataSet
na
MasterQuery
. Komponenty te stanowić będą
kolumny raportu. Przypomnijmy, że by wybrać więcej niż jeden komponent na
raz, należy go kliknąć przy wciśniętym klawiszu SHIFT.
11. Właściwość
DataField
pierwszego
QRDBText
ustaw na
CUSTOMER
,
drugiego na
CITY
, trzeciego na
STATE_PROVINCE
, a
czwartego na
COUNTRY
.
12. W sekcji nagłówków kolumn umieść cztery komponenty
QRLabel
. Posłużą
one jako nagłówki kolumn dla listowanych w raporcie pól bazy danych. Każdy
z nich umieść nad inną kontrolką
QRDBText
, zaś ich napis (
Caption
)
zmodyfikuj tak, by opisywał to pole bazy danych, które identyfikuje. Na
przykład,
Caption
pierwszego komponentu
QRLabel
powinien być
Customer
, ponieważ listować będzie pole
CUSTOMER
.
13. Właściwość
Font
wszystkich czterech komponentów
QRLabel
zmień tak, by
obejmowała zarówno atrybut
fsBold
, jak i
fsUnderline
. Pomoże to
uwydatnić je w raporcie jako nagłówki kolumn.
14. W lewym górnym rogu raportu umieść
QRLabel
i jego
Caption
ustaw na
Report Date:
.
15. Bezpośrednio pod etykietą daty raportu dodaj nowy
QRLabel
i ustaw jego
Caption
na
Report Time:
.
16. Po prawej stronie etykiety z datą raportu umieść komponent
QRSysData
i jego właściwość
Data
ustaw na
qrsDate
. Komponent
QRSysData
jest
specjalnie przeznaczony do podawania wartości zmiennych z
poziomu
systemu, takich jak bieżący numer strony, tytuł raportu i temu podobnych.
Tutaj użyjemy go do wydrukowania bieżącej daty w obszarze tytułowym
raportu.
17. Drugi komponent
QRSysData
dodaj po prawej stronie etykiety czasu raportu.
Jego pole
Data
powinno mieć wartość domyślną
grsTime
. Spowoduje to
włączenie do nagłówka raportu godziny jego wydrukowania.
18. Moduł z kodem źródłowym nowego formularza zapisz jako
LISTREPO.PAS
.
Po wykonaniu wszystkich tych czynności listowy raport o
klientach jest
w zasadzie gotowy. Na rysunku 19.2 pokazano, jak powinien wyglądać ukończony
projekt.
Rozdział 19 Raporty
585
Można łatwo podejrzeć, jak nasz nowy raport wyglądać będzie w fazie wykonania
(run-time), klikając go prawym klawiszem myszy i wybierając
Preview
z menu
kontekstowego. Taki właśnie widok raportu pokazano na rysunku 19.3.
Szczęśliwe ukończenie raportu listowego przygotowało nas do przejścia do raportu
grupowego. Gdy zbudujemy już wszystkie raporty, zobaczymy też, jak
skonstruować aplikację służącą do ich uruchamiania. Teraz zamknijmy okno
podglądu raportu i powróćmy do edytora formularzy Delphi.
Grupowy raport o klientach
Pracując nad innymi raportami z tego rozdziału zaoszczędzimy sobie mnóstwo
czasu i wysiłku, jeśli nasz gotowy raport listowy zapamiętamy w składnicy
Rysunek 19.2.
Wygląd
ukończonego
listowego raportu
o klientach.
Rysunek 19.3.
Listowy raport
o klientach
pokazany tak, jak
będzie wyglądał
w fazie wykonania.
586
Część IV
obiektów i
przy tworzeniu wszystkich pozostałych skorzystamy z
cechy
dziedziczenia. Wypróbujmy wpierw, jak działa to w przypadku grupowego raportu
o klientach.
1. Prawym klawiszem myszy kliknij utworzony właśnie raport listowy i wybierz
opcję menu kontekstowego
Add To Repository
.
2. Nowej pozycji w składnicy nadaj jakiś znaczący tytuł, na przykład
Customer
Report
, a jako jej opis (
Description
) wpisz
Generic Customer
Report Class
. Nowy formularz przypisz do karty
Forms
składnicy, po
czym kliknij klawisz
OK
.
3. Kliknij opcję menu
File\New\Forms
, po czym z okna dialogowego
New Items
wybierz formularz
Customer Report
.
4. Zaznacz przycisk opcji
Inherit
i kliknij
OK
. W edytorze formularzy Delphi
powinien pojawić się nowy formularz
Customer Report
.
Zauważmy, że w naszym nowym formularzu raportu występują już wszystkie
nagłówki, specyfikacje zbiorów danych (DataSet) i
inne detale raportów
z oryginalnego raportu o klientach. Występujące w Delphi wizualne dziedziczenie
formularzy zaoszczędzi nam trudu pracowitego rekonstruowania wszystkich
podstawowych elementów w każdym z raportów oddzielnie.
Mając już nowy formularz na ekranie, możemy od razu przystąpić do takiego
modyfikowania jego formy, by uzyskać raport grupowy. Tak więc, by utworzyć
Grupowy raport o klientach, należy wykonać następujące czynności:
1. Zmień nazwę (
Name
) nowego formularza na
CustomerGroupReport
.
2. Właściwość
ReportTitle
komponentu
QuickReport
zmień na coś
innego, na przykład na
Customer Group Report
.
3. Umieść w raporcie komponent
TQRGroup
i jego właściwość
Master
ustaw
tak, by wskazywała na komponent
QuickRep
. Jego właściwość
Expression
ustaw na
MasterQuery.Country
.
4. Na komponencie
QRGroup
umieść komponent
QRExpr
i
ustaw jego
właściwość
Expression
na
MasterQuery.Country
.
5. Właściwość
Font
komponentu
QRExpr
zmodyfikuj tak, by obejmowała
atrybut
Bold
i zwiększ wielkość czcionki do 12 punktów, by wyróżniała się na
wydruku raportu.
6. Zamknij komponent
MasterQuery
, jeśli jest aktualnie otwarty (jego
właściwość
Active
ustaw na
False
).
Rozdział 19 Raporty
587
7. Wywołaj edytor właściwości
SQL
i dodaj na końcu istniejącej instrukcji
SELECT
klauzulę
ORDER BY Country
. Po zakończeniu redagowania
instrukcja
SELECT
powinna mieć postać:
SELECT * FROM CUSTOMER ORDER BY Country
.
8. Otwórz komponent
MasterQuery
zmieniając jego właściwość
Active
na
True
.
Nasz nowy raport jest już gotowy. Moduł źródłowy naszego formularza
zapisujemy na dysku jako
GRPREPO
.
PAS
, po czym klikamy go prawym
klawiszem myszy i wybieramy opcję
Preview
. Na rysunku 19.4 pokazano, co
powinniśmy wtedy zobaczyć.
Zauważmy, że lista klientów jest teraz zorganizowana według krajów. Ten sposób
grupowania znany jest jako podział na poziomie grupy (level break) lub
przerywanie sterowania (control break). Stosowanie w raportach grup pozwala
sumować dane szczegółowe raportu na każdym poziomie grupowania. Możemy na
przykład utworzyć stopkę grupy, która podawałaby całkowitą ilość klientów dla
każdego kraju. Grupy mogą zawierać zarówno nagłówki, jak i stopki, te zaś mogą
używać pól obliczanych, systemowych lub zwykłych.
Rysunek 19.4.
Widok grupowego
raportu o klientach
w podglądzie.
588
Część IV
UWAGA:
Zauważmy, że zmieniliśmy SQL towarzyszący raportowi tak, by zbiór jego
rezultatów był posortowany według kolumny
Country
. Jeśli mamy zamiar
grupować według określonego pola, to należy bezwzględnie zapewnić, by nasz
zbiór danych był właściwie posortowany. Gdyby tak nie było, to otrzymalibyśmy
wiele oddzielnych podziałów w różnych miejscach raportu. Jeśli nasz raport ma
kilka poziomów podziału, to dane należy koniecznie posortować według
wszystkich uczestniczących w nich pól, i to w takiej samej kolejności, jak
kolejność odpowiadających im grup w raporcie.
Raport typu „pozycja główna/szczegóły”
Kolejnym raportem, który zbudujemy, jest raport typu „pozycja główna/szczegóły”
(master/detail) o wartości sprzedaży w rozbiciu na poszczególnych klientów.
Wymienia on po kolei wszystkich klientów i dla każdego z nich podaje
odpowiadającą mu sumę sprzedaży. By sporządzić nowy raport typu pozycja
główna/szczegóły, należy wykonać następującą procedurę:
1. Utwórz nowego potomka oryginalnego raportu o
klientach korzystając
z sekwencji opcji
File\New\Forms\Customer
Report\Inherit\OK
.
2. Właściwości
Name
nowego formularza nadaj wartość
CustomerMDReport
.
3. Zmień właściwość
ReportTitle
komponentu
QuickRep
na
Sales by
Customer Report
.
4. Umieść na formularzu komponent
TDataSource
i
jego właściwość
DataSet
zmień tak, by odwoływała się do komponentu
MasterQuery
.
5. Umieść na formularzu komponent
TQuery
i ustaw jego właściwość
DatabaseName
na
IBLOCAL
. Do jego właściwości
SQL
wpisz następujący
kod:
SELECT * FROM SALES WHERE Cust_No = :Cust_No
ORDER BY Order_Date
6. Jego właściwość
DataSource
zmień tak, by wskazywała na komponent
TDataSource
, dodany wcześniej. Ustanowi to zależność typu pozycja
główna/szczegóły pomiędzy tablicą
CUSTOMER
(skojarzoną z komponentem
MasterQuery
) i naszym nowym zapytaniem do tablicy
SALES
.
7. Otwórz komponent
TQuery
, przełączając jego właściwość
Active
na
True
.
8. Umieść na raporcie komponent
TQRSubDetail
i jego właściwość
Master
ustaw tak, by odwoływała się do komponentu
TQuickRep
. Jego właściwość
DataSet
powinna odwoływać się do nowego
TQuery
.
Rozdział 19 Raporty
589
9. Na komponencie
TQRSubDetail
umieść trzy komponenty
TQRLabel
i trzy
komponenty
TQRDBText
. Właściwość
DataSet
wszystkich trzech
komponentów
TQRDBText
ustaw na dodany właśnie
TQuery
.
10. Właściwość
DataField
pierwszego komponentu
QRDBText
ustaw na
OrderDate
, drugiego na
Discount
, a trzeciego na
Total_Value
.
11. W polu
Mask
komponentu
QRDBText
dla dyskonta (Discount) wpisz
.#0
,
zaś w
polu
Mask
analogicznego komponentu dla Total_Value umieść
#,##0.00
. Właściwość
Mask
formatuje liczby pokazywane przez te
komponenty. W przypadku pola Discount są to wartości zmiennoprzecinkowe,
zatem przy pomocy maski ograniczamy ilość podawanych przez niego liczb
znaczących. W przypadku kolumny
Total_Value
mamy do czynienia
z dużymi sumami pieniędzy i tak też pole to formatujemy.
UWAGA:
By nasza aplikacja mogła poprawnie manipulować numerycznymi i dziesiętnymi
polami InterBase’u (takimi jak kolumna
Total_Value
), będziemy musieli
właściwość
Enable BCD
aliasu IBLOCAL ustawić na
True
. Jeśli tego nie
zrobimy, to BDE i w konsekwencji Delphi będą pola tych typów traktować jako
wartości całkowite. Wskutek tego nasze raporty nie będą w stanie wyświetlać
części ułamkowych, zaś komponenty edycyjne, takie jak DBEdit, będą odrzucać
części ułamkowe we wprowadzanych danych. Więcej informacji na ten temat
znajdziemy w rozdziale 17, „Delphi a InterBase”.
12. Komponenty
QRLabel
umieść ponad każdym z komponentów
QRDBText
w taki sposób, by stały się one nagłówkami dla ich kolumn. Właściwość
Caption
każdego
QRLabel
zmień tak, by odpowiadała kolumnie, którą
określa.
13. Właściwość
Font
każdego z
trzech komponentów
QRLabel
w
sekcji
QRSubDetail
zmodyfikuj tak, by zawierała atrybut
Bold
Italic
, co
pozwoli lepiej uwidocznić etykiety na raporcie.
W tym momencie nasz nowy raport jest zasadniczo ukończony. Rysunek 19.5
pokazuje, jak powinien on wyglądać w edytorze formularzy Delphi.
590
Część IV
Teraz moduł naszego formularza zapisujemy jako
MDREPO.PAS
, następnie zaś
sam raport klikamy prawym klawiszem myszy i, aby go „podglądnąć”, wybieramy
opcję
Preview
. Rysunek 19.6 ilustruje rzeczywisty wygląd naszego nowego raportu
pozycja główna/szczegóły.
WSKAZÓWKA:
Pisząc ten rozdział odkryłem coś, co wygląda na błąd (bug) w komponentach
QuickReport
Delphi. Gdy próbowałem po raz pierwszy uruchomić raport typu
pozycja główna/szczegóły, otrzymałem komunikat Access Violation. Wspominał
on coś o pustym wskaźniku. Po powrocie z edytora formularzy spostrzegłem, że
szerokość komponentu
TQRDBText
dla pola
Order_Date
zwężyła się do zera
pikseli. Wszelkie próby poszerzenia go zawiodły, z pewnością więc problem
dotyczył tego właśnie pola. Skasowałem i ponownie dodałem odpowiedni
komponent, nadal bez skutku. W końcu zdecydowałem się bezpośrednio dodawać
komponenty
TField
do pól zapytania, klikając dwukrotnie
TQuery
dla
SALES
i naciskając CTRL+A. I to z jakiś powodów rozwiązało problem. Jeśli Czytelnik
popadnie kiedyś w podobne kłopoty, to podana tu wskazówka zaoszczędzi mu
może nieco frustracji.
Jeśli w komponentach
QuickReport
natkniemy się na nasze „własne” błędy, nie
zawadzi złożyć wizyty na poświeconej QuickReport stronie WWW pod
http://
www.qusoft.com
. Prócz tego Delphi posiada plik o nazwie
QRPT2MAN.DOC
(znajdujący się w katalogu
\Program Files\Delphi 3\Quickrpt
),
zawierający dodatkowe informacje na temat stosowania komponentów
QuickReport w aplikacjach Delphi.
Rysunek 19.5.
Nasz raport typu
pozycja
główna/szczegóły
w edytorze
formularzy Delphi.
Rozdział 19 Raporty
591
Nasz raport pozycja/szczegół jest teraz w pełni ukończony. Zamykamy okno
podglądu i powracamy do edytora formularzy Delphi.
Raport krzyżowy
Ze wszystkich rodzajów raportów, omawianych w tym rozdziale, raport krzyżowy
- który właśnie zamierzamy skonstruować - jest najbardziej niekonwencjonalny.
Wynika to stad, że nie korzysta on z komponentów
QuickReport
, a zamiast
nich używa komponentów typu
DecisionCube
. Oznacza to w praktyce, że dla
wydrukowania raportu nie wywołujemy metody drukowania związanej z danym
komponentem, tak jak to zrobilibyśmy w
przypadku komponentów
QuickReport
- tutaj konieczne będzie wywołanie metody drukowania
formularza.
Aby skonstruować nasz raport krzyżowy, wykonajmy po kolei poniższe kroki:
Zainicjuj nowy formularz i
zmień jego nazwę (
Name) na
Customer CTReport
.
1. Na nowym formularzu umieść trzy komponenty
TPanel
. Właściwość
Align
pierwszego ustaw na
alTop
, drugiego na
alBottom
, zaś trzeciego na
alClient
.
2. Wykasuj właściwość
Caption
drugiego i trzeciego panelu.
3. Nagłówek, czyli
Caption
, pierwszego
TPanel
ustaw na
Sales by
Country and Year
, a jego czcionkę (
Font
) na
Arial
18 punktowy
normalny.
Rysunek 19.6.
Nasz nowy raport
pozycja
główna/szczegóły
w całej swej
okazałości.
592
Część IV
4. Na formularzu umieść komponent
DecisionQuery
(znajdujący się w karcie
DecisionCube
palety komponentów) i jego właściwości
DatabaseName
nadaj wartość
IBLOCAL
.
5. W podobny sposób umieść na formularzu komponent
DecisionCube
i jego
atrybut
DataSet
ustaw na dodany właśnie komponent
DecisionQuery
.
6. Dodaj do formularza komponent
DecisionSource
i ustaw jego właściwość
DecisionCube
na zainstalowany w
poprzednim kroku komponent
DecisionCube
.
7. Komponent
DecisionGrid
umieść na trzecim panelu
TPanel
(który
powinien zajmować większą część środkowej części formularza) i ustaw jego
właściwość
Align
na
alClient
.
8. Właściwość
DecisionSource
komponentu
DecisionGrid
ustaw na
dodany właśnie
DecisionSource
.
9. Na drugim panelu
TPanel
(który powinien znajdować się u dołu formularza)
umieść komponent
DecisionPivot
i nadaj jego właściwości
Align
wartość
alClient
.
10. Właściwość
DecisionSource
komponentu
DecisionPivot
ustaw na
znajdujący się na formularzu komponent
DecisionSource
.
11. Komponent
DecisionQuery
kliknij prawym klawiszem myszy i z menu
kontekstowego wybierz edytor zapytań decyzyjnych, czyli Decision Query
Editor.
12. W wyświetlonym oknie dialogowym wybierz przycisk
Query Builder
.
13. Po wyświetleniu przez wizualny edytor zapytań (Visual Query Builder) okna
dialogowego
Add
Table
, dodaj do zapytania tablice CUSTOMER i SALES,
klikając je dwukrotnie, po czym kliknij
Close
.
14. Pole
Cust_No
przeciągnij z tablicy CUSTOMER do kolumny
Cust_No
w tablicy SALES
,
ustanawiając miedzy nimi złączenie.
15. Przeciągnij kolumnę
Country
z tablicy CUSTOMER do dolnego panelu
w wizualnym edytorze zapytań, dodając go tym samym do tworzonego raportu.
16. Prócz tego do zapytania kolumny dodaj
Order_Date
i
Total_Value
z tablicy SALES, przeciągając je do dolnego panelu edytora zapytań, po czym
zakończ edycję klikając przycisk
OK
(ten z obrazkiem znaczka „
√
”).
17. Powróciwszy do edytora zapytań decyzyjnych, przeciągnij kolumny
Country
i
Order_Date
do pola listy
Dimensions
. Kolumnę
Total_Value
przeciągnij zaś do pola listy
Summaries
. Na zapytanie o rodzaj agregacji
należy odpowiedzieć wybraniem opcji
sum
. Jej wybranie sprawi, że kolumna
Rozdział 19 Raporty
593
Total_Value
będzie sumowana. Po zakończeniu redagowania pole
Query
Text
na karcie SQL Query powinno wyświetlać następujący kod:
SELECT CUSTOMER.COUNTRY, SALES.ORDER_DATE,
➥
SUM( SALES.TOTAL_VALUE )
FROM CUSTOMER CUSTOMER
INNER JOIN SALES SALES
ON (CUSTOMER.CUST_NO = SALES.CUST_NO)
GROUP BY CUSTOMER.COUNTRY, SALES.ORDER_DATE
Jeśli tak nie jest, to oznacza to, że coś nie wyszło albo w samym oknie
dialogowym
Decision
Query
Editor
, albo w wizualnym edytorze zapytań.
18. Jeśli kod SQL jest poprawny, kliknij przycisk
OK
aby zamknąć edytor zapytań
decyzyjnych.
19. Prawym klawiszem myszy kliknij komponent
DecisionCube
i wybierz
opcję edytora kostki decyzyjnej, czyli
Decision
Cube
Editor
. Zaznacz kolumnę
SUM i jej właściwość
Format
ustaw na
#,##0.00
. Zapewni to, że sumy dla
kolumny
Total_Value
wyświetlane będą poprawnie. Wyjdź z
okna
dialogowego klikając
OK
.
20. Właściwość
Active
komponentu
DecisionQuery
przestaw na
True
.
Powinieneś wtedy ujrzeć, jak twoja siatka decyzyjna (
DecisionGrid
)
wypełnia się danymi. Jeśli zostaniesz poproszony o
podanie nazwy
użytkownika i hasła, to tak jak poprzednio wpisz poprawne wartości. Rysunek
19.7 pokazuje, jak wszystko to powinno w rzeczywistości wyglądać.
21. Na zakończenie zapisz moduł swego formularza jako CTREPO.PAS
.
Rysunek 19.7.
Nasz raport
krzyżowy tak, jak
wygląda w trakcie
projektowania.
594
Część IV
Konstruowanie aplikacji dla raportów
By właściwie ocenić skuteczność, z jaką komponent
DecisionCube
tworzy
raporty krzyżowe, trzeba zobaczyć go w „akcji”, czyli w fazie wykonania. Tu
jednak wyłania się kolejne zagadnienie, związane z
pisaniem raportów -
konstruowanie aplikacji użytkownika dla raportów.
Wydruki 19.1 do 19.3 pokazują kod źródłowy programu REPORTS. Aplikacja ta
korzysta ze wszystkich raportów, które zbudowaliśmy w tym rozdziale (dla
zwięzłości ich kodu tutaj nie zamieszczono), i pozwala wyświetlać je na
podglądzie i uruchamiać z jednego centralnego formularza.
UWAGA:
By zbudować aplikację REPORTS, należy albo wpisać ręcznie jej kod źródłowy
wylistowany tutaj, albo też załadować go z płyty CD-ROM-u dołączonej do
książki.
Listing 19.1 Kod źródłowy Reports.DPR
-plik projektu Reports
program reports;
uses
Forms,
repo00 in 'repo00.pas' {Form1},
listrepo in 'listrepo.pas' {CustomerReport},
grprepo in 'grprepo.pas' {CustomerGroupReport},
mdrepo in 'mdrepo.pas' {CustomerMDReport},
ctrepo in 'ctrepo.pas' {CustomerCTReport},
salegrap in 'salegrap.pas' {CustomerGraphReport};
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TForm1,
Form1);
Application.CreateForm(TCustomerReport,
➥
CustomerReport);
Application.CreateForm(TCustomerGroupReport,
➥
CustomerGroupReport);
Application.CreateForm(TCustomerMDReport,
➥
CustomerMDReport);
Application.CreateForm(TCustomerCTReport,
➥
CustomerCTReport);
Application.CreateForm(TCustomerGraphReport,
➥
CustomerGraphReport);
Application.Run;
Rozdział 19 Raporty
595
end.
Listing 19.2 Kod źródłowy repo00.PAS
-
formularz główny
aplikacji
unit repo00;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
➥
Controls, Forms, Dialogs,StdCtrls;QuickRpt
type
TForm1 = class(TForm)
btPreview:
TButton;
btPrint:
TButton;
ListBox1:
TListBox;
btCustomerCTPreview:
TButton;
btCustomerCTPrint:
TButton;
Label1:
TLabel;
Label2:
TLabel;
procedure btPreviewClick(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure btPrintClick( Sender: TObject);
procedure btCustomerCTPreviewClick(Sender: TObject);
procedure btCustomerCTPrintClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1:
TForm1;
implementation
uses listrepo, ctrepo;
{$R *.DFM}
procedure TForm1.btPreviewClick(Sender: TObject);
var
c,f : Integer;
begin
if (ListBox1.ItemIndex<>-1) then
With Screen do
For f:=0 to FormCount - 1 do
With
Forms[f]
do
For c:=0 to ComponentCount - do
596
Część IV
if(Components[c]
is
TQuickRep)
and
(TQuickRep(Components[c]).ReportTitle
➥
=ListBox1. Items[ListBox1.ItemIndex])
then
begin
TQuickRep(Components[c]).Preview;
break;
end;
end;
procedure TForm1.FormShow(Sender: TObject);
var
c,f : Integer;
begin
With Screen do
For f:=0 to FormCount - 1 do
With Forms[f] do
For c:0 to ComponentCount - 1 do
if Components[c] is TQuickRep then
ListBox1.Items.Add(TQuickRep
➥
(Components[c]).ReportTitle);
If (ListBox1.Items.Count <> 0) then ListBox1.ItemIndex:=0;
end;
procedure TForm1.btPrintClick(Sender: TObject);
var
c,f : Integer;
begin
if (ListBox1.ItemIndex<>-1) then
With Screen do
For f:=0 to FormCount - 1 do
With
Forms[f]
do
For c:=0 to ComponentCount - 1 do
if
(Components[c]
is
TQuickRep)
and
(TQuickRep(Components[c].ReportTitle
➥
=ListBox1.Items[ListBox1.ItemIndex]
then
begin
TQuickRep(Components[c]).Print;
break;
end;
end;
procedure TForm1.btCustomerCTPreviewClick(Sender: TObject);
begin
CustomerCTReport.Show;
end;
procedure TForm1.btCustomerCTPrintClick(Sender: TObject);
begin
CustomerCTReport.Print;
end;
Rozdział 19 Raporty
597
end.
Listing 19.3 Kod źródłowy formularza związanego z
formularzem
głównym aplikacji
object Form1: TForm1
Left=200
Top=109
Width=544
Height=375
Caption=’Reports’
Font.Charset=DEFAULT_CHARSET
Font.Color=clWindowText
Font.Height=-11
Font.Name=’MS Sans Serif’
Font.Style=[]
OnShow=FormShow
PixelsPerInch=96
TextHeight=13
objectLabel1:
TLabel
Left=0
Top=40
Width=79
Height=13
Caption=’Sales by Country’
end
objectLabel2:
TLabel
Left=0
Top=120
Width=34
Height=13
Caption=’Others:’
end
object btPreview: TButton
Left=0
Top=288
Width=75
Height=25
Caption=’Pre&view’
TabOrder=0
OnClick=btPreviewClick
end
object btPrint: TButton
Left=96
Top=288
Width=75
Height=25
Caption=’&Print’
TabOrder=1
OnClick=btPrintClick
end
598
Część IV
object ListBox1: TListBox
Left=0
Top=136
Width=169
Height=137
ItemHeight=13
TabOrder=2
end
object btCustomerCTPreview: TButton
Left=0
Top=56
Width=75
Height=25
Caption=’Preview’
TabOrder=3
OnClick=btCustomerCTPreviewClick
end
object btCustomerCTPrint: TButton
Left=88
Top=56
Width=75
Height=25
Caption=’&Print’
TabOrder=4
OnClick=btCustomerCTPrintClick
end
end
Na rysunku 19.8 pokazano, jak nowa aplikacja wygląda w fazie wykonania.
W aplikacji REPORTS zastosowano kilka technik, które być może Czytelnik
zechce wykorzystać we własnym zakresie. Po pierwsze, rzućmy okiem na kod
związany ze zdarzeniem
OnShow
formularza głównego:
procedure TForm1.FormShow(Sender: TObject);
var
Rysunek 19.8.
Aplikacja
REPORTS w całej
swej okazałości
Rozdział 19 Raporty
599
c, f : Integer;
begin
With Screen do
For f := 0 to FormCount - 1 do
With Forms[f] do
For c := 0 to ComponentCount - 1 do
if Components[c] is TQuickRep then
➥
ListBox1.Items.Add(TQuickRep
➥
(Components[c]).ReportTitle);
If (ListBox1.Items.Count <> 0) then ListBox1.ItemIndex :=
➥
0;
end;
Zacytowany fragment kodu korzysta z predefiniowanej zmiennej
Screen
po to,
by przejrzeć przez wszystkie istniejące formularze i określić, który z nich zawiera
komponenty
TQuickRep
. Te formularze, które komponenty
TQuickRep
zawierają, dodawane są do listy kontrolki
ListBox1
formularza. Pamiętamy
oczywiście, że
TCustomerReport
zawiera komponent
TQuickRep
i stanowi
podstawę dla wszystkich innych formularzy raportów w tym rozdziale.
Po zbudowaniu listy skontrolowana zostaje liczba jej pozycji (
count
). Ma to na
celu sprawdzenie, czy w ogóle odnaleziono jakieś komponenty
TQuickRep
. Jeśli
komponenty takie istnieją (
Count <> 0
), to w
ListBox
wyróżniona zostaje
pierwsza pozycja listy.
Interesującym aspektem omawianego kodu jest to, że przy tworzeniu dynamicznej
listy dostępnych raportów korzysta on informacji o typach fazy wykonania (RTTI,
Runtime Type Information). Jeśli tylko nasze raporty budujemy jako formularze
zawierające komponenty
QuickRep
, możemy postąpić identycznie w naszych
własnych aplikacjach.
Teraz przyjrzyjmy się kodowi obsługującemu zdarzenie
OnClick
przycisku
Preview
:
procedure TForm1.btPreviewClick(Sender: TObject);
var
c, f : Integer;
begin
if (ListBox1.ItemIndex <> -1) then
With Screen do
For i := 0 to FormCount - 1
With
Forms[f]
do
For c := 0 to ComponentCount - 1 do
if (Components[c] is TQuickRep) and
(TQuickRep(Components[c]).ReportTitle
=
➥
ListBox1.Items[ListBox1.ItemIndex])
then
begin
TQuickRep(Components[c].Preview;
600
Część IV
break;
end;
end;
Także i
tutaj kod przebiega w
pętli poprzez listę dostępnych formularzy
i komponentów, poszukując odpowiednika raportu aktualnie wybranego
w komponencie
ListBox
. Jeśli znajdzie odpowiadający mu komponent,
przekształca jego typ na formularz TQuickRep i wywołuje metodę
Preview
.
Podobnie jak poprzednio pozwala to wykonywać raporty dynamicznie. Pozwala
także na dodawanie raportów do aplikacji i sprawia, że możliwe jest ich
natychmiastowe wykonanie bez żadnego dodatkowego kodowania. Kluczem do
wszystkiego jest przeglądanie list formularzy i komponentów z pomocą RTTI.
Wygląd raportów w fazie wykonania
Ponieważ dla naszych raportów dysponujemy już całkiem niezłą aplikacją,
możemy przyjrzeć się dokładniej raportowi krzyżowemu. Kliknijmy przycisk
Preview
umieszczony poniżej etykiety
Sales
by
Country
. Na rysunku 19.9
pokazano, co powinniśmy zobaczyć:
Teraz mamy okazje przekonać się, jak potężnym narzędziem jest komponent
DecisionCube
. W tym celu spróbujemy wykonać następujące czynności:
1. Przeciągnijmy nagłówek kolumny
Order_Date
do nagłówka kolumny
Country
. Zobaczymy, że odpowiadające im rzędy i kolumny zamieniają się
Rysunek 19.9.
Nasz raport
krzyżowy w fazie
wykonania.
Rozdział 19 Raporty
601
miejscami w siatce. Nosi to nazwę „rotacji” (pivoting) i jest jednym z głównych
powodów, dla których ludzie używają kostek decyzyjnych (zwanych także
tablicami rotacyjnymi (pivot tables)).
2. Zwolnijmy przycisk
Order_Date
lub
Customer
na komponencie
DecisionPivot
u dołu formularza. Przyciski te wskazują, które wymiary
występują w kostce. Domyślnie uwzględnione są wszystkie, lecz można to
zmienić w fazie wykonania używając kontrolki
DecisionPivot
.
3. Kliknij prawym klawiszem myszy przycisk
Order_Date
lub
Customer
i z menu
kontekstowego wybierz opcję
Drilled
In
. Następnie ten sam przycisk kliknij
lewym klawiszem myszy, wyświetlając listę możliwych wartości głębszego
poziomu (drill-in). Można na przykład zagłębić się w kolumnę
Order_Data
i ograniczyć wyświetlane dane do konkretnego roku. Można również zagłębić
się w kolumnę
Country
i przeglądnąć dane dla poszczególnych krajów.
Wykresy
Delphi nie tylko pozwala budować wyszukane raporty, lecz i dostarcza narzędzi
graficznych do kreślenia wykresów i
diagramów. Komponenty
DBChart
,
QRChart
i
DecisionGraph
przeznaczone są specjalnie do konstruowania
wykresów skojarzonych z danymi (data-aware). Pokażemy teraz, jak komponenty
te można wykorzystać w budowie skomplikowanych diagramów ekonomiczno-
finansowych.
Kreślenie za pomocą QRChart
Zacznijmy od zbudowania prostego diagramu z pomocą należącego do zestawu
QuickReport
komponentu
QRChart
. Dlatego właśnie, że jest on
komponentem związanym z
QuickReport
, można dodawać go do raportów oraz
podglądać i drukować z użyciem wbudowanych w
TQuickRep
metod
Preview
i
. By utworzyć raport z diagramem, trzeba wykonać następujące kroki:
1. Z utworzonego wcześniej formularza CustomerReport sporządź nowy
formularz potomny i nazwij go
CustomerGraphReport
.
2. Kliknij komponent
TQuickRep
i jego właściwości
ReportTitle
nadaj
wartość
Graph of Sales by Country
.
3. Wybierz wszystkie cztery komponenty
QRLabel
z sekcji nagłówków kolumn
raportu i skasuj zawartość ich atrybutu
Caption
. Wizualnie będzie to miało
efekt równoważny do usunięcia ich z raportu. Nie możemy ich zwyczajnie
usunąć, ponieważ zostały one odziedziczone z innego formularza.
602
Część IV
4. W sekcji szczegółów raportu wybierz wszystkie cztery komponenty
QRDBText
, wyzeruj ich właściwość
DataField
, a właściwość
Width
ustaw na
0
. Będzie to wyglądać tak, jakby zostały usunięte z raportu.
5. Wybierz jednocześnie sekcje stopki strony, szczegółów i nagłówków kolumn,
po czym ustaw ich właściwość
Enabled
na
False
. Ich właściwość
Height
zmień natomiast na
0
. Wizualnie będzie to wyglądać na usuniecie ich
z raportu.
6. Powiększ sekcję tytułu tak, by zajmowała przynajmniej połowę formularza.
Tutaj właśnie zostanie umieszczony wykres, najlepiej więc zapewnić sobie
dużo wolnego miejsca.
7. Umieść w obszarze tytułowym komponent
QRChart
i tak zwiększ jego
wymiary, by zajął jak największą część obszaru tytułu.
8. Zamknij komponent MasterQuery, a jego SQL zmień następująco:
SELECT C.COUNTRY, SUM(S.TOTAL_VALUE)
FROM SALES S,
CUSTOMER C
WHERE S.CUST_NO=C.CUST_NO
GROUP BY C.COUNTRY
9. Teraz otwórz go ponownie ustawiając
Active
z powrotem na
True
.
Zapytanie to zwraca listę krajów z tabeli CUSTOMER wraz z całkowitą
wartością sprzedaży do każdego z nich, pobraną z tabeli SALES. Nasz nowy
wykres bazować będzie na tym właśnie zapytaniu.
10. Dwukrotnie kliknij komponent
QRChart
, po czym na karcie
Chart
\
Series
kliknij przycisk
Add
.
11. Jako typ diagramu wybierz
Pie
i kliknij
OK
.
12. Na karcie
Chart
kliknij fiszkę
Titles
i jako tytuł diagramu wpisz
Country
Sales Figures
.
13. Kliknij fiszkę
Series
(tę na prawo od zakładki
Chart
), a następnie na karcie
Series
kliknij fiszkę karty
DataSource
.
14. Na karcie
DataSource
wybierz
DataSet
jako typ źródła danych dla diagramu.
Kliknij listę rozwijaną
DataSet
i jako zbiór danych (dataset) dla diagramu
wybierz twój komponent
MasterQuery
.
15. Zmień właściwość
Labels
tak, by odnosiła się do kolumny
Country
zapytania; zmodyfikuj też właściwość
Pie
, która ma wskazywać na kolumnę
SUM. Spowoduje to, że rozmiary segmentów koła bazować będą na kolumnie
SUM, natomiast ilość segmentów zależeć będzie od kolumny
Country
.
Każdy segment reprezentować będzie wartość sprzedaży do innego kraju.
Rozdział 19 Raporty
603
W tym momencie nasz prosty diagram kołowy jest już prawie ukończony. Na
zakończenie klikamy przycisk
Close
. Rysunek 19.10 przedstawia nasz nowy
wykres.
Ponieważ wykres jest już gotowy, moduł jego kodu źródłowego zapamiętujemy
jako
SALEGRAP.PAS
. Następnie uruchamiamy aplikację
REPORTS
naciskając
klawisz F9. Gdy aplikacja pojawi się na ekranie, nasz raport z diagramem
wybieramy z listy raportów i klikamy przycisk
Preview
. Na rysunku 19.11
pokazano, co powinniśmy ujrzeć.
Gdy tylko skończymy podziwiać dziewiczą urodę naszego dzieła, zamykamy
zarówno okno podglądu, jak i samą aplikację, i powracamy do Delphi.
Rysunek 19.10.
Nowy wykres
w formie, w jakiej
wyświetlany jest
w edytorze
formularzy Delphi.
Rysunek 19.11.
Nasz wykres
w formie, w jakiej
widać go na
podglądzie
w aplikacji
RAPORTS.
604
Część IV
Kreślenie przy pomocy DecisionGraph
Komponentu
DecisionGraph
możemy użyć do zobrazowania sum krzyżowych
generowanych przez komponent
DecisionCube
. My zaś
DecisionGraph
dodamy do formularza
CustomerCTReport
, skonstruowanego wcześniej.
Skorzystamy przy tym z komponentów
DecisionCube
istniejących już na
formularzu.
By do formularza
CustomerCTReport
dodać nowy wykres tego rodzaju,
należy wykonać następujące czynności:
1. Naciśnij SHIFT+F12 i załaduj
CustomerCTReport
, wybierając go z listy
formularzy.
2. Właściwość
Align
komponentu
DecisionGrid
zmień na
alTop
i podnieś
jego dolną krawędź tak, by zajmował on jedynie około połowy wolnego
miejsca w środkowej części formularza.
3. W niezajętej strefie formularza, zwolnionej właśnie przez komponent
DecisionGrid
, umieść komponent
DecisionGraph
i
ustaw jego
właściwość
Align
na
alClient
. Powinien wówczas zająć resztę wolnego
miejsca na formularzu.
4. Jego
DecisionSource
ustaw na znajdujący się na formularzu komponent
DecisionSource
. Bezpośrednio po tym powinien wyświetlić się nowy
wykres słupkowy.
5. Zmodyfikuj nowy wykres, klikając go dwukrotnie, a następnie w edytorze
klikając fiszkę
Titles
. Tytuł (
Title
) wykresu zmień na
Sales by
Country and Year
, po czym kliknij
Close
.
Nasz nowy wykres jest już gotowy. Jako etykiet dla osi X i słupków używa on krawędzi
swego komponentu
DecisionCube
. Pola SUM kostki używa jako osi Y.
Zobaczmy teraz, jak nowy wykres wygląda w fazie wykonania. Zapiszmy naszą
pracę, uruchommy aplikację REPORTS i kliknijmy przycisk
Preview
dla raportu
Sales by Country. Rysunek 19.12 pokazuje, co powinniśmy wtedy zobaczyć.
Rozdział 19 Raporty
605
By przekonać się, jak ściśle powiązane są ze sobą nasze komponenty
DecisionGrid
i
DecisionGraph
, przeciągnijmy w
komponencie
DecisionPivot
kolumnę
Order_Date
ponad kolumną
Country
i upuśćmy
ją z jej prawej strony. Będzie to miało taki skutek, że kolumny zamienia się
miejscami (na co wskazuje kursor myszy), co z kolei „przekręci” tabelę.
Zauważmy, że gdy obracamy
DecisionCube
, zmianie ulega zarówno tabela, jak
i wykres - tak jak to pokazano na rysunku 19.13.
Nasz nowy wykres jest teraz w pełni ukończony. Wychodzimy z aplikacji
REPORTS
i powracamy do Delphi.
Wzbogacanie raportów
Ponieważ pamiętaliśmy, by możliwie wiele naszych raportów oprzeć na wspólnym
„przodku”, wzbogacenie ich en masse jest już tylko drobnostką. Wykorzystując
Rysunek 19.12.
Nasz nowy
DecisionGraph
w fazie wykonania.
Rysunek 19.13.
Komponent
DecisionPivot
może obracać
zarówno
DecisionGrid
,
jak
i
DecisionGraph
.
606
Część IV
okoliczność, że wszystkie one wywodzą się z klasy
CustomerReport
, do
obszaru tytułowego każdego raportu dodamy systemową nazwę użytkownika
osoby, generującej raport.
Aby na naszych raportach umieścić pole z nazwą użytkownika, trzeba wykonać
następujące kroki:
1. Załaduj
CustomerReport
z powrotem do edytora formularzy Delphi.
2. Dwa komponenty
QRLabel
umieść po prawej stronie obszaru tytułowego
w
jednej linii z
datą wydruku raportu. Pierwszy z
nich nazwij
UserNameLabel
, drugi
laUserName
. Ponieważ komponenty te zostaną
odziedziczone przez inne formularze, muszą mieć unikalne nazwy wśród
wszystkich formularzy hierarchii. Zakładanie, że domyślne nazwy nadawane
przez Delphi na jednym formularzu nie będą w konflikcie z takimiż nazwami na
innych, nie jest bynajmniej bezpieczne.
3. Kontrolki te umieść obok siebie, przy czym prawa kontrolka powinna kończyć
się na prawym marginesie raportu.
4. Ustaw
Caption
etykiety
UserNameLabel
na
User Name:
. Właściwość
Alignment
etykiety
laUserName
ustaw na
taRightJustify
.
5.
Dwukrotnie kliknij zdarzenie
OnPrint
komponentu
laUserName
i w następujący sposób zmodyfikuj procedurę obsługi zdarzenia:
procedurę TListReport.laUserNamePrint(sender: TObject; var
Value: String);
var
MaxNameLen : Integer;
begin
MaxNameLen := 30;
SetLength(Value,
MaxNameLen);
GetUserName(PChar(Value),
MaxNameLen);
SetLength(Value,
Pred(MaxNameLen));
//Nazwa powraca jako łańcuch zakończony zerem
end;
Podprogram ten, by nadać wartość przekazanemu mu parametrowi
Value
,
wywołuje procedurę API Windowsa o nazwie
GetUserName
. To, co zostanie
umieszczone w
Value
, zostanie wydrukowane w raporcie. Należy zwrócić uwagę
na konwersję typu (typecasting), konieczną dla przekazania
Value
do procedury
jako łańcucha w stylu C. Po powrocie z
GetUserName
zmienna
MaxNameLen
(w której
GetUserName
umieścił ilość bajtów przekopiowanych do
Value
)
zostaje użyta do nastawienia długości
Value
przed powrotem z kodu obsługi
zdarzenia. Jest to w istocie środek zabezpieczający służący zapewnieniu, że
łańcuch będzie poprawnie zakończony.
Rozdział 19 Raporty
607
By ujrzeć efekt naszych modyfikacji, uruchamiamy ponownie aplikację
REPORTS
i wchodzimy do podglądu niektórych z raportów, opartych na
Customer
Report
,. Zobaczymy, że z wyjątkiem raportu krzyżowego, każdy z nich ma teraz
pole użytkownika w prawym górnym rogu. Choć dodaliśmy je tylko jeden raz
w jednym miejscu, to zmiana ta przeniosła się na wszystkie formularze potomne.
Wyobraźmy sobie, ile pracy można by zaoszczędzić, gdyby wszyscy programiści
aplikacji Delphi projektowali systemy w pełni korzystające z tej niezwykle
efektywnej właściwości. Na rysunku 19.14 widzimy nasz Raport listowy
o klientach z dodanym nowym polem.
Rysunek 19.14.
Dzięki
dziedziczeniu
formularzy można
daleko sięgających
zmian dokonać
w przysłowiową
sekundę.