20 Rozdziae 19id 21444 Nieznany

background image

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.

background image

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.

background image

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

.

background image

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.

background image

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.

background image

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

).

background image

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.

background image

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

.

background image

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.

background image

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.

background image

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

Print

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.

background image

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

background image

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.

background image

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;

background image

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

background image

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;

background image

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

background image

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

background image

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;

background image

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.

background image

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

Print

. 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.

background image

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.

background image

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.

background image

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ć.

background image

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

.

background image

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.

background image

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ę.


Wyszukiwarka

Podobne podstrony:
20 rozdzial 19 lokja3dicdpmiyri Nieznany (2)
20 rozdzial 19 o4hmogawvoqxcbc6 Nieznany
20 Stosowanie zasad projektowan Nieznany (2)
05 rozdzial 04 nzig3du5fdy5tkt5 Nieznany (2)
28 rozdzial 27 vmxgkzibmm3xcof4 Nieznany (2)
22 Rozdzial 21 KP4Q5YBIEV5DBSVC Nieznany (2)
09 08 Rozdzielnice budowlane RB Nieznany (2)
17 rozdzial 16 fq3zy7m2bu2oan6t Nieznany (2)
Kanicki Systemy Rozdzial 10 id Nieznany
29 rozdzial 28 ciw47mwstcqakqpq Nieznany
24 rozdzial 23 wjv3mksbkbdm37qy Nieznany
29 rozdzial 28 w3cbrpnwzh762znr Nieznany (2)
13 Rozdziae 12id 14782 Nieznany (2)
16 rozdzial 15 EJCDLTJY3F3I2FKL Nieznany (2)
14 rozdzial 13 w2pa42u4da5r3dcm Nieznany (2)
16 rozdzial 15 zpgg3d2etikxyjv3 Nieznany
20 Rownanie Schrodingeraid 2144 Nieznany
02 rozdzial 01 t4p4wqyl4oclhuae Nieznany (2)

więcej podobnych podstron