Andrzej Purczyński

Tworzenie baz danych w środowisku Delphi z wykorzystaniem komponentu TTable

  1. Wprowadzenie

    Borland Delphi (Win32) jest 32-bitowym, obiektowym i wizualnym środowiskiem do tworzenia aplikacji w systemie Windows. Delphi zastępuje wcześniejsze wersje kompilatorów Turbo Pascala firmy Borland wraz z bibliotekami Turbo Vision i Objects Windows Library.

    Nazwę Delphi zaproponował Danny Thorpe podczas zwołanej z tej okazji burzy mózgów (ang. brainstorming). Porównał on niezwykłe zalety powstałego produktu w zakresie tworzenia baz danych do autorytetu delfickiej wyroczni wróżki Pytii. Nazwa przyjęła się i na rynku pojawiła się już n-ta wersja tego środowiska. Początkowo wersje oznaczano kolejnymi liczbami a obecnie liczą roku.

    W swoim czasie odmiana Pascala - Turbo Pascal, która powstała pod kierunkiem Andersa Hejlsberga w firmie Borland, znacznie ułatwiła tworzenie i naukę programowania. Pascal stał się "językiem akademickim" o przejrzystej budowie strukturalnej. Jego najlepsze cechy, ale już w ujęciu obiektowym przejęło środowisko Delphi (ang. Object Pascal).

    Delphi należy do bardzo wydajnych środowisk programowania określanych skrótem RAD (ang. Rapid Application Development), a jego niewątpliwą zaletą jest łatwość tworzenia aplikacji do obsługi różnego rodzaju banków danych.


  2. Dostęp do plików bazy danych na dysku

    Wykorzystanie komponentu TTable jest najprostszym sposobem utworzenia aplikacji, choć uzyskana baza danych ma więcej ograniczeń niż np. baza utworzona za pomocą komponentu TQuery (rysunek 2).

    W ramach środowiska Delphi znajdują się aplikacje wspomagające konfigurację środowiska, w którym baza ma pracować i tworzenie oraz łączenie poszczególnych jej elementów.

    Rys. 1. Rola aliasu

    Baza danych utworzona w Delphi oprócz plików zawierających samą aplikację i dane, wymaga zainstalowania w systemie oprogramowania BDE (ang. Borland Database Engine), które zawiera zbiór funkcji obsługi baz danych i zwalnia z konieczności pisania kodu procedur, takich jak np. procedury otwierania plików, odbierania i wprowadzania danych, zamykania plików itp. Moduły BDE są dostępne dla wszystkich aplikacji baz danych w Delphi.

    BDE jest realizacją standardu IDAPI (ang. Integrated Database Application Program Interface) opracowanego na początku lat 90 przez konsorcjum największych firm informatycznych (m.in. Borland, Microsoft, Hewlett Packard, IBM, Oracle). Był on odpowiedzią na coraz bardziej nieprecyzyjną specyfikację ODBC (ang. Open DataBase Connectivity). Mimo początkowej zgodności członków konsorcjum specyfikacja nie doczekała się powszechnej akceptacji. W efekcie tylko Borland tworzy sterowniki w tym formacie. BDE jest motorem dostępu do danych i zapewnia spójną obsługę formatów dbf (z pominięciem foxbase), Paradox, Interbase, Oracle (do wersji 9 włącznie), DB2, Informix, MS SQL 7 oraz dostęp do driverów ODBC (uwaga: jednak nie każdy sterownik ODBC chce współpracować z BDE).

    Baza danych składa się z wielu plików, które należy umieścić w wydzielonym folderze do którego dostęp jest określany zwykle nie bezpośrednio, ale poprzez tzw. alias (nazwę alternatywną). Alias jest zastępczą nazwą bazy zawierającą wszystkie informacje niezbędne do połączenia się z nią. Dzięki wprowadzeniu alisów nie musimy w aplikacji bazy wprowadzać wielu szczegółów wiążących się ze specyfiką obsługi różnych typów baz. Rysunek 1 pokazuje schematycznie rolę abstrakcyjnego elementu jakim jest alias w ujednoliceniu obsługi różnorodnych typów baz.

    Jawne wprowadzenie ścieżki dostępu do folderu bazy danych w kodzie programu wymagałoby sztywnego zachowania tego układu folderów we wszystkich instalacjach takiej bazy danych. Alias można zdefiniować korzystając z jednej z dołączonych do Delphi aplikacji DataBase Explorer, BDE Configuration, lub DataBase Desktop | Tools | Alias Manager ... .

    Rys. 2. Powiązania elementów bazy danych

    Alias określa nie tylko ścieżkę dostępu do folderu z plikami bazy danych, ale również rodzaj wymaganego sterownika.

    Kolejnym krokiem zmierzającym do utworzenia bazy danych w oparciu o komponent TTable jest utworzenie tabelki. Służy do tego program DataBase Desktop dostępny z głównego menu w opcji Tools, a także spoza środowiska Delphi. W programie tym należy określić przede wszystkim typ tablicy, wybierając z menu File | New | Table. Następnie tworzona jest struktura tablicy z wyszczególnieniem wszystkich pól, ich typów i rozmiarów. Można wykorzystać istniejącą już tablicę i skopiować jej strukturę wybierając opcję Borrow. Tablicę należy zapisać nadając jej wybraną nazwę i wskazując folder gdzie zapis ma być umieszczony. Miejsce zapisu można wskazać używając aliasu jeśli wcześniej taki został utworzony.


    Komponenty Data Access mają możliwość bezpośredniego dostępu do danych. Komponenty Data Control lub QReport zawsze wymagają połączenia ze źródłem danych.


  3. Hasło dostępu do bazy danych

    Aplikacja zawierająca wiele zebranych i wykorzystywanych informacji powinna być chroniona przed nieuprawnionym dostępem i możliwością dostępu do danych. Borland dostarcza w ramach środowiska Delphi już od wersji 2.0 standardowe okno dialogowe wprowadzające hasło. Jest ono dostępne po wybraniu opcji: File | New | Dialogs | Password Dialog | OK . Okno to należy zintegrować z aplikacją tak aby otwierało się jeszcze przed otwarciem bazy danych. W tym celu z menu należy wybrać Project | Options | Forms | Main Forms i ustawić na PasswordDlg.
    Po sprawdzeniu hasła powinien być otworzony formularz główny. Dlatego w module obsługującym PasswordDlg (np. Unit2) trzeba wprowadzić w deklaracji Uses nazwę modułu związanego z formularzem głównym np. Form1. W przypadku wprowadzenia złego hasła powinien być wyświetlony odpowiedni komunikat, a otwarte wcześniej okno do wpisania hasła - zamknięte. Przykładowy zapis obsługi hasła przedstawia poniższy listing.
    unit Unit2;
    interface
    uses Windows, SysUtils, Classes, Graphics, Forms, Controls, StdCtrls,
      Buttons, Dialogs;
    type
      TPasswordDlg = class(TForm)
        Label1: TLabel;
        Password: TEdit;
        OKBtn: TButton;
        CancelBtn: TButton;
        procedure CancelBtnClick(Sender: TObject);
        procedure OKBtnClick(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    var
      PasswordDlg: TPasswordDlg;
    implementation
    uses Unit1;
    {$R *.DFM}
    procedure TPasswordDlg.CancelBtnClick(Sender: TObject);
    begin
         {Koniec programu po wybraniu klawisza Cancel}
         Application.Terminate;
    end;
    procedure TPasswordDlg.OKBtnClick(Sender: TObject);
    begin
         {Gdy hasło jest poprawne}
         if Password.Text='1234567' then
            begin
            Form1.Show;             {pokazuje formularz bazy}
            PasswordDlg.Hide;       {zamyka okno hasła}
            end
         else {w innym przypadku koniec programu}
            begin
            MessageDlg('Złe hasło',mtError,[mbOK],0);
            PasswordDlg.Close;
            Application.Terminate;
            end;
    end;
    end.
    
    Procedury przedstawione w zapisie obsługują klawisze OK i Cancel. Oprócz tego elementy, które należy dopisać są podane wytłuszczonym drukiem.

  4. Tworzenie indeksów

    Indeksowanie tablicy w omawianym tutaj przypadku można wykonać w systemie Database Desktop na etapie projektowania struktury tablicy, lub wprowadzania do niej zmian.
    Podczas tworzenia struktury tablicy można również wprowadzić indeksację jej pól wybierając opcję Indexes w polu edycyjnym Table properties i nacisjając przycisk Define. Indeks bazy danych jest szczególnym rodzajem tabeli, która zawiera opisy pól wybranych przez użytkownika i wskaźniki do pozycji rekordów zawierających te pola w bazie. Wskaźniki określają porządek pokazywania danych co pozwala na szybkie wyszukiwanie danych. Najczęściej są dostępne trzy opcje indeksowania:
    • Unique - decyduje o unikalności (braku powtórzeń w bazie) pól indeksowanych tzw. kluczy;
    • Maintained - decyduje o automatycznym aktualizowaniu indeksowania po każdej zmianie zapisów w tablicy;
    • Descending - decyduje o kolejności wg malejących wartości indeksowanych pól.
    Baza dBase IV zapisuje indeksy w pliku z rozszerzeniem MDX do identyfikacji których używa się znacznika indeksu (ang. index tag), gdyż w pliku tym mieści się więcej niż jeden indeks. Plik MDX jest powiązany z daną tabelą i jest odczytywany wraz z nią. Wprowadzone indeksy są widoczne w oknie dialogowym Restructure. Po zaznaczeniu indeksu z listy można go usunąć lub zmienić.

    Indeksowanie polega na zapamiętywaniu wskaźników określających w jaki sposób dane mają być uporządkowane, a nie na fizycznym układaniu danych według żądanej kolejności.

    Indeksowanie pozwala na szybkie wyszukiwanie rekordów ale spowalnia wprowadzanie zmian w bazie.
    Zakładanie indeksów w bazie typu dBase przebiega w następujący sposób:
    1. Otwieramy program Database Desktop.
    2. Wybieramy polecenie File | Open | Table.
    3. W oknie Open Table wybieramy nazwę bazy - Alias i następnie wskazujemy nazwę tabeli (Nazwa pliku) z danymi.
    4. Jeśli tabela jest już otwarta, np. w aktualnym projekcie, należy własność Active komponentu Table, ustawić na False.
    5. Wybieramy polecenie Table | Restructure ... i w otwartym oknie Table properties wybieramy opcję Indexes, a następnie przycisk Define w oknie Restructure dBase IV.
    6. Po otwarciu okna Define Index za pomocą klawisza-przełącznika Expression Index/Index Field, można wybrać jedną z dwu metod określania indeksów.
      • Jeśli wybrana będzie opcja Expression Index (na klawiszu pojawi się napis Index Field), będzie można utworzyć wyrażenie indeksowe złożone z pól i funkcji.
        Dwukrotne pstryknięcie w nazwę pola umieszcza ją w oknie Expression Index. Do połączenia pól można wykorzystać znak plus (+).
        Np. indeks porządkujący dane w pierwszej kolejności wg pola NAZWA, a następnie wg pola WERSJA, bez rozróżniania wielkości liter; przedstawia zapis:

                  Upper(NAZWA)+Upper(WERSJA)

        Oznacza to, że dla takich samych zawartości pola NAZWA o kolejności będzie decydowała zawartość pola WERSJA i to niezależnie od użytych wielkości liter.
      • Jeśli wybrana będzie opcja Index Field (na klawiszu pojawi się napis Expression Index), wystarczy wybrać pole z listy i zatwierdzić klawiszem OK. W okienku Save Index As należy wpisać nazwę znacznika indeksu (Index Tag Name), która jest używana do identyfikacji indeksu w pliku typu MDX. Jako znacznik nazwy może być użyta nazwa pola (do 10 znaków).
        Po wybraniu klawisza OK indeks zostanie dołączony do listy indeksów w oknie dialogowym Restructure ...

  5. Wyszukiwanie danych

    Metoda SetKey pozwala ustawić specyficzny stan tabeli dsSetKey, w którym do danego pola, zamiast jego wartości, przyporządkowywana jest wartość szukana. Np. dla tabeli program.dbf należy użyć zapisu: program.SetKey. Do pola indeksowanego, którego wartości szukamy przypisuje się szukaną wartość tak jak zmienia się jego wartość. W tym przypadku stan dsSetKey informuje system, że jest to właśnie wartość szukana a nie wprowadzana nowa wartość pola. Przykładowe zapisy tej operacji mogą wyglądać tak:

    program.FieldByName['NAZWA'].AsString:='TESTAP';
    lub
    program.FieldByName['NAZWA'].AsString:=edNazwa.Text;

    Proces wyszukiiwania jest inicjowany metodą GotoKey, np. poleceniem: program.GotoKey;
    Jeśli w bazie zostanie znaleziona szukana wartość, to metoda GotoKey zwraca wartość True i bieżącym rekordem staje się rekord znaleziony. Przy braku szukanej wartości metoda GotoKey zwraca wartość False, a bieżący rekord nie ulega zmianie.
    W przypadku gdy znamy tylko początkową część szukanego zapisu korzystamy z metody GotoNearest.
    Na wyszukiwanie składają się więc trzy podstawowe operacje, a mianowicie:
    1. Ustawienie stanu SetKey.
    2. Przypisanie wartości szukanej do pola.
    3. Włączenie odpowiedniej metody wyszukiwania.
    Delphi posiada także zaimplementowane metody łączące te trzy operacje. Są to odpowiednio metody FindKey i FindNearest, np.:
        procedure TForm1.SearchIndeksClick(Sender:TObject);
           begin
               program.FindNearest([edNazwa.Text]);
           end;
    
    W przypadku korzystania z takiego skrótu należy zwracać uwagę na kolejność indeksowanych pól. FindKey(['TESTAP','5.0']);, to nie to samo co FindKey(['5.0','TESTAP']);.
    W pierwszym przypadku nie ma niebezpieczeństwa takiej pomyłki, gdyż wartości przypisuje się osobno konkretnym polom.


  6. Filtrowanie danych

    Operacja polega na możliwości przeglądania tylko rekordów należących do wybranego zakresu. Filtrowanie danych obsługiwanych komponentem klasy TTable jest ograniczone, gdyż nie można przeglądać tym sposobem rekordów, w których dane pole przyjmuje tylko jedną z dwóch możliwych wartości. Większe możliwości w tym zakresie stwarza korzystanie z komponentu TQuery.
    Do zdefiniowania zakresu danych przeznaczona jest metoda SetRange, która odnosi się do aktualnie aktywnego indeksu. Stan poprzedni przywraca metoda CancelRange. Np.:

    SetRange(const StartValues, EndValues: array of const);
    i
    CancelRange;

    Można również zadeklarować zakres uruchamiając kolejno:
    • metodę SetRangeStart, definiując początkowy kres za pomocą polecenia, np.:
      Table1.SetRangeStart;
      Table1.Fields[0].AsString:=kresDolny;
    • metodę SetRangeEnd, definiując końcowy kres za pomocą polecenia, np.:
      Table1.SetRangeEnd;
      Table1.Fields[0].AsString:=kresGorny;
    • metodę ApplyRange, która powoduje, że podany zakres staje się obowiązującym dla danych komponentu TTable, np.:
      Table1.ApplyRange;.

  7. Opracowywanie raportów za pomocą QuickReport

    Od wersji 2.0 w środowisku Delphi pojawiła się nowość, która znakomicie ułatwiła obsługę wydruków raportów z baz danych. Wiele komponentów zgromadzonych na zakładce QReport działa bardzo podobnie jak ich zwykłe odpowiedniki (np. QRDBText jak DBText). Są one jednak wzbogacone o możliwość obsługi drukowania. QuickReport jest głównym komponentem do tworzenia raportów z bazy danych. Po jego umieszczeniu na formatce należy ustalić odpowiednio właściwości DataSource i Table albo Query.
    Do utworzenia odpowiedniego kształtu wydruku wykorzystywane są takie komponenty jak QRBand, QRLabel, QRMemo, QRDBText.
    KomponentOpis
    QuickReportGłówny komponent odpowiedzialny za całościową obsługę danych i drukowania. Jednak odpowiednią formę wydruku nadają inne komponenty. Głównymi metodami używanymi z tym komponentem są Preview i Print.
    QRBandOkreśla pojedynczy rodzaj danychw raporcie, wydzielając dla nich obszar. Rodzaj ten jest ustalany właściwością BandType.
    QRGroupDostarcza do raportu danych w przypadku gdy wymagane jest specjalne pogrupowanie informacji. Jest on używany najczęściej tam gdzie wymagane jest kilka poziomów grupowania danych.
    QRDetailLinkUżywa się go w raporcie, gdy trzeba przedstawić kilka poziomów szczegółów. Jest to druga metoda tworzenia raportów od ogółu do szczegółu (master/detail). Źródłem danych dla QRDetailLink jest tabela główna. Dla każdego rekordu z tej tabeli są drukowane w raporcie odpowiednie grupy informacji.
    QRLabelPozwala na drukowanie nagłówków kolumn i nazw umieszczanych w raporcie. Do drukowania danych z tabeli służy komponent QRDBText.
    QRMemoDziała podobniejak standardowy komponent Memo ale dodatkowo obsługuje drukarkę. Uwaga: przy drukowaniu nie ma możliwości korzystania z pasków przesuwu. Drukowany jest aktualnie widziany fragment tekstu.
    Komponent ten do określenia pokazywanego z bazy tekstu wymaga ustalenia własności Strings.
    QRDBTextNajczęściej wykorzystywany komponent do drukowania danych z bazy.
    QRDBCalcPozwala na pokazywanie sumy wartości znajdujących się w wybranych polach lub liczby pozycji zawartych w obszarze szczegółów. Właściwości DataSource i DataField określają co ma być policzone. Właściwość ResetGroup określa kiedy ma być wyzerowany licznik.
    QRDBSysDataMoże pokazywać aktualną datę, czas, tytuł raportu, nr strony lub inne parametry systemu. Podstawową własnością komponentu jest właściwość Data, która definiuje typ danych do pokazania.
    QRShapePozwala dodać do raportów proste elementy graficzne. Można np. za jego pomocą otrzymać prosty wykres słupkowy. Właściwość Brish określa kolor i styl pędzla (np. ukośny, poziomy i in.).
    QRPreviewDaje podgląd raportu na ekranie przed wydrukowaniem. Jeden z ważniejszych komponentów tej zakładki.
    Aby stworzyć prosty raport za pomocą komponentów QuickReport nie potrzeba prawie wcale dodawać zapisów kodu. Cała praca sprowadza się głównie do przeciągania komponentów na nową formatkę i ich rozmieszczania. Obsługa podstawowych opcji menu takich jak podgląd raportu i jego drukowanie może przykładowo wyglądać tak:
    procedure TForm1.Podgld1Click(Sender: TObject);
    begin
      Form2.QuickReport1.Preview;
    end;
    
    procedure TForm1.Drukuj1Click(Sender: TObject);
    begin
      Form2.QuickReport1.Print;
    end;
    
    W przykładzie założono, że na formatce pierwszej (Form1) znajduje się menu główne (komponent MainMenu) z opcjami: Podgląd i Drukuj, a na formatce drugiej (Form2) umieszczony jest raport. Należy pamiętać aby w deklaracji Uses w module Unit1 znalazł się zapis:

    uses Unit2; {moduł związany z formatką drugą Form2}

    QuickReport jest szybkim i wygodnym narzędziem, jednak posiada także wiele ograniczeń w stosunku do rozbudowanych bardziej rozwiązań, takich jak np. ReportSmith.

  8. Pola wyliczeniowe i stany zestawu danych

    Pola wyliczeniowe (ang. calculated fields) są to dodatkowe pola, których wartość jest wyliczana automatycznie po każdej zmianie w którymkolwiek z pól wchodzących do obliczanej zależności. Do ich utworzenia wykorzystuje się edytor pól. Można go otworzyć podwójnie pstrykając myszką w komponent Table. W płaszczyźnie nowootwartego okna pstrykamy prawym klawiszem myszki i z oferowanych opcji menu kontekstowego wybieramy New field... W oknie New Field nadajemy nazwę i typ pola wyliczeniowego (zaznaczyć opcję Calculated).
    Dla komponetu Table należy dodatkowo zapisać obsługę zdarzenia OnCalcFields.Zdarzenie to jest zgłaszane automatycznie, gdy wymagane jest odświeżenie zawartości pól obliczeniowych. W tym momencie baza przechodzi w tryb dsCalcEdit, w którym jedynie pola obliczeniowe mogą być zmieniane.
    Przykładowo jeśli tabela danych o nazwie oporniki zawierała pola napiecie i rezystor, to można w polu obliczeniowym prad wyznaczyć wartość natężenia z prawa Ohma:
    procedure TForm1.Table1CalcFields(DataSet: TDataSet);
    begin
      with oporniki do
        begin
          FieldByName('prad'.AsFloat:=
            FieldByName('napiecie').AsFloat / FieldByName('rezystor').AsFloat;
    end;
    
    Tabela danych i jej zawartość mogą znajdować się w jednyym z sześciu stanów:
    1. dsEdit - redagowanie rekordu.
    2. dsBrowse - przeglądanie rekordów.
    3. dsInsert - wstawianie nowego rekordu.
    4. dsInactive.
    5. dsSetKey - szukanie rekordu.
    6. dsCalcFields - obliczanie wartości pól wyliczeniowych.
    Poruszanie się po rekordach ułatwiają właściwości:
    - EOF (ang. End Of File), która przyjmuje wartość True, gdy nie ma ani jednego rekordu, gdy użyto metody Last albo metody Next, gdy bieżącym rekordem był ostatni;
    - BOF (ang. Beginning Of File), która przyjmuje wartość True, gdy nastąpi otwarcie zestawu rekordów, wywołana będzie metoda First albo metoda Prior, gdy bieżącym rekordem będzie rekord pierwszy.
    Oprócz wymienionych metod poruszanie się po zestawie rekordów ułatwia też metoda MoveBy(n), która powoduje przeskoczenie n rekordów w przód albo wstecz, gdy liczba n jest liczbą ujemną.
    Przykład zastosowania tych metod i właściwości do przetwarzania wszystkich rekordów w tabeli:
    begin
      Student.First; //skok na początek tabeli danych
      Student.DisableControls; //wyłączenie elementów sterujących
      while not(Student.EOF) do //aż nie zostanie osiągnięty ostatni rekord
        begin
          Student.Edit; //przejdź w tryb redagowania rekordu
          Student.FileByName('Ocena').AsInteger:=0; //zapisz do pola Ocena wartość wstępną zerową
          Student.Post; //zatwierdź zmiany zapisu w rekordzie
          Student.Next; //przejdź do następnego rekordu
        end;
      Student.EnableControls; //włącz elementy sterujące po zakończeniu pętli
    end;
    
    Przykład powyższy zakłada, że w tablicy Student.dbf do wszystkich rekordów w polu Ocena na wstępie wpisywane są wartości zerowe.