Dezvoltarea Aplicatiilor cu Baze de Date Intr Un Mediu Rad

Universitatea de Vest Timișoara

Facultatea de matematică

Secția informatică

Dezvoltarea aplicațiilor cu baze de date

într-un mediu RAD

Conducător ștințific Candidat

lector Ioan Despi Nițu Adrian

Cuprins

Introducere

Capitolul 1 Medii RAD …………………………………… 1

1.1 Generalități ………………………………………………….….. 1

1.2 Caracteristici ale mediilor RAD ………………………………… 2

Capitolul 2 Delphi ……………………………………….. 2

2.1 Prezentare generală …………………………………………… 2

2.2 Suportul pentru baze de date …………………………………….. 3

Capitolul 3. Dezvoltarea aplicațiilor cu baze de date ……… 7

3.1 Uneltele de dezvoltare …………………………………………… 8

3.2 Dezvoltarea de aplicații pentru servere locale și la distanță ………. 11

3.2.1 Metodologia de realizare a aplicațiilor ……………………… 11

3.2.2 Irarhia componentelor pentru baze de date …………………… 15

Capitolul 4 Prezentarea unei aplicații dezvoltată în Delphi … 21

4.1 Introducere ……………………………………………………….. 21

4.1.1 Semnificația și structura tabelelor baze de date ……………… 22

4.1.2 Funcționalitatea aplicației ……………………………………. 23

4.2 Utilizare ………………………………………………………….. 24

4.3 Implementare ……………………………………………. 39

4.3.1 Interfața programului …………………………………………… 39

4.3.2 Gestiunea tabelei produselor …………………………………… 31

4.3.3 Gestiunea tabelei relațiilor ……………………………………… 40

4.3.4 Gestiunea matricei relațiilor …………………………………….. 47

4.3.5 Vizualizarea arborescentă .………………………………………. 51

4.3.6 Facilitățile de calcul …………………………………………….. 53

Anexă

Introducere

În prezenta lucrare: "Dezvoltarea aplicațiilor cu baze de date într-un mediu RAD" , se realizează într-o primă parte prezentarea conceptului de RAD (Rapid Application Development) iar în a doua parte se descrie pe un exemplu concret construcția unei aplicații cu baze de date într-un asemenea mediu. Lucrarea este însoțită de o aplicație realizată sub Borland Delphi 2.0, aplicație ce constituie exemplul analizat în lucrare.

În primul capitol: Medii RAD, se face o prezentare a celor mai importante caracteristici ale acestora. Se pune accent în principal pe necesitatea utilizării acestor medii și pe avantajele majore ce le oferă.

În capitolul al doilea: Delphi, este prezentat unul din cele mai noi și mai bune produse ale firmei Borland, produs care a devenit un exponent de marcă al mediilor RAD sau chiar un standard al acestora.

Capitolul următor: Dezvoltarea aplicațiilor cu baze de date este o descriere mai amănunțită a modului în care se realizează aplicațiile cu baze de date în Delphi, acesta fiind dealtfel unul din atuurile principale ale popularității acestui mediu. Sunt descrise uneltele pentru baze de date oferite de Delphi și este prezentată metodologia dezvoltării aplicațiilor.

Ultimul capitol: Prezentarea unei aplicații dezvoltată în Delphi, descrie în detaliu realizarea aplicației atașată lucrări, aplicație ce se dorește a fi un exemplu interesant a ceea ce se poate realiza cu Delphi în domeniul bazelor de date.

=== Anexa ===

Anexă

unit-ul formei principale ;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

Menus, StdCtrls, Buttons, ExtCtrls;

type

TForm_m = class(TForm)

MainMenu1: TMainMenu;

Table1: TMenuItem;

Treeview1: TMenuItem;

descendentview1: TMenuItem;

Ascendentview1: TMenuItem;

Produse1: TMenuItem;

Legaturi1: TMenuItem;

Open1: TMenuItem;

Produse2: TMenuItem;

Legaturi2: TMenuItem;

OpenDialog1: TOpenDialog;

BitBtn1: TBitBtn;

BitBtn2: TBitBtn;

BitBtn3: TBitBtn;

Calculate2: TMenuItem;

Calculate1: TMenuItem;

Advanced1: TMenuItem;

BitBtn4: TBitBtn;

BitBtn5: TBitBtn;

BitBtn6: TBitBtn;

procedure descendentview1Click(Sender: TObject);

procedure Ascendentview1Click(Sender: TObject);

procedure Calculate1Click(Sender: TObject);

procedure Produse1Click(Sender: TObject);

procedure Legaturi1Click(Sender: TObject);

procedure FormClick(Sender: TObject);

procedure Table1Click(Sender: TObject);

procedure Treeview1Click(Sender: TObject);

procedure Produse2Click(Sender: TObject);

procedure Legaturi2Click(Sender: TObject);

procedure FormCreate(Sender: TObject);

procedure BitBtn1Click(Sender: TObject);

procedure BitBtn2Click(Sender: TObject);

procedure BitBtn3Click(Sender: TObject);

procedure Advanced1Click(Sender: TObject);

procedure FormShow(Sender: TObject);

procedure BitBtn5Click(Sender: TObject);

procedure BitBtn4Click(Sender: TObject);

procedure BitBtn6Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

var

Form_m: TForm_m;

open_pro,open_leg:boolean;

change,root:byte;

implementation

{$R *.DFM}

uses Unit_pro, Unit_rel, Unit_dlg,Unit_dlg_a,Unit_calc,Unit_o, Unit_adv;

procedure TForm_m.descendentview1Click(Sender: TObject);

begin

Form_dlg.Show;

end;

procedure TForm_m.Ascendentview1Click(Sender: TObject);

begin

Form_d_a.Show;

end;

procedure TForm_m.Calculate1Click(Sender: TObject);

begin

Form_calc.Show;

end;

procedure TForm_m.Produse1Click(Sender: TObject);

begin

Form_pro.Show;

end;

procedure TForm_m.Legaturi1Click(Sender: TObject);

begin

Form_rel.Show;

end;

procedure TForm_m.Produse2Click(Sender: TObject);

begin

open_pro:=true;

Form_o.Caption:='Open table "produse"';

Form_o.ShowModal;

end;

procedure TForm_m.Legaturi2Click(Sender: TObject);

begin

open_leg:=true;

Form_o.Caption:='Open table "relatii"';

Form_o.ShowModal;

end;

procedure TForm_m.FormCreate(Sender: TObject);

begin

Table1.Enabled:=false;

Treeview1.Enabled:=false;

Calculate2.Enabled:=false;

open_pro:=false;

open_leg:=false;

end;

procedure TForm_m.BitBtn1Click(Sender: TObject);

begin

Form_dlg.Show;

end;

procedure TForm_m.BitBtn2Click(Sender: TObject);

begin

Form_d_a.Show;

end;

procedure TForm_m.BitBtn3Click(Sender: TObject);

begin

Form_calc.Show;

end;

procedure TForm_m.Advanced1Click(Sender: TObject);

begin

Form_adv.Show;

end;

procedure TForm_m.FormShow(Sender: TObject);

begin

Treeview1.Enabled:=false;

end;

procedure TForm_m.BitBtn5Click(Sender: TObject);

begin

Form_pro.Show;

end;

procedure TForm_m.BitBtn4Click(Sender: TObject);

begin

Form_rel.Show;

end;

procedure TForm_m.BitBtn6Click(Sender: TObject);

begin

Form_adv.Show;

end;

end.

Unit-ul de gestiune a tabelei produselor ;

interface

uses

Dialogs,SysUtils, Windows, Messages, Classes, Graphics, Controls,

StdCtrls, Forms, DBCtrls, DB, DBGrids, DBTables, Grids, ExtCtrls;

type

TForm_pro = class(TForm)

Table1Nr: TFloatField;

Table1Denumire: TStringField;

Table1Cost_a: TFloatField;

Table1Cost_c: TFloatField;

DBGrid1: TDBGrid;

DBNavigator: TDBNavigator;

Panel1: TPanel;

DataSource1: TDataSource;

Panel2: TPanel;

Table1: TTable;

Table2: TTable;

Label1: TLabel;

Table1Icon: TGraphicField;

DBImage1: TDBImage;

procedure act_del_combo(x:integer; var cb:TComboBox);

function act_del_list(x:integer; var ls:TListBox):integer;

function check(nr:integer):boolean;

procedure reactualizez(i1:integer;j:integer);

procedure actualizez(i:byte;n:integer);

procedure FormCreate(Sender: TObject);

procedure Table1BeforeEdit(DataSet: TDataSet);

procedure Table1BeforeDelete(DataSet: TDataSet);

procedure Table1AfterDelete(DataSet: TDataSet);

procedure Table1BeforeInsert(DataSet: TDataSet);

procedure Table1AfterInsert(DataSet: TDataSet);

procedure Table1AfterOpen(DataSet: TDataSet);

procedure Table1PostError(DataSet: TDataSet; E: EDatabaseError;

var Action: TDataAction);

procedure Table1AfterPost(DataSet: TDataSet);

procedure DBGrid1DblClick(Sender: TObject);

private

{ private declarations }

public

{ public declarations }

end;

var

Form_pro: TForm_pro;

n1,old,old_n,old_n2,m,del_nr:integer;

sursa :char;

extern,delete,ciudat:boolean;

w_message,old_den:string;

implementation

uses Unit_a, Unit_rel,Unit_dlg, Unit_calc, Unit_dlg_a, ImageWin,Unit_adv;

{$R *.DFM}

procedure TForm_pro.FormCreate(Sender: TObject);

begin

Table1.Open;

Table1.Refresh;

extern:=false;

sursa:='e';

delete:=false;

ciudat:=false;

end;

function TForm_pro.act_del_list(x:integer; var ls:TListBox):integer;

var i:integer;

begin

for i:=0 to ls.Items.Count-1 do

if ls.Items.Strings[i]=IntToStr(x) then

begin

ls.Items.Delete(i);

result:=i;

exit;

end;

end;

procedure TForm_pro.act_del_combo(x:integer; var cb:TComboBox);

var i:integer;

begin

for i:=0 to cb.Items.Count-1 do

if cb.Items.Strings[i]=IntToStr(x) then

begin

cb.Items.Delete(i);

exit;

end;

end;

function TForm_pro.check(nr:integer):boolean;

begin

check:=Table1.FindKey([nr]);

end;

procedure TForm_pro.reactualizez(i1:integer;j:integer);

begin

extern:=true;

if (Table1.FindKey([j])) then

begin

n1:=m*(Table1.Fields[2].AsInteger+Table1.Fields[3].AsInteger);

if(Table1.FindKey([i1])) then

begin

Table1.Edit;

Table1.Fields[3].AsInteger:=Table1.Fields[3].AsInteger+unit_pro.n1;

Table1.Post;

actualizez(i1,n1);

end

else

begin

w_message:='unit_pro :Date inconsistente,lipseste produsul: '+ IntToStr(i1);

ShowMessage(w_message);

end;

extern:=false;

end;

extern:=false;

end;

procedure TForm_pro.actualizez(i:byte;n:integer);

var j:byte;

new_n:integer;

begin

for j:=1 to 30 do

if Unit_a.a[j,i]=1 then

begin

Table1.DisableControls;

try

Table1.First;

while not Table1.EOF do

begin

if (Table1.Fields[0].AsInteger=j) then

begin

Table2.Open;

if (not (Table2.FindKey([j,i])))then

begin

w_message:='Date inconsistente, lipseste legatura :'+IntToStr(j)+'-'+IntToStr(j);

ShowMessage(w_message);

end;

new_n:=n*Table2.Fields[2].AsInteger;

Table2.Close;

Table1.Edit;

Table1.Fields[3].AsInteger:=Table1.Fields[3].AsInteger+new_n;

Table1.Post;

actualizez(j,new_n);

end;

Table1.Next;

end;

finally

Table1.EnableControls;

end;

end;

if j=31 then ciudat:=true;

end;

procedure TForm_pro.Table1BeforeInsert(DataSet: TDataSet);

begin

sursa:='i';

end;

procedure TForm_pro.Table1AfterInsert(DataSet: TDataSet);

begin

Table1.Fields[3].Asinteger:=0;

end;

procedure TForm_pro.Table1BeforeEdit(DataSet: TDataSet);

begin

if extern then exit;

sursa:='e';

old:=Table1.Fields[0].Value;

old_n:=Table1.Fields[2].AsInteger;

old_n2:=Table1.Fields[3].AsInteger;

old_den:=Table1.Fields[1].Value;

end;

procedure TForm_pro.Table1BeforeDelete(DataSet: TDataSet);

var i:integer;

MyBookmark: TBookmark;

begin

delete:=true;

extern:=true;

del_nr:=Table1.Fields[0].AsInteger;

n1:=-(Table1.Fields[2].AsInteger+Table1.Fields[3].AsInteger);

MyBookmark := Table1.GetBookmark;

actualizez(del_nr,n1);

Form_rel.act_rel(del_nr,0);

Table1.GotoBookmark(MyBookmark);

extern:=false;

for i:=1 to 30 do

begin

unit_a.a[Table1.Fields[0].AsInteger,i]:=0;

unit_a.d[Table1.Fields[0].AsInteger,i]:=0;

end;

for i:=1 to 30 do

begin

unit_a.a[i,Table1.Fields[0].AsInteger]:=0;

unit_a.d[i,Table1.Fields[0].AsInteger]:=0;

end;

act_del_combo(del_nr,Form_dlg.ComboBox1);

act_del_combo(del_nr,Form_d_a.ComboBox1);

act_del_combo(del_nr,Form_calc.ComboBox1);

act_del_combo(del_nr,Form_calc.ComboBox2);

i:=act_del_list(del_nr,Form_adv.ListBox1);

act_del_list(del_nr,Form_adv.ListBox4);

Form_adv.ListBox7.Items.Delete(i);

Form_adv.ListBox9.Items.Delete(i);

//Form_adv.ListBox7.Items.Delete(Form_adv.NameToIndex(Form_adv.ListBox7,Table1.Fields[1].AsString));

//Form_adv.ListBox9.Items.Delete(Form_adv.NameToIndex(Form_adv.ListBox9,Table1.Fields[1].AsString));

end;

procedure TForm_pro.Table1AfterDelete(DataSet: TDataSet);

begin

Table1.IndexDefs.Update;

Table1.Refresh;

delete:=false;

end;

procedure TForm_pro.Table1AfterOpen(DataSet: TDataSet);

var x,i:integer;

begin

// if unit_a.deja_pro then exit;

extern:=true;

unit_a.deja_pro:=true;

Form_calc.ComboBox1.Items.Clear;

Form_calc.ComboBox2.Items.Clear;

Form_d_a.ComboBox1.Items.Clear;

Form_dlg.ComboBox1.Items.Clear;

Form_adv.ListBox1.Items.Clear;

Form_adv.ListBox4.Items.Clear;

Form_adv.ListBox7.Items.Clear;

Form_adv.ListBox9.Items.Clear;

Form_adv.ListBox2.Items.Clear;

Form_adv.ListBox3.Items.Clear;

Form_adv.ListBox5.Items.Clear;

Form_adv.ListBox6.Items.Clear;

Form_adv.ListBox8.Items.Clear;

Form_adv.ListBox10.Items.Clear;

try

Table1.DisableControls;

Table1.First;

while not Table1.EOF do

begin

x:=Table1.Fields[0].AsInteger;

Form_adv.Add_sort_combo(Form_calc.ComboBox1,x);

Form_adv.Add_sort_combo(Form_calc.ComboBox2,x);

Form_adv.Add_sort_combo(Form_d_a.ComboBox1,x);

Form_adv.Add_sort_combo(Form_dlg.ComboBox1,x);

i:=Form_adv.Add_sort(Form_adv.ListBox1,x);

Form_adv.Add_sort(Form_adv.ListBox4,x);

Form_adv.ListBox7.Items.Insert(i,Table1.Fields[1].AsString);

Form_adv.ListBox9.Items.Insert(i,Table1.Fields[1].AsString);

Table1.Next;

end;

Table1.First;

finally

Table1.EnableControls;

Form_calc.ComboBox2.Text :=Form_calc.ComboBox2.Items[0];

Form_calc.ComboBox1.Text :=Form_calc.ComboBox1.Items[0];

Form_d_a.ComboBox1.Text :=Form_d_a.ComboBox1.Items[0];

Form_dlg.ComboBox1.Text :=Form_dlg.ComboBox1.Items[0];

Form_adv.ListBox1.ItemIndex :=0;

Form_adv.ListBox4.ItemIndex :=0;

Form_adv.ListBox7.ItemIndex :=0;

Form_adv.ListBox9.ItemIndex :=0;

end;

extern:=false;

end;

procedure TForm_pro.Table1AfterPost(DataSet: TDataSet);

var i,j,n1:integer;

begin

if extern then exit;

extern:=true;

if sursa='e' then

begin

if ((Table1NR.Value<0)or(Table1NR.Value>100)or(Table1.Fields[1].AsString='')) then

begin

ShowMessage('0<= Nr <=100 si Denumire <> Null . Abort ! OK?!');

Table1.Edit;

Table1.Fields[0].AsInteger:=old;

Table1.Fields[1].AsString:=old_den;

Table1.Fields[2].AsInteger:=old_n;

Table1.Fields[3].AsInteger:=old_n2;

Table1.Post;

extern:=false;

Exit;

end;

if Table1.Fields[0].AsInteger<>old then

begin

Form_rel.act_rel(old,Table1NR.Value);

for i:=1 to 30 do

begin

if unit_a.a[i,old]=1 then

begin

unit_a.a[i,old]:=0;

unit_a.a[i,Table1.Fields[0].AsInteger]:=1;

end;

if unit_a.d[i,old]=1 then

begin

unit_a.d[i,old]:=0;

unit_a.d[i,Table1.Fields[0].AsInteger]:=1;

end;

end;

for j:=1 to 30 do

begin

if unit_a.a[old,j]=1 then

begin

unit_a.a[old,j]:=0;

unit_a.a[Table1.Fields[0].AsInteger,j]:=1;

end;

if unit_a.d[old,j]=1 then

begin

unit_a.d[old,j]:=0;

unit_a.d[Table1.Fields[0].AsInteger,j]:=1;

end;

end;

act_del_combo(old,Form_dlg.ComboBox1);

act_del_combo(old,Form_d_a.ComboBox1);

act_del_combo(old,Form_calc.ComboBox1);

act_del_combo(old,Form_calc.ComboBox2);

act_del_list(old,Form_adv.ListBox1);

act_del_list(old,Form_adv.ListBox4);

Form_adv.ListBox7.Items.Delete(Form_adv.NameToIndex(Form_adv.ListBox7,old_den));

Form_adv.ListBox9.Items.Delete(Form_adv.NameToIndex(Form_adv.ListBox9,old_den));

j:=Table1.Fields[0].AsInteger;

Form_adv.Add_sort_combo(Form_calc.ComboBox1,j);

Form_adv.Add_sort_combo(Form_calc.ComboBox2,j);

Form_adv.Add_sort_combo(Form_d_a.ComboBox1,j);

Form_adv.Add_sort_combo(Form_dlg.ComboBox1,j);

i:=Form_adv.Add_sort(Form_adv.ListBox1,Table1.Fields[0].AsInteger);

Form_adv.Add_sort(Form_adv.ListBox4,Table1.Fields[0].AsInteger);

if i=Form_adv.ListBox1.Items.Count-1 then

begin

Form_adv.ListBox7.Items.Add(Table1.Fields[1].AsString);

Form_adv.ListBox9.Items.Add(Table1.Fields[1].AsString);

extern:=false;

Exit;

end;

Form_adv.ListBox7.Items.Insert(i,Table1.Fields[1].AsString);

Form_adv.ListBox9.Items.Insert(i,Table1.Fields[1].AsString);

end;

if Table1.Fields[1].AsString<>old_den then

if Table1.Fields[0].AsInteger=old then

begin

Form_adv.ListBox7.Items[Form_adv.NameToIndex(Form_adv.ListBox7,old_den)]:=Table1.Fields[1].AsString;

Form_adv.ListBox9.Items[Form_adv.NameToIndex(Form_adv.ListBox9,old_den)]:=Table1.Fields[1].AsString;

end;

if Table1.Fields[2].AsInteger<>old_n then

begin

n1:=(Table1.Fields[2].AsInteger-old_n);

actualizez(Table1.Fields[0].AsInteger,n1);

end;

extern:=false;

Exit;

end;

if sursa='i' then

begin

if ((Table1NR.Value<0)or(Table1NR.Value>100)or(Table1.Fields[1].AsString='')) then

begin

ShowMessage('0<= Nr <=100 si Denumire <> Null . Abort ! OK?!');

Table1.Delete;

extern:=false;

Exit;

end;

j:=Table1.Fields[0].AsInteger;

Form_adv.Add_sort_combo(Form_calc.ComboBox1,j);

Form_adv.Add_sort_combo(Form_calc.ComboBox2,j);

Form_adv.Add_sort_combo(Form_d_a.ComboBox1,j);

Form_adv.Add_sort_combo(Form_dlg.ComboBox1,j);

i:=Form_adv.Add_sort(Form_adv.ListBox1,Table1.Fields[0].AsInteger);

Form_adv.Add_sort(Form_adv.ListBox4,Table1.Fields[0].AsInteger);

if i=Form_adv.ListBox1.Items.Count-1 then

begin

Form_adv.ListBox7.Items.Add(Table1.Fields[1].AsString);

Form_adv.ListBox9.Items.Add(Table1.Fields[1].AsString);

extern:=false;

Exit;

end;

Form_adv.ListBox7.Items.Insert(i,Table1.Fields[1].AsString);

Form_adv.ListBox9.Items.Insert(i,Table1.Fields[1].AsString);

end;

extern:=false;

end;

procedure TForm_pro.DBGrid1DblClick(Sender: TObject);

begin

extern:=true;

Table1.Edit;

ImageForm.ShowModal;

if (ImageForm.FileListBox1.Filename <>'') then

begin

Table1Icon.LoadFromFile(ImageForm.FileListBox1.Filename);

end;

Table1.Post;

extern:=true;

end;

=== capturi ===

-în momentul inserării unei noi relații X conține pe Y , trebuie avută în vedere posibilitatea ca nu cumva componenta Y să conțină deja pe X. Această situație nu trebuie tolerată întrucât ar duce la apariția de cicluri în ierarhie, cu efecte nedorite pentru rularea programului și corectitudinea datelor.

Facilitățile de calcul se referă la posibilitatea ca utilizatorul să precizeze pentru mai multe produse cantitatea necesară și să obțină costul și cantitatea de subcomponente sau repere necesară pentru realizarea cantității propuse .

Funcționalitățile grafice se referă la posibilitatea vizualizării, pentru un anumit produs, a tuturor produselor în cărei componență intră sau a tuturor produselor ce intră în componența sa, adică a arborilor ascendenților sau descendenților.

Acestea sunt, pe scurt, facilitățile programului.

4.2 Utilizare

Primul lucru cu care utilizatorul vine în contact în momentul începerii execuției programului este meniul principal al acestuia (fereastra principală) . Din cadrul acestei ferestre poate exploata mai departe facilitățile aplicației, fie navigând prin meniul afișat și selectând opțiunile dorite, fie mai scurt, prin acționarea butoanelor specifice atașate.

Meniu principal:

Meniul principal este compus din patru opțiuni principale:

-Open

-EditTables

-TreeView

-Calculate

Open

Conține două subopțiuni: Open table “produse” și Open table “relații”.

În mod evident, pentru a putea utiliza programul este necesară deschiderea bazelor de date ce fac obiectul acestei aplicații. Acesta se face prin selectarea pe rând a celor două opțiuni Open, fiecare din ele deschizând o fereastră standard de deschidere de fișiere. Fereastra conține două liste, una cu toții directorii de pe driver-ul (directorul) curent și una cu toate fișierele tip baze de date aflate în directorul selectat. Având posibilitatea de a naviga prin toții directorii sau toate driver-ele aflate în sistem, utilizatorul poate alege astfel care dintre tabelele baze de date existente va fi cea selectată. Dacă va opta pentru una cu o structură diferită de cea prezentată anterior, va fi atenționat și va trebui să aleagă în final o tabelă corectă.

Dialogul de deschidere a tabelelor baze de date:

Când programul este lansat în execuție, toate opțiunile cu excepție primei sunt dezactivate astfel că utilizatorul nu îl va putea folosi până când nu va selecta tabele necesare. Odată ce ele au fost selectate, celelalte opțiuni devin active și se va putea continua folosirea programului.

Această opțiune nu este folosită exclusiv la începerea execuției programului, ea poate fi selectată și ulterior în cazul în care utilizatorul dorește schimbarea setului de

tabele accesate. Dacă se dorește de exemplu deschiderea unei alte tabele a componentelor, acest lucru se va putea face fără nici o problemă, cu singura observație că va trebui să se schimbe și tabela asociată a relațiilor între componente, pentru a se păstra integritatea datelor.

EditTables

Se compune din două opțiuni: Edit table “produse” și Edit table “relații”.

După ce au fost selectate tabelele baze da date folosite, utilizatorul va putea opta pentru aceste două posibilități pentru a putea gestiona cele două tabele.

Astfel selectarea unei astfel de opțiuni va afișa o fereastră cunoscută de editarea de date, ce are ca și unelte principale un grid și un navigator tipic. Cu ajutorul lor se pot edita, modifica, insera sau șterge date.

Ferestrele de editare și vizualizare pentru cele două tabele:

Tabela produselor.

Tabela legăturilor dintre produse.

Anterior am precizat că în tabela produselor se memorează pentru fiecare componentă câte un mic simbol grafic – o imagine bitmap de fapt. Pentru a putea atașa sau schimba o astfel de imagine pentru o anumită componentă , se efectuează dublu-click pe înregistrarea corespunzătoare ei din gridul afișat. Se deschide o fereastră de vizualizare de imagini bitmap similară cu cea de deschidere a fișierelor, cu diferența că imaginea corespunzătoare fișierului selectat este afișată instantaneu. De aici se poate selecta imaginea dorită.

Selectarea icon-ului unui produs:

TreeViews

Are două opțiuni simetrice : AscendentView și DescendentView.

Cele două oferă utilizatorului o vedere ascendentă sau descendentă a unei anumite componente. Astfel, selectarea primei opțiuni de exemplu, va genera apariția unei noi ferestre în cadrul căruia utilizatorul va trebui să selecteze dintr-o listă componenta căreia dorește săi vadă ascendenții. Odată selectată se va afișa un arbore care are ca rădăcină componenta respectivă și din care pornesc noduri ce reprezintă întreaga ierarhie a componentelor ce o conțin. Fiecare nod reprezintă deci o componentă și va afișa identificatorul, denumirea și icon-ul asociat ei, precum și numărul de bucății din componenta fiu.

Dialogul de vizualizare al unui subarbore al ierarhiei produselor.

Fereastra mai conține și două butoane de expandare sau colapsare a întregului arbore.

Calculate

Se descompune la rândul ei în Calculate și Advanced.

Prima opțiune dă utilizatorului o posibilitate rapidă de a afla câte bucății dintr-o anumită componentă se găsesc în compoziția alteia. Micul dialog afișat permite selectarea celor două produse (ansamblul pe de o parte și subansamblul pe de altă parte) din cele două list_box-uri prezente.

A doua opțiune oferă posibilități mai avansate de calcul. Utilizatorul are posibilitatea să precizeze pentru mai multe produse cantitatea necesară pentru fiecare și să obțină cantitatea de subcomponente sau repere necesare obținerii cantității propuse.

Facilitățile de calcul :

În dialogul mai complex care apare, utilizatorul are la dispoziție patru seturi de list_box-uri grupate două câte două, cu următoarea semnificație și utilitate:

-primul set conține identificatorii și numele tuturor componentelor, din care utilizatorul va alege doar pe acelea care îl interesează.

-al doilea set păstrează componentele selectate din primul, fiecărei componente selectate putândui-se edita numărul de bucății dorite.

-al treilea set conține identificatorii și denumirile tuturor subansamblelor sau reperelor din care utilizatorul va selecta pe cele dorite.

-al patrulea set păstrează componentele selectate din cel precedent, și în plus pentru fiecare componentă numărul de bucății calculat.

Toate aceste șase facilității pot fi accesate altfel decât prin selectarea opțiunilor corespunzătoare din meniuri, prin acționarea celor șase butoane prezente în forma principală. Fiecare buton are atașată o imagine ce se dorește sugestivă pentru genul de operație dorit.

=== DIP_N ===

1.Medii RAD

1.1 Introducere

Ca orice activitate productivă și aducătoare de profit, producția de software a fost în permanență preocupată de problema productivității. Mutațiile survenite în domeniul tehnologiei informaticii, dintre care enumerăm pe cele mai importante: generalizarea interfețelor utilizator grafice, impunerea arhitecturii Client/Server, orientarea către sisteme deschise și conectivitate la toate nivelele, au pus proiectanții de aplicații software în fața unor cerințe cu totul noi.

Dezvoltarea de aplicații care să utilizeze o interfață utilizator grafică(GUI) este radical diferită față de dezvoltarea tradițională. Controlul fluxului de execuție este cedat utilizatorului, iar aplicația, până acum monolitică, se divide în mai multe subprograme care trebuie să-și păstreze o anumită independență pentru că ar putea fi apelate în orice moment.

Mai mult, arhitectura Client/Server rupe total serviciile de procesare de date cele mai importante de serviciile de prezentare și interfață cu utilizatorul, distribuindu-le pe mașini diferite.

1.2 Caracteristicile unui mediu RAD

Aceste schimbări de viziune au dus la creșterea explozivă a complexității aplicațiilor, și primul lucru care a fost clar a fost inadecvarea vechilor metode și unelte la noile cerințe. În acest moment, industria de soft-ware vine în sprijinul proiectanților prin ceea ce se cheamă RAD(Rapid Application Development).

Vom schița în linii mari structura unui instrument RAD tipic:

Mediu integrat de dezvoltare

Era nevoie în primul rând de un mediu de lucru integrat, care să permită dezvoltarea aplicației de la început și până la sfârșit și care să permită accesul la toate componentele aplicației.

Instrumente vizuale

Cu ajutorul acestora se pot desena ferestre de dialog, forme de intrare, machete de rapoarte. Plasarea controalelor specializate se face prin drag&drop , iar definirea atributelor(culoare, etichetă, evenimente, sursa de date).

Limbajul

Oricât de departe ar merge însă facilitățile vizuale oferite, ele nu elimină nevoia folosiri unui limbaj de programare. De regulă acest limbaj este mai mult sau mai puțin orientat obiect, poate dispune de compilator de cod adevărat(ObjectPascal-Delphi) sau admite doar o pseodocompilare.

Conectivitatea

Suportul pentru conectarea la surse diverse de date este extrem de important pentru instrumentele RAD. Majoritatea acestor instrumente oferă conectivitate către principalele servere de date existente pe piață. Pentru a permite proiectanților să dezvolte și mai ales să testeze aplicațiile client/server în mod off-line, multe dintre produse livrează și o versiune locală a unui server de baze de date (InterBase pentru Delphi).

2.Delphi

2.1 Generalități

Delphi este un astfel de mediu rapid de dezvoltare a aplicațiilor (RAD), bazat pe Microsoft Windows.

Un compilator extrem de rapid și un limbaj obiectual evoluat și revăzut, capabil să creeze executabile independente și module DLL au creat premizele deveniri RAD a mediului TurboPascal.

Conceperea unui mediu vizual de dezvoltare deosebit de puternic și elastic bazat pe componente vizuale ca obiecte reutilizabile, dezvoltarea unei biblioteci de aproape o sută de componente de bază, introducerea de componente cu suport pentru baze de date locale și partajate în rețea ca și de arhitecturi client/server au completat ceea ce în final s-a numit Delphi.

În Delphi editorul vizual este strâns legat de editorul de cod. Acestea sunt permanent sincronizare și, chiar dacă relația nu este în ambele sensuri, conceptul este cel de lucru alternativ în ambele planuri, cu accent pe editorul vizual care pilotează scrierea procedurilor de cod specific ca rutine de tratare a evenimentelor.

Entitățile cu care operează editorul vizual corespund la obiecte de tip component în codul Pascal. Numai clase neabstracte derivate din tipul TComponent pot fi integrate în biblioteca de componente a editorului vizual.

Aplicația este văzută ca un ansamblu de forme: ferestre și dialoguri Windows, care ocupă un rol central în arhitectura programelor. Forma este entitatea care cuprinde celelalte componente și al cărei scop global permite o viziune completă a interacțiunii acestora și facilitează obținerea unei funcționalității specifice. Reflectarea formei în cod se face ca o unitate distinctă (Pascal unit) care declară în partea de interfață o clasă de tip TForm având drept câmpuri obiectele componente. Noua formă conține o apreciabilă doză de funcționalitate prin cele câteva zeci de metode private și publice moștenite de la TForm ca și prin comportamentul standard al obiectelor componente care asigură aspectul și controlul interacțiunilor dintre componente.

Multe din cererile tradiționale ale programării sub Windows sunt satisfăcute prin biblioteca de componente Delphi.

Delphi conține unelte de proiectare de genul template-urilor pentru aplicații și forme, astfel că se pot crea și testa foarte rapid diverse prototipuri de aplicații. Apoi, utilizând componentele și generatorul de cod, prototipurile se transformă în aplicații robuste care se potrivesc cerințelor pieței. Facilitățile oferite în domeniul bazelor de date permit dezvoltarea de puternice aplicații cu baze de date, desktop si client/server.

Delphi nu este numai un mediu vizual pentru crearea de aplicați ce folosesc componentele standard, el include și posibilitatea de a crea noi componente ce vor fi folosite de aplicații în același mod ca și cele standard, folosind ObjectPascal.

Ce sunt componentele ?

Componentele sunt “cărămizile” unei aplicații Delphi. Adeseori ele reprezintă partea vizibilă a interfeței aplicației dar pot reprezenta și elemente non-vizuale, așa cum sunt de exemplu “timer-ele” sau “database”.

Sunt trei nivele importante în abordarea componentelor: funcțional, tehnic și practic.

Definiția funcțională

Din perspectiva utilizatorilor, o componentă este ceva ce se alege de pe paletă și este folosită în aplicație prin manipularea ei în FormDesigner formei sau prin cod. Din perspectiva proiectantului de componente, ea este pur și simplu un obiect. Adeseori sunt câteva restricții reale asupra ce poate fi făcut atunci când se programează o componentă, și este bine să fie clar la ce se așteaptă utilizatorii finali când folosesc această componentă.

Definiția tehnică

La un nivel simplist, o componentă este un obiect descendent din tipul TComponent. Acest tip de bază definește cea mai elementară comportare pe care toate componentele trebuie să o aibă, așa cum este abilitatea de a apărea în paleta de componente și de a opera în FormDesigner.

Definiția practică

O componentă este un element care poate fi încorporat în mediul de dezvoltare Delphi. Poate reprezenta aproape orice nivel de complexitate, de la o simplă componentă standard până la o interfață complexă cu o altă platformă hard-ware sau cu un alt sistem soft-ware. Pe scurt, o componentă poate fi orice ce se poate scrie în cod, atât timp cât se potrivește cadrului general al componentelor.

2.2 Suportul pentru baze de date

Delphi intrinsec nu are încorporat nici un fel de suport pentru baze de date. Totuși componentele specializate în acces la baze de date livrate în standard sunt atât de complete încât transformă efectiv dezvoltarea unei forme într-o familiară editare de machetă cu tabel și query-uri în cele mai variate relații de one to many, many to many, one to many to many to … etc , cu controale din cele mai diverse, de la câmpuri de editare cu validare la câmpuri calculate, de la tabele cu editare in place la câmpuri lookup combobox sau list box. Nu lipsește nici clasicul navigator cu butoane de înainte-înapoi, ștergere, inserare, actualizare, etc.

Dezvoltatorul conține un control total asupra interacțiunilor dintre utilizator și baza de date prin intermediul unui set complet de evenimente generate de obiecte dataset (tabele și query-uri) si datasource (interfață între dataset și câmpurile de editare). Există astfel posibilitatea de a efectua validări înainte de actualizarea modificărilor în tabelă, de a primi controlul înaintea inserării sau ștergerii de înregistrării sau la prima tentativă de modificare a înregistrării curente. Iar lista posibilităților rămâne deschisă.

Filosofia în manipularea tabelelor este inspirată din Paradox prin aceleași operații precum Insert, Edit, Cancel, Post, Next, First, Last, etc. Câmpurile unei înregistrării sunt accesibile ca obiecte componente distincte prin intermediul editorului de câmpuri al tabelei, existând tipuri distincte pentru fiecare format, inclusiv imagine, memo sau câmp binar BLOB. Adesea singura operațiune cu câmpuri efectuată prin program este atribuirea sau citirea valorii unui câmp. Accesul se face prin intermediul proprietății Value și menținerea sincronizării câmpului cu controlul de editare asociat sau cu tabela se face automat prin metodele moștenite.

În spatele scenei se găsește o implementare deosebit de puternică a accesului la bazele de date. Obiectul tabelă tratează în mod unitar atât o tabelă Paradox sau dBase cât și una SQL. Nu este necesară nici un fel de modificare în cod sau în proprietățile tabelei pentru a muta o aplicație de pe o bază de date locală pe o bază de date echivalentă de pe un server SQL. Nu există de asemenea nici o limitare în lucrul simultan cu tabelele provenite din surse diferite. Pe aceeași formă pot coexista tabele SQL si un spreadsheet Excel provenit dintr-o sursă ODBC. Remarcabilă este și aducerea la un numitor comun a diverselor specii de index, programatorul utilizând transparent un index secundar Paradox, un index dBase sau unul SQL.

Întrucât editoarele de componente nu dispun de facilității de creare, editare sau interogare de baze de date în scopuri de testare a aplicațiilor, Delphi este însoțit de DatabaseDesktop – un paradox miniatural capabil să creeze și restructureze tabele Paradox și dBase dar și SQL, fără capabilității de forme și rapoarte – evident.

Doar utilizarea tabelelor, chiar cu filtre și legate în relații complexe, nu mai satisface adesea nici măcar în scopuri de editare. Iar a interoga navigând prin tabele și testând relații a devenit nu numai desuet și neproductiv dar chiar generator de bug-uri.

Utilizarea obiectelor tip TQuery deschide perspective remarcabile către cele mai diverse scheme de prelucrare sau editare a datelor. Query-urile sunt în esență obiecte de execuție a unei comenzi SQL asupra unor tabele putând proveni din surse eterogene, Paradox, dBase, SQL sau ODBC. Fiind un descendent al tipului TDataSet query-ul are numeroase trăsături comune cu tabela, putând fi integrat în relații, navigat, chiar editat în anumite condiții când se comportă ca un dynaset și actualizează tabelele de proveniență a datelor. Evident, comenzile SQL admit variabile care permit schimbarea dinamică a rezultatelor query-ului de valori din înregistrarea curentă a altei tabele sau query.

Un obiect specializat în copierea și adăugarea de dataset-uri, TBatchMove, permite înlănțuirea query-urilor atunci când nu se poate ajunge la rezultatul dorit printr-o singură interogare SQL.

Dezvoltatorii nefamiliari cu SQL pot folosi DatabaseDesktop pentru a construi vizual un query-by-example pe care îl pot traduce apoi în comandă SQL și integra în obiectul query, dacă nu posedă cumva versiunea client/server care include un editor vizual de query-uri.

Utilizarea de query-uri reduce dramatic timpul de dezvoltare a unei baze de date și creează premize pentru migrarea acesteia pe suport SQL. Folosirea lui baze de date locale simplifică dezvoltarea, dar asta se plătește scump. Astfel, crearea unui obiect query necesită cam 100 Kbytes pe când un obiect tabelă se mulțumește cu câțiva Kbytes.

Cine este responsabil de interpretarea și execuția quey-ului, ca și de acces la baze de date în general? Borland și-a bazat produsul pe al său BDE( Borland Database Engine) care este o implementare a standardului IDAPI – alternativă viabilă la ODBC. Prin BDE este posibil accesul atât la surse native cum sunt Paradox și dBase precum și la conexiuni ODBC pentru interpretează și execută comenzile SQL, cât și la servere SQL, cărora le transmite cererile SQL și optimizează modul de acces la date. Din punct de vedere al performanței pe baze de date locale, driver-ele native Paradox și dBase sunt net mai rapide decât cele similare disponibile prin ODBC, astfel încât acesta din urmă rămâne ca alternativă de conectare la formate mai ciudate de tabele.

Borland integrează în produsul său și LocalInterbaseServer, o versiune locală de server SQL, mono-utilizator și multi-instanță, în scopul declarat de testare locală de aplicații Delphi pe baze de date SQL înaintea scalării acestora pe servere reale Interbase, Oracle, Sysbase sau Informix.

Numeroase sunt extensiile de baze de date care pot fi regăsite în ultima versiune Delphi 2 și care ușurează considerabil surmontarea problemelor specifice ridicate de proiectele de baze de date .

Dicționarul de date stochează și utilizează informația despre conținutul și comportamentul datelor din tabele. Aici se pot specifica atribute extinse de câmpuri precum valorile minimă, maximă și implicită, opțiuni de formatare în afișare și editare. Este locul ideal pentru a stabili și asigura integritatea datelor. Formele în care urmează să fie utilizate vor prelua instantaneu caracteristicile și vor stabili conexiunile la selectarea câmpurilor de date.

Componentele de acces la bazele de date au fost rescrise în întregime păstrând însă interfața versiunilor precedente. Astfel, tabelele și query-urile sunt completate cu proprietății și evenimente de filtrare dinamică a datelor și oferă evenimente suplimentare pentru tratarea extinsă a erorilor. Există o proprietate care permite utilizarea facilității de stocare în cache a modificărilor. Tabelele pot face uz de tehnica specială BDE de filtrare a datelor printr-o expresie de tip SQL care garantează obținerea unui set editabil de înregistrări, cu minimum de consum de memorie.

Paleta de acces la baza de date s-a îmbogățit cu o formă specială de query -TUpdateSQL – care preia opțiunile de ștergere, inserare și actualizare de înregistrări spre deosebire de clasicul TQuery, care este rezervat acum doar pentru operațiuni de interogare. Remarcabil este editorul vizual de compunere rapidă a frazei SQL, inclus în toate versiunile pachetului spre deosebire de editorul lui TQuery.

De o atenție deosebită se bucură și componentele vizuale de prezentare și editare a datelor. Obiectele DBLookupCombo și DBLookupList sunt păstrate numai pentru compatibilitate; înlocuitoarele lor mai versatile și mai performante sunt acum DBLookupComboBox DBLookupListBox. Omniprezentul DBGrid este semnificativ îmbunătățit cu facilități de formatare la nivel de coloană și include tehnici de căutare și look-up în câmpul curent. Un nou componente – DBCtrlGrid permite prezentarea mai multor înregistrări dintr-o tabelă, fiecare având rezervat propriul spațiu de afișare în care se pot plasa toate celelalte tipuri de controale de date pentru editarea datelor. Afișarea unei liste de imagini dintr-o tabelă se poate face astfel fără a mai scrie vre-o linie de cod.

În actuala versiune a bibliotecii de componente, ReportSmith este înlocuit cu QuickReport. Acesta reprezintă un set de 11 componente care se integrează perfect cu componentele de acces la bazele de date, dar pot prelua datele și din vectori liste sau orice fel de variabile. Rapoartele se redactează sub forma clasică de benzi care pot include titluri, câmpuri calculate, de însumare și de sistem, dar și imagini bitmap sau matafile ori forma geometrice simple. Sunt posibile rapoarte master-detail pe mai multe nivele sau cu mai multe seturi detail și grupate pe criterii foarte diverse, inclusiv câmpuri calculate. Benzile pot reprezenta seturi detail, antete sau subsoluri de pagină, grup sau raport și pot fi organizate pe mai multe coloane sau în format de etichete multiple, iar calculele pot fi inițializate la nivel de bandă. Datele se pot previzualiza în faza de design iar un comportament special permite previzualizarea lor în timpul rulării. Pentru baze de date mici QuickReport este cu un ordin de mărime mai rapid decât ReportSmith.

Noua versiune pe 32 de biți a lui Borland Database Engine comportă o arhitectură obiectuală care permite un acces simplu și nativ din limbajele obiectuale la funcții încorporate de un nivel foarte înalt, de la operațiuni cu seturi de date prin interogări SQL și filtre până la suport navigațional complet prin relații master-detail și lookup.

Noua concepție pe 32 de biți se reflectă în suportul pentru multitasking preemtiv: mai multe programe pot fi deservite simultan de BDE și pot accesa aceeași bază de date în același timp. În plus, în cadrul aceluiași program se pot executa simultan mai multe operațiuni BDE separate în fire de execuție diferite. Este posibilă astfel execuția de query-uri multiple în spate în timp ce în față utilizatorul editează o tabelă.

Accesul la bazele de date de pe server se poate efectua acum prin utilizarea convenției numelui universal preluată de la Windows 95.

Performanța interogării SQL pe tabele desktop a crescut considerabil și au căzut o serie de restricții din subsetul LocalSQL, care se aproprie acum și chiar depășește standardul SQL92. Deasemenea o îmbunătățire substanțială este suportul tranzacțional complet pentru tabele Paradox și dBase. Modificările tabelelor pot fi grupate într-o tranzacție și efectuate sau anulate integral, asigurând actualizarea bazei de date într-o manieră consistentă și cu păstrarea integrității referențiale.

Facilitarea de efectuare locală a modificărilor (cached updates) permite utilizatorilor să efectueze operații asupra bazei de date într-o perioadă mai lungă de timp fără a modifica imediat baza de date de pe server, reducând la minim consumul de resurse pe server ca și traficul pe rețea.

Dezvoltatorii de aplicații client-server SQL vor aprecia posibilitatea de a monitoriza frazele SQL care se transmit serverului la fiecare execuție de funcție BDE, ca și utilizarea de guvernatori care limitează numărul de înregistrări din seturile de date returnate de server în scopuri de accelerare a procesului de dezvoltare.

Prin toate acestea, Borland a livrat un produs remarcabil, destinat realizării de aplicații vitale pentru un sistem de operare recunoscut pentru limitările și instabilitatea sa. Dincolo de paleta bogată de facilități pe care o posedă, Delphi asigură aplicațiilor produse o excepțională siguranță și stabilitate în rulare, mediul însuși bucurându-se de o fiabilitate remarcabilă.

3. Dezvoltarea aplicațiilor cu baze de date în Delphi

Construirea unei aplicații cu baze de date este similară cu construirea oricărei alte aplicații în Delphi. Tehnicile de bază ale dezvoltării de astfel de aplicații sunt, pe scurt :

– Crearea și gestionarea proiectelor.

– Crearea formelor și gestionarea "unit-urilor".

– Lucrul cu "componente","proprietății" și "unit-uri".

– Scrierea de cod Object Pascal.

O aplicație cu baze de date este realizată utilizând uneltele de dezvoltare , componentele de acces la date și componentele interfeței grafice. Ea folosește aceste componente să comunice cu BDE care la rândul lui comunică cu baza de date fizică. Următoarea figură ilustrează relațiile dintre uneltele Delphi și aplicațiile baze de date cu BDE și baze de date fizice :

.

3.1 Uneltele de dezvoltare

Vom clasifica aceste unelte și le vom descrie pe scurt :

Data Acces Component – Accesează baze de date, tabele și proceduri stocate.

Data Acces Control – Realizează interfața utilizator cu baza de date.

Database Desktop – Creează, indexează și interoghează baze de

date Paradox, dBase și SQL.

Report Smith – Creează, vizualizează și tipărește rapoarte.

BorlandDatabaseEngine(BDE) – Accesează date de pe fișiere Paradox sau dBase precum și de baze de date ale server-ului Interbase.

BDE Configuration Utility – Creează și gestionează conectările la

bazele de date folosite de BDE.

Aceste caracteristici permit utilizatorului să construiască aplicații cu baze de date care sunt conectate prin intermediul BDE la tabele Paradox,dBase sau Local InterBase Server. În multe cazuri se pot crea aplicații simple de acces la date fără să se scrie o singură linie de cod. Aplicațiile nu trebuie să știe nimic despre BDE acesta fiind integrat în componente.

Posibilitățile avansate sunt folosite de programe ce presupun o mai mare funcționalitate. Aceste posibilității includ: Local SQL – un subset al lui industry-standard SQL care permite efectuarea de interogări SQL asupra bazelor de date, funcții API de nivel jos care permit acces direct la BDE, suport ODBC.

Delphi folosește componente orientate obiect să creeze aplicațiile cu baze de date așa cum procedează și cu aplicațiile non baze de date Componentele pentru baze de date au, ca și cele standard, proprietății (atribute) care pot fi setate fie în faza de proiectare fie în timpul rulări programului Aceste componente au setări inițiale care le induc un comportament ce permite o folosire a lor cât mai eficientă cu cât mai puține linii de cod.

Paleta acestor componente prezintă două seturi :

Data Access page – conține obiectele ce accesează bazele de date prin încapsularea de informații cum ar fi: numele bazei de date la cere să se conecteze, numele tabelei accesate, câmpuri specifice ale ei. Cele mai frecvent utilizate astfel de obiecte sunt : TTable, TQuery, TDataSource, TReport.

Data Controls page – conține obiectele ce realizează interfața cu baze de date pentru afișarea și modificarea acestora în forme. Aceste componente sunt derivate din cele standard. Exemple: TDBEdit, TDBNavigator,TDBGrid.

Componentele din Data Acces page nu sunt vizibile în timpul rulării dar oferă aplicației legătura cu BDE. Cele din Data Controls page se atașează la o componentă numită TDataSource și realizează interfața cu tabelele.

Figura următoare ilustrează cum aceste componente relaționează cu datele, intre ele sau cu interfața, într-o aplicație Delphi cu baze de date :

Așa cum se vede, o formă conține cel puțin trei componente: TTable sau TQuery care comunică cu BDE, TDataSource care acționează ca o conductă intre primele două și componentele de control, și în final, unul sau mai multe componente de control de genul TDBGrid, TDBNavigator, ce permit utilizatorului final să editeze datele.

Data Access Page

Când se construiește o aplicație cu baze de date, se pun componentele de acces la date pe forma aplicației, apoi se setează proprietățile ce specifică baza de date, tabela și datele de accesat. Ele realizează o legătură între sursa de date si componentele de control ale datelor. În timpul rulării programului, aceste componente nu sunt vizibile, ele fiind mascate, continuând însă să gestioneze accesul la date.

Mai jos sunt prezentate pe scurt componentele acestei "pagini":

TDataSource – Acționează ca o conductă între componentele TTable, TQuery, TStoredProc si componentele de control, de exemplu TDBGrid.

TTable – Primește date de la o tabelă baze de date prin intermediul lui BDE și apoi furnizează aceste date componentelor de control prin intermediul lui TDatSource.

TQuery – Utilizează interogării SQL pentru recepționarea datelor dintr-o tabelă prin intermediul lui BDE sau pentru trimiterea de noi date către o tabelă, tot prin intermediul lui BDE.

TStoredProc – Permite unei aplicații să acceseze procedurile stocate pe un server.

TDataBase -Setează o legătură persistentă cu o bază de date, în special o cu bază de date aflată la distanță.

TBatchMove – Copiază structura sau datele dintr-o tabelă. Poate fi folosită pentru mutarea unei tabele întregi dintr-un format într-altul.

TReport – Permite vizualizarea și tipărirea de rapoarte prin intermediul lui ReportSmith.

Data Controls page

Această "pagină" oferă un set de componente de interfață pentru controlul datelor prin care se pot crea aplicații cu baze de date bazate pe forme.

Multe dintre aceste componente sunt versiuni ale componentelor standard (TDBEdit,TDBComboBox,etc). În plus față de funcționalitățile standard, aceste componente pot afișa datele dintr-o tabelă sau pot trimite datele modificate înapoi spre baze de date.

Lista următoare prezintă o scurtă descriere a acestor componente:

TDBNavigator – Un buton de navigație prin tabelă care poate muta pointer-ul curent al tabelei înainte sau înapoi, poate pune sau scoate tabela din starea de inserare sau de editare, poate reactualiza afișările pentru a prezenta datele curente.

TDBText – Poate afișa un câmp al unei înregistrări curente active.

TDBEdit – Poate afișa sau edita un câmp al unei înregistrării curente și active.

TDBCheckBox – Poate edita sau afișa un câmp de tip boolean al unei înregistrării.

TDBListBox – Afișează valorile unei coloane dintr-o tabelă.

TDBComboBox – Afișează sau editează datele dintr-o coloană.

TDBGrid – Un grid în care se pot afișa sau edita datele în formă tabulară, similar cu "spreadsheet"

TDBMemo – Afișează sau editează câmpuri memo.

TDBImage – Poate afișa, tăia sau copia câmpuri de tip BLOB (binary large object) reprezentând imagini bitmap.

TDBLookupList – Similar cu TDBListBox, poate afișa valori ale unor câmpuri legate dintr-o altă tabelă.

TDBLookupCombo – Similar cu TDBLookupList, permițând și editarea.

Aceste componente pot realiza o consistentă interfață vizuală pentru aplicațiile Delphi cu baze de date, indiferent dacă acestea accesează o bază de date locală sau una aflată la distanță, pe un server.

DatabaseFormExpert

Această unealtă automatizează multe din sarcinile necesare pentru crearea unor forme de editare de date sau de forme tabulare pentru o bază de date existentă. Poate genera forme simple sau master-detail folosind componentelor TQuery sau TTable.

Utilizatorii neexperimentați pot folosi acest DatabaseFormExpert pentru a învăța cum să construiască forme cu baze de date, iar cei experimentații pentru rapiditatea dezvoltării.

DatabaseDesktop

DatabaseDesktop este o unealtă de întreținere și definire a bazelor de date. El permite interogarea, crearea, restructurarea, indexarea și modificarea tabelelor baze de date de tip Paradox, dBase sau SQL. Tot el permite copierea de date dintr-un format într-altul. Astfel este posibil să se copieze o tabelă Paradox dintr-o bază de date existentă către o bază de date aflată la distanță pe un server SQL.

3.2 Dezvoltarea de aplicații pentru servere locale și la distanță

Delphi Client / Server permite programatorilor să dezvolte aplicații atât pentru baze de date locale cât și pentru baze de date aflate pe servere la distanță. Unul din punctele tari ale lui Delphi este ușurința cu care o aplicație proiectată să lucreze cu baze de date locale poate fi adaptată să acceseze date aflate pe un server SQL la distanță. Interfața utilizator nu necesită schimbări chiar dacă sursa datelor se schimbă. Pentru un utilizator final o astfel de aplicație arată la fel chiar dacă accesează date locale sau la distanță.

Pentru aplicațiile simple care folosesc TQuery ca să acceseze datele locale, tranziția către un server la distanță este la fel de simplă ca și operația de schimbare a sursei de date. Pentru alte aplicații, mai trebuie făcute câteva schimbări semnificative. Câteva dintre aceste schimbări sunt rezultatul diferitelor convenții și modelele concurente dintre baze de date locale și cele de pe un server SQL.

Spre exemplu, baze de date locale de tip Paradox sau dBase sunt orientate pe înregistrare. Ele întotdeauna afișează înregistrări în ordine numerică sau alfabetică. Ele accesează o singură înregistrare la un moment dat. De fiecare dată când un utilizator modifică o înregistrare, schimbarea este imediat înregistrată de baza de date. Utilizatorii pot vedea un întreg domeniu de înregistrări și pot naviga înainte și înapoi prin acest domeniu.

În contrast, bazele de date SQL sunt orientate pe seturi de înregistrării, și proiectate pentru accesul simultan al mai multor utilizatori. Ordinea înregistrărilor trebuie să fie specificată in corpul interogației SQL. Pentru a suporta accesul simultan , SQL apelează la tranzacții să gestioneze accesul la date.

3.2.1 Metodologia dezvoltări de aplicații

Construirea de aplicații în Delphi este similară cu construirea altor tipuri de soft-ware, dar există importante distincții și schimbări ce trebuie prezentate.

Dezvoltarea scenariului

Odată ce proiectarea aplicației depinde în mod normal de structura bazei de date accesată, baza de date trebuie definită înainte ca aplicația să fie dezvoltată.

Sunt patru scenarii posibile care pot fi întâlnite în dezvoltarea de aplicații Delphi :

– Baza de date nu există sau trebuie redefinită :

– Dacă lucrăm cu baze de date locale ca Paradox sau dBase, trebuie folosite utilitățile DatabaseDesktop.

– Dacă avem baze de date SQL, trebuie folosite uneltele oferite tot de DatabaseDesktop.

– Baza de date există local și va fi accesată acolo. Dacă BDE și baza de date sunt pe aceiași mașină, atunci aplicația va fi "standalone", adică non client/server.

– Baza de date există local și este accesată de pe un server SQL

– Baza de date se află pe un server SQL la distanță și va fi accesată de acolo. Aceasta este o aplicație client/server standard.

Ciclul de dezvoltare al aplicației

Scopul dezvoltării unei aplicații cu baze de date este să se producă un produs care să satisfacă cerințele pe termen lung ale utilizatorilor. Atâta timp cât acest scop pare evident, este important să nu se piardă din vedere întreaga complexitate și desele conflicte apărute în procesul de dezvoltare. Pentru a crea o aplicație reușită este obligatoriu să se definească cât mai detaliat, încă din faza de proiectare a aplicației, cerințele utilizatorilor finali.

Cele trei stadii principale ale dezvoltării de aplicații sunt:

– Proiectare si modelare.

– Implementare.

– Transferare și întreținere.

Sunt diferite procese ale bazelor de date și ale aplicației în fiecare dintre aceste faze. În funcție de mărimea și scopul proiectului dezvoltat, aceste procese sunt realizate fie de mai mulți indivizi fie de unul singur. De obicei o singură echipă sau un singur individ se ocupă de procesele bazelor de date și cealaltă echipă sau celălalt individ de cele ale aplicației.

Pentru aplicațiile client/server, procesele bazelor de date și ale aplicațiilor devin mai distincte din moment ce ele rulează pe platforme diferite, deseori cu sisteme de operare diferite.

Când responsabilitatea dezvoltări este astfel împărțită, este important să se clarifice din faza de proiectare care funcții vor fi realizate de către serverul baze de date și care vor fi realizate de către aplicația client. În general liniile de demarcație sunt bine trasate, dar procesele bazelor de date de genul procedurilor stocate, pot face funții care pot fi realizate și de aplicația client. Depinde de configurația dezvoltării, de cerințele aplicației și de alte considerente, care dintre aceste funcții sunt alocate serverului și care aplicației client.

Este de asemenea important de realizat că dezvoltarea de aplicații baze de date este prin natura sa un proces iterativ. Este posibil ca utilizatorii să nu-si poată înțelege pe deplin propriile cerințe, sau să adauge noi cerințe pe parcursul dezvoltării programului. Elementele interfeței utilizator sunt întodeauna redefinite în funcție de cerințe. În general, un număr de iterații prin ciclul dezvoltării este întâlnit înainte ca o aplicație să poată îndeplinii vre-o parte din cerințele sale.

Faza de proiectare

Această faza începe cu definirea cerințelor aplicației. În consultanță cu utilizatorii finali cunoscători, se definesc funcționalitățile pentru bazele de date și pentru aplicația client. Se determină care aspecte ale funcționalității vor fi implementate în proiectarea bazei de date și care aspecte vor fi implementate în proiectarea aplicației.

Pentru aplicațiile client/server, deseori unele funcții pot fi realizate atât de server cât și de aplicație; de exemplu o funcție matematică complexă de transformare poate fi realizată de aplicația client sau de către o procedură stocată de pe server. Configurația hard-ware a platformei pe care va rula produsul, determină în general când aceste funcții sunt realizate mai performant de server sau de client. De exemplu dacă platforma client este de așteptat a fi un desktop PC și platforma server să fie o stație mai puternică, atunci este probabil cel mai bine ca funcțiile cele mai puternice și complicate să ruleze pe server. Dacă platforma hard-ware se schimbă, atunci este posibil să se mute funcțiile dintre client și server într-o altă iterație.

Faza de implementare

În această fază se construiește și se testează aplicația proiectată în faza de design. În timpul acestei faze trebuie folosită o sursă de date duplicat, care este o sursă de date ce are aceiași structură esențială ca și baza de date principală, dar cu un mic subset de date reprezentative. Nu este recomandat să dezvoltăm o aplicație direct pe o bază de date productivă întrucât o aplicație netestată ar putea altera datele sau orice altceva ce interferează cu baza de date.

Dacă în final aplicația va fi furnizată să folosească o sursă de date de pe un desktop, trebuie făcute copii ale tabelelor folosite de aplicație si populate cu date semnificative dar neimportante.

Dacă aplicația va fi furnizată să folosească o sursă de pe un server îndepărtat(un server SQL), atunci există două posibilității în timpul implementării:

– Dezvoltarea și testarea aplicației pe o bază de date non productivă pe LocalInterbaseServer.

– Dezvoltarea și testarea aplicației pe o bază de date non productivă direct pe server.

Primul mod are avantajul că este izolat pe platforma de dezvoltare și nu va interfera cu alte activității ale serverului. Nu va consuma resursele serverului sau aglomera traficul pe rețea. Primul dezavantaj este că doar facilitățile serverelor standard SQL pot fi folosite și testate în timpul acestei faze, dacă se folosește un server altul decât Interbase pentru furnizarea aplicației.

A doua posibilitate permite folosirea tuturor facilităților specifice ale serverului, dar va consuma resursele serverului și ale rețelei în timpul testării. Acest mod poate fi periculos, dacă ne gândim că o eroare de programare poate cauza “căderea” serverului în momentul testării.

Faza de furnizare

În această fază aplicațiile client/server sunt puse în fața testului final : folosirea lor de către utilizatorii finali. Pentru a ne asigura că funcționalitatea de bază a aplicației nu prezintă erori, trebuie furnizat un prototip al acesteia înainte de a furniza aplicația propriu zisă.

Întrucât ultimii judecători ai eficacității unei aplicații sunt utilizatorii finali, dezvoltarea trebuie să fie pregătită să încorporeze schimbările în aplicație sosite de la aceștia. Uneori schimbările în aplicație pot necesita schimbări în structura bazelor de date, și invers, schimbări la nivelul bazelor de date pot genera modificări în aplicație. Pentru acest motiv dezvoltarea aplicației și dezvoltarea bazelor de date trebuie să conlucreze strâns în această fază.

Furnizarea unei aplicații client/server necesită atingerea unor subiecte speciale cum ar fi includerea conectivității si accesul multi-utillizator.

Furnizarea unei aplicații

Aceasta înseamnă predarea aplicației utilizatorilor finali și furnizarea soft-ware-lui necesar să execute aplicația în mediul de producție. Aplicațiile non baze de date nu necesită decât un fișier .EXE de executat, aplicațiile Delphi nu necesită un interpretor sa un DLL.

În mod normal când furnizăm o aplicație baze de date , vom crea un pachet care include fișierele de care utilizatorii finali au nevoie pentru a rula aplicația. Aceste fișiere includ:

– Fișierul .EXE al aplicației și fișierele .DLL(dacă există).

– Fișiere auxiliare (un fișier Readme sau un .HLP)

– Suport BDE pentru accesul la date(desktop sau server)

– ReportSmith Runtime pentru rularea și tipărirea rapoartelor.

– Dacă aplicația folosește controale VBX, trebuie incluse fiecare VBX împreună cu Bivbx11.Dll

Pentru aplicațiile mai complexe este bine a se folosi un program de instalare a aplicațiilor.

Furnizarea suportului BDE

Când furnizăm o aplicație cu baze de date, trebuie să ne asigurăm că platforma client are versiunea corectă a BDE instalată. Delphi include Redistributable BDE, care se instalează și care poate fi distribuit odată cu aplicația.

3.2.2 Ierarhia componentelor pentru baze de date

Această ierarhie este importantă pentru a arăta proprietățile, metodele și evenimentele moștenite de componente de la cele precedente lor. Cele mai importante componente pentru baze de date sunt:

– TSession , o componentă globală creată automat în momentul rulării programului. Nu este vizibilă pe formă nici în timpul proiectării nici în timpul execuției.

-TDatabase , componentă ce prezintă un nivel adițional de control al legării la un server, control al tranzacției și alte facilități. Apare în pagina Data Acces.

-TDataSet și descendenții săi : TTable și TQuery, la care ne vom referi colectiv prin componente ”dataset”.

-TDataSource, o conductă între componentele ”dataset” și cele de control.

-TField, componente ce corespund coloanelor bazei de date, create dinamic de Delphi în timpul execuției sau în faza de proiectare folosind Fields Editor. Câmpurile de editare și în general componentele din DataControl page, folosesc aceste componente pentru a accesa datele din tabele. În plus se pot defini câmpuri ”calculate”, ale căror valori se determină în funcție de valorile altor câmpuri.

Ierarhia componentelor :

Componenta TSession

Delphi creează o astfel de componentă numită ”Session” de fiecare dată când o aplicație se execută. Nu se poate explicit crea sau vedea o astfel de componentă, dar se pot folosi metodele și proprietățile sale care afectează global aplicația.

TSession prezintă controlul global al tuturor legăturilor cu bazele de date folosite într-o aplicație. Proprietățile sale conțin informații ca: bazele de date active și numărul lor, calea directorului folosi de BDE , directorul în care vor fi puse fișierele temporare.

Metodele componentei permit și ele aflarea de alte informații în legătură cu bazele de date: numele drive-ului BDE instalat, parametri pentru drive-ul BDE, numele tuturor tabelelor dintr-o bază de date specificată.

Componentele ”dataset”

Componentele TTable și TQuery derivă din TDataSet prin TDBDataSet. Aceste componente au în comun un mare număr de proprietăți, metode și evenimente moștenite. Le vom referii împreună prin ”dataset”.

Un ”dataset” poate fi la un moment dat în următoarele stări:

Inactive, când este închis.

Browse, starea inițială când un ”dataset” este deschis. Înregistrările pot fi văzute dar nu se pot modifica sau insera altele noi.

Edit, permite editarea înregistrării curente.

Insert, permite inserarea unei noi înregistrări.

SetKey, permite metodelor FindKey, GoToGey și GoToNearest să caute anumite înregistrări după valorile lor.

CalcFields, mod în care are loc evenimentul OnCalcFields.

În general, o aplicație poate pune un ”dataset” într-o anumită stare prin executarea metodei corespunzătoare stării respective. Spre exemplu o aplicație poate pune un ”dataset” numit Table1 în starea Insert prin apelarea metodei Table1.Insert, sau în starea Edit prin Table1.Edit. Unele metode, după apelarea lor, returnează un ”dataset ” în starea Browse, în funcție de rezultatul apelării. Astfel apelarea metodei Cancel întotdeauna va returna un “dataset ” în starea Browse.

Modul CalcFields este mai special. O aplicație nu poate pune în mod explicit un “dataset” în starea CalcFields. Un “dataset” este pus automat în această stare în momentul când are loc evenimentul OnCalcFields. După acest eveniment “dataset-ul” revine în starea inițială.

Următoarea diagramă ilustrează principalele stări ale unui “daset” și metodele care cauzează trecerea lor dintr-o stare în alta.

Deschiderea și închiderea unui ”dataset”

Înainte ca o aplicație să poată accesa datele printr-un “dataset”, acesta trebuie să fie deschis. Sunt două moduri de deschidere a unui “dataset”:

– Setarea proprietății Active a “daset-ului” pe True, fie în faza de design fie în momentul execuției.(Ex : Table1.Active:=True;)

-Apelarea metodei Open (Ex : Query1.Open)

Ambele metode deschid un “dataset” și îl pun în starea Browse.

În mod similar, închiderea unui “dataset” se poate face fie prin setarea lui Active pe False fie prin apelarea metodei Close.

Navigația într-un “dataset”

Fiecare “dataset” activ are un cursor, care este un pointer la înregistrarea curentă din “dataset”. Un număr de linii de înainte și de după cursor sunt stocate de Delphi într-un buffer local. Delphi întotdeauna va reține un număr suficient de înregistrării să poată afișa înregistrarea curentă, plus un număr adițional de linii, pentru a reduce din timpul necesar pentru reactualizare în cazul în care utilizatorul face “scroll” într-un “dataset”.

Metodele și proprietățile pentru navigație :

Last (metodă) – mută cursorul pe ultima linie dintr-un “dataset”

First (metodă) – mută cursorul pe prima linie dintr-un “dataset”

Next (metodă) – mută cursorul pe următoarea linie dintr-un “dataset”

Prior (metodă) – mută cursorul pe linia anterioară dintr-un “dataset”

BOF(proprietate) -este true când cursorul se află la începutul unui “dataset”, altfel este false.

EOF(proprietate) – true când cursorul este pe ultima înregistrare, altfel false.

MoveBy(n) – mută cursorul cu n poziții mai departe într-un “dataset”, n poate fi pozitiv sau negativ.

Multe din aceste metode sunt încapsulate în componenta TDBNavigator.

Modificarea datelor dintr-un “dataset”

Următoarele metode și proprietății permit unei aplicații să insereze, modifice și să șteargă date dintr-un “dataset” :

Edit(metodă) – pune un ”dataset” în starea Edit; dacă ea era deja în această stare, nu va avea nici un efect.

Append(metodă) – comite ultima modificare, mută cursorul după ultima înregistrare și pune “dataset-ul” în starea Insert.

Insert(metodă) – comite ultima modificare și pune “dataset-ul” în starea Insert.

Post(metodă) – comite modificările făcute; dacă o face cu succes “dataset-ul” este pus în starea Browse, dacă nu, rămâne în starea inițială.

Cancel(metodă) – anulează operația curentă și pune “dataset-ul” în starea Browse.

Delete(metodă) – șterge înregistrarea curentă și pune “dataset-ul” în starea Browse.

CanModify(proprietate) – are două posibile valori: true și false care specifică când se pot modifica datele dintr-un “dataset”.

AppendRecord([șir de valori]) – adaugă o înregistrare cu valorile câmpurilor specificate, similară cu Append.

InsertRecord([șir de valori]) – Inserează o înregistrare cu valorile câmpurilor specificate, analogă cu Insert.

SetFields([șir de valori]) – Modifica câmpurile înregistrării curente setându-le cu valorile indicate.

Evenimentele corespunzătoare unui “dataset”

“Dataset-urile” înregistrează un număr de mesaje care permit aplicației să efectueze validări sau alte procese în funcție de metoda executată de “dataset”.Aceste evenimente sunt prezentate mai jos:

BeforeOpen, AfterOpen – Apelat înainte/după ce un “dataset” este deschis.

BeforeClose, AfterClose – Apelat înainte/după ce un “dataset” este închis.

BeforeInsert, AfterInsert – Apelat înainte/după ce un “dataset” intră în starea Insert.

BeforeEdit, AfterEdit – Apelat înainte/după ce un “dataset” intră în starea Edit.

BeforePost, AfterPost – Apelat înainte/după ce modificările într-un “dataset” sunt comise.

BeforeCancel, AfterCancel – Apelat înainte/după ce o operație este anulată.

BeforeDelete, AfterDelete – Apelat înainte/după ce o înregistrare este ștearsă.

OnNewRecord – Apelat când este creată o nouă înregistrare; folosit pentru setarea de valori inițiale.

OnCalcFields – Apelat când un câmp programat a fi calculat este astfel modificat.

TTable

Aceasta este una din cele mai importante componente, alături de altă componentă din clasa Tdataset, TQuery, permite unei aplicații să acceseze datele dintr-o tabelă. Vom descrie mai jos cele mai importante proprietăți care se întâlnesc numai la TTable.

Specificarea numelui tabelei ce trebuie accesată – Numele tabelei trebuie asignat proprietății TableName, fie în timpul design-ului fie în run-time. Proprietatea DatabaseName specifică unde Delphi va căuta tabela specificată. Poate fi un alias BDE, o specificație explicită sau un DatabaseName definit de o altă componentă TDataBase. Pentru tabelele Paradox sau dBase, o specificație explicită este calea directorului în care se găsește tabela; pentru o tabelă SQL este calea directorului plus numele bazei de date.

În locul specificației explicite, DatabaseName poate fi un alias BDE. Avantajul este că se poate schimba sursa datelor pentru întreaga aplicație prin simpla schimbare a definiției alias-ului din BDE Configuration Utility. Nici una din aceste două proprietăți nu poate fi schimbată atâta timp cât tabela este deschisă.

Tipul tabelei – Proprietatea TableType specifică tipul tabelei accesate. Dacă este setată “default”, extensia fișierului tabelei va determina tipul ei.

Căutarea într-o tabelă – TTable are un număr de funcții care caută anumite valori într-o tabelă :

– funcțiile GoTo.

– funcțiile Find.

Cel mai comod mod de a căuta într-o tabelă este folosirea funcțiilor Find: FindKey și FindNearest. Ele combină funcționalitatea funcțiilor GoTo: SetKey, GoToKey și FindNearest. În tabelele Paradox și dBase aceste funcții pot căuta numai după valori ale câmpurilor indexate. În tabelele SQL ele pot căuta după orice câmp, dacă acesta este specificat în proprietatea IndexFileName a lui TTable. Pentru a putea totuși căuta și în tabelele Paradox și dBase după câmpuri neindexate, trebuie folosită sintaxa SQL dintr-o componentă TQuery. Fiecare dintre aceste metode primește ca parametru un vector de valori, în care fiecare valoare corespunde unei coloane din tabelă.

Limitarea înregistrărilor accesate – O tabelă în realitate poate fi foarte mare, iar aplicațiile adeseori nu necesită decât un număr limitat de înregistrării din tabele. Următoarele metode permit unei aplicații să lucreze cu un subset de date dintr-o tabelă:

– SetRangeStart și EditRangeStart, indică precizările ulterioare asupra valorilor unui câmp ce specifică începutul domeniului de înregistrări inclus în aplicație.

– SetRangeEnd și EditRangeEnd, indică precizările ulterioare ce specifică sfârșitul domeniului de înregistrării inclus în aplicație.

– SetRange([StartValues], [EndValues]) , combină pe primele două .

– ApllyRange/CancelRange , aplică / anulează un domeniu declarat.

Indecși

Un index determină cum sunt sortate înregistrările folosite într-o aplicație. Inițial Delphi afișează datele în ordine crescătoare după valorile câmpului cheie primară.

Delphi suportă indecși SQL, indecși permanenți(maintained) pentru tabele Paradox și indecși MDX pentru tabele dBase.

Metoda GetIndexName returnează o listă a numelor indecșilor valabili ai tabelei. Pentru tabele Paradox indexul primar nu are nume și de aceea nu apare în această listă.

Proprietatea IndexFields conține un vector al numelor câmpurilor conținute într-un index. IndexName conține indexul selectat pentru a afișa datele. Cele două proprietății sunt în regim de excludere reciprocă.

Alte proprietății și metode

– EmptyTable , șterge toate înregistrările dintr-o tabelă.

– DeleteTable , șterge o tabelă.

– BatchMove , copiază date și structuri de la o tabelă la alta.

TDataSource

TDataSource acționează ca o conductă între componentele “dataset” si cele “data-aware”. Adeseori singura proprietate care este setată la această componentă este DataSet care specifică la care din “dataset-urile” existente se va lega. Pasul următor constă în setarea proprietății DataSource a fiecărui control cu numele componentei TDataSource respective. O altă proprietate este Enabled , care dacă va avea valoarea false va disconecta temporar componenta de “dataset-ul” asociat. AutoEdit specifică dacă “datase-ul” asociat va intra automat în starea Edit în momentul când un utilizator va începe să scrie într-unul din controalele legate la “dataset”.

Evenimentele asociate acestei componente sunt :

– OnDataChange , apelat ori de câte ori cursorul se mută la o altă înregistrare, adică cu alte cuvinte o aplicație cheamă Next, Insert, Previous sau orice altă metodă care schimbă poziția cursorului.

– OnUpdateData , este apelat ori de câte ori datele din înregistrarea curentă sunt pe cale de a fi reactualizate.

– OnStateChange , apelat ori de câte ori starea “dataset-ului” se schimbă.

TField

Toate componentele de control al datelor folosite de Delphi, se bizuiește pe o clasă de bază: TField. Deși nu sunt vizibile pe formă, componentele TField sunt importante deoarece ele reprezintă o legătură directă cu coloanele bazei de date. Aceste componente conțin proprietății ce specifică tipul câmpului, valoarea curentă, formatul de afișare, formatul de editare și alte caracteristici. De asemenea mai prezintă și evenimente, de exemplu OnValidate , care pot fi folosite pentru implementarea de reguli de validare.

Fiecare coloană recepționată de la o tabelă are propria-i componentă TField . Inițial aceste componente sunt generate dinamic în timpul proiectării când un ”dataset” este deschis. Ele sunt de asemenea generate și în run-time. Dacă sunt generate dinamic ele corespund întotdeauna câmpurilor bazei de date.

Pentru a genera o listă persistentă de componente TField , se poate folosi FieldsEditor. În felul acesta se garantează faptul că de fiecare dată când o aplicație rulează, folosește și afișează aceleași coloane, în aceeași ordine, de fiecare dată, indiferent dacă structura fizică a bazei de date s-a schimbat.

O altă facilitate oferită de aceste componente este posibilitatea conversiilor, ele având încorporate diferite funcții de conversie. Aceste funcții pot fi folosite în orice expresie în care sunt implicate componente TField , de orice parte a operatorului de atribuire. Valoarea efectivă a câmpului reprezentat de TField poate fi accesată prin intermediul proprietății Value a componentei respective.

Afișarea datelor

Înregistrările dintr-o tabelă se pot se pot afișa fie folosind componente standard, fie componente “data aware”, fie prin TField. Mai există două moduri de accesare în momentul rulării programului: proprietatea Fields și metoda FieldsByName, aparținând lui TTable. Ambele accesează valoarea câmpului specificat al înregistrării curente. Fiecare necesită o componentă TField dar nu și una TDataSource. Și aceste proprietății au încorporate diverse funcții de conversie.

În general este de preferat folosirea componentelor “data aware” într-o aplicație Delphi cu baze de date. Aceste componente au încorporate proprietății și metode care le permit să se conecteze la câmpurile bazei de date, să afișeze valoarea lor curentă și să le poată modifica.

Utilizarea controalelor pentru date

Așadar cel mai comod mod de afișare și editare a datelor dintr-o tabelă este folosind componentele din setul “DataControl”. Acesta conține componente pentru afișarea și editarea tuturor înregistrărilor, pentru navigarea printre înregistrării, pentru ștergerea sau inserarea lor. După ce le-am descris sumar pe fiecare în parte într-o secțiune anterioară, vom face o scurtă prezentare generalizată.

Multe dintre aceste controale sunt versiuni “data aware” ale controalelor standard. Un control “data aware” afișează date dintr-o tabelă și eventual poate comite schimbările efectuate asupra datelor. În faza de design, atunci când se conectează o astfel de componentă la un “data source” activ, se pot vedea imediat datele reale.

Controalele sunt legate la o anumită tabelă prin intermediul proprietății DataSource. Aceasta specifică numele componentei TDataSource de la care controlul primește datele. Componenta TDataSource este legată de un “dataset” care la rândul său este conectat la o bază de date. Controalele nu pot accesa decât câmpurile din tabelă pentru care există un TField corespunzător. Multe dintre ele au prin setarea proprietății DataField o legătură explicită cu un TField.

4.Prezentarea unei aplicații dezvoltată în Delphi.

4.1 Introducere

DBMS-urile convenționale sunt pasive. Ele execută numai interogații sau tranzacții care sunt emise explicit de către utilizatori sau de programe. Totuși pentru multe aplicații este important să monitorizeze situații care le interesează și să declanșeze un răspuns în timp util atunci când apare o situație. De exemplu un sisteme de control al stocurilor ar trebui să monitorizeze cantitățile de produse din baza de date inventar și să inițieze o comandă când acestea scad sub un anumit prag.

Un DBMS activ este un sistem pentru baze de date care monitorizează situațiile care îl interesează și, când acestea apar, declanșează un răspuns potrivit în timp util. Comportamentul dorit se exprimă prin reguli de producție (Eveniment – Condiție – Acțiune) care sunt definite și memorate în baza de date.

După această scurtă introducere iată tema principală a lucrării : Având date două tabele baze de date cu o anumită structură și semnificație a datelor, provenite din medii de gestiune pasive (Paradox, dBase, SQL), să se realizeze un program complet de gestionare a acestor tabele care să le dea un caracter activ. Mai precis, prin acest program să se monitorizeze anumite situații și când acestea apar să se declanșeze un răspuns adecvat în timp util.

4.1.1 Semnificația și structura tabelelor utilizate.

Vom începe cu prezentarea semnificației datelor pentru cele două tabele referite anterior.

Să presupunem că dorim să gestionăm o mulțime de componente (obiecte) fiecare componentă putând fi formată la rândul său din altele; avem așadar o structură ierarhică a componentelor. Pentru a putea diferenția aceste obiecte pe niveluri ierarhice, le vom împărții în trei categorii: ansamble, subansamble și repere.

Pentru a fi a fi bine înțeleși vom da un exemplu comun de o astfel de ierarhie de componente.

1, 2, 3, etc – semnifică identificatori ai unor componente.

1

2 3 7

7 5 2 9 10 5

10 5 7 5 4 5

10 5

Produsul cu identificatorul 1 conține produsele cu identificatorii 2, 3 și 7, produsul 2 conține pe 7 și pe 5, ș.a.m.d.

Produsele 4, 5, 10 sunt produse finale numite și repere.

Un ansamblu este o componentă în alcătuirea căreia intră una sau mai multe alte componente; un subansamblu este o componentă care intră în alcătuirea uneia de la nivel superior, dar care la rândul ei poate fi formată din alte componente. Un reper (frunză) este o componentă conținută în structura alteia și care nu mai poate fi divizată în alte componente .

Presupunând că aceste componente sunt obiecte practice care intră într-un anumit proces de fabricație, fiecare astfel de componentă are atașată alături de identificatorul și denumirea sa, două costuri (prețuri) de referință. Primul este costul propriu de asamblare a componentei iar al doilea este costul cumulat al prețurilor reale ale tuturor componentelor ce intră în alcătuirea sa. Costul real al fiecărui produs este deci suma celor două costuri : costul de asamblare plus costul subcomponentelor. Alături de aceste informații, pentru a face datele mai sugestive, fiecare componentă mai are atașată și o mică imagine de tip bitmap.

Aceasta este semnificația datelor din prima tabelă, la care ne vom referi prin “tabela produselor”. Pentru a putea susține aceste informații, această tabelă are următoare structură:

câmpul nr.1 : “Număr.” – (întreg pozitiv) cheie primară , conține identificatorul componentei.

câmpul nr.2 : ”Denumire” – (șir de caractere) unic, conține denumirea componentei.

câmpul nr.3 : “Cost_a” – (întreg pozitiv) semnifică costul de asamblare.

câmpul nr.4 : “Cost_c” – (întreg pozitiv) semnifică costul subcomponentelor.

câmpul nr.5 : “Icon” – (grafic) conține imaginea reprezentativă a componentei.

Tabela mai conține doi indecși: unul primar după câmpul Numar : Nr_idx și unul secundar, după câmpul Denumire : Den_idx.

Am vorbit anterior de o structură ierarhică a componentelor. Această structură este memorată cu ajutorul celei de a doua tabele. Să presupunem că avem următoarea situație: componenta cu identificatorul X conține un număr de Z bucăți din cea cu identificatorul Y. Această informație este memorată în tabelă printr-o înregistrare de forma: X – Y – Z . Deci în această tabelă, o înregistrare semnifică o relație de apartenență.

Structura celei de a doua tabele: tabela relațiilor, este așadar următoarea:

Câmpul nr.1: “Nr” – (întreg pozitiv) conține identificatorul componentei “părinte”.

Câmpul nr.2: “Nr_leg” – (întreg pozitiv) conține identificatorul componentei “fiu”.

Câmpul nr.1: “Cantitatea” – (întreg pozitiv) semnifică cantitatea (exprimată în bucăți) în care componenta “fiu” se găsește în componenta “părinte”.

Tabela conține trei indecși : indexul primar, format din primele două câmpuri, și câte un index secundar pentru fiecare din primele două câmpuri separat: Nr_idx și Nr1_idx.

Pentru a putea utiliza corect aplicația, este necesar ca cele două tabele accesate să aibă exact această structură (aceleași nume de câmpuri, aceiași indecși), ceea ce într-adevăr nu contează este ordinea în care se găsesc câmpurile.

4.1.2Funcționalitatea programului

Scopul aplicației mele este gestionarea acestor două tabele baze de date, așa încât în orice moment ele să indice situația reală, nealterată a datelor din sistem. Pe lângă această funcționalitate de bază mai trebuie asigurate alte câteva facilității de calcul și de vizualizare grafică a ierarhiei de componente.

Pornind de la aceste trei funcționalității, programul se împarte în trei mari unității, fiecare unitate asigurând câte o funcționalitate : prima parte se ocupă exclusiv de gestionarea corectă a tabelelor baze de date, a doua realizează vederile grafice ale sistemului iar în final, a treia parte implementează facilitățile de calcul.

Gestionarea corectă a tabelelor presupune posibilitatea de a adăuga, modifica, șterge sau ordona datele din tabele menținându-se integritatea lor. De fapt nu aceste deziderate uzuale reprezintă adevărata problemă de rezolvat ci păstrarea corectă a datelor în urma acestor operații. Deși acest lucru va fi prezentat amănunțit ulterior, vom da numai două exemple de probleme care pot apărea.

-dacă dintr-un motiv sau altul costul de asamblare al unei componente (X de exemplu) se schimbă, este necesar ca prețul fiecărei componente ce are în componența sa pe X să se actualizeze ca urmare a modificării făcute.

-în momentul inserării unei noi relații X conține pe Y , trebuie avută în vedere posibilitatea ca nu cumva componenta Y să conțină deja pe X. Această situație nu trebuie tolerată întrucât ar duce la apariția de cicluri în ierarhie, cu efecte nedorite pentru rularea programului și corectitudinea datelor.

Facilitățile de calcul se referă la posibilitatea ca utilizatorul să precizeze pentru mai multe produse cantitatea necesară și să obțină costul și cantitatea de subcomponente sau repere necesară pentru realizarea cantității propuse .

Funcționalitățile grafice se referă la posibilitatea vizualizării, pentru un anumit produs, a tuturor produselor în cărei componență intră sau a tuturor produselor ce intră în componența sa, adică a arborilor ascendenților sau descendenților.

Acestea sunt, pe scurt, facilitățile programului.

4.2 Utilizare

Primul lucru cu care utilizatorul vine în contact în momentul începerii execuției programului este meniul principal al acestuia (fereastra principală) . Din cadrul acestei ferestre poate exploata mai departe facilitățile aplicației, fie navigând prin meniul afișat și selectând opțiunile dorite, fie mai scurt, prin acționarea butoanelor specifice atașate.

Meniu principal:

Meniul principal este compus din patru opțiuni principale:

-Open

-EditTables

-TreeView

-Calculate

Open

Conține două subopțiuni: Open table “produse” și Open table “relații”.

În mod evident, pentru a putea utiliza programul este necesară deschiderea bazelor de date ce fac obiectul acestei aplicații. Acesta se face prin selectarea pe rând a celor două opțiuni Open, fiecare din ele deschizând o fereastră standard de deschidere de fișiere. Fereastra conține două liste, una cu toții directorii de pe driver-ul (directorul) curent și una cu toate fișierele tip baze de date aflate în directorul selectat. Având posibilitatea de a naviga prin toții directorii sau toate driver-ele aflate în sistem, utilizatorul poate alege astfel care dintre tabelele baze de date existente va fi cea selectată. Dacă va opta pentru una cu o structură diferită de cea prezentată anterior, va fi atenționat și va trebui să aleagă în final o tabelă corectă.

Dialogul de deschidere a tabelelor baze de date:

Când programul este lansat în execuție, toate opțiunile cu excepție primei sunt dezactivate astfel că utilizatorul nu îl va putea folosi până când nu va selecta tabele necesare. Odată ce ele au fost selectate, celelalte opțiuni devin active și se va putea continua folosirea programului.

Această opțiune nu este folosită exclusiv la începerea execuției programului, ea poate fi selectată și ulterior în cazul în care utilizatorul dorește schimbarea setului de

tabele accesate. Dacă se dorește de exemplu deschiderea unei alte tabele a componentelor, acest lucru se va putea face fără nici o problemă, cu singura observație că va trebui să se schimbe și tabela asociată a relațiilor între componente, pentru a se păstra integritatea datelor.

EditTables

Se compune din două opțiuni: Edit table “produse” și Edit table “relații”.

După ce au fost selectate tabelele baze da date folosite, utilizatorul va putea opta pentru aceste două posibilități pentru a putea gestiona cele două tabele.

Astfel selectarea unei astfel de opțiuni va afișa o fereastră cunoscută de editarea de date, ce are ca și unelte principale un grid și un navigator tipic. Cu ajutorul lor se pot edita, modifica, insera sau șterge date.

Ferestrele de editare și vizualizare pentru cele două tabele:

Tabela produselor.

Tabela legăturilor dintre produse.

Anterior am precizat că în tabela produselor se memorează pentru fiecare componentă câte un mic simbol grafic – o imagine bitmap de fapt. Pentru a putea atașa sau schimba o astfel de imagine pentru o anumită componentă , se efectuează dublu-click pe înregistrarea corespunzătoare ei din gridul afișat. Se deschide o fereastră de vizualizare de imagini bitmap similară cu cea de deschidere a fișierelor, cu diferența că imaginea corespunzătoare fișierului selectat este afișată instantaneu. De aici se poate selecta imaginea dorită.

Selectarea icon-ului unui produs:

TreeViews

Are două opțiuni simetrice : AscendentView și DescendentView.

Cele două oferă utilizatorului o vedere ascendentă sau descendentă a unei anumite componente. Astfel, selectarea primei opțiuni de exemplu, va genera apariția unei noi ferestre în cadrul căruia utilizatorul va trebui să selecteze dintr-o listă componenta căreia dorește săi vadă ascendenții. Odată selectată se va afișa un arbore care are ca rădăcină componenta respectivă și din care pornesc noduri ce reprezintă întreaga ierarhie a componentelor ce o conțin. Fiecare nod reprezintă deci o componentă și va afișa identificatorul, denumirea și icon-ul asociat ei, precum și numărul de bucății din componenta fiu.

Dialogul de vizualizare al unui subarbore al ierarhiei produselor.

Fereastra mai conține și două butoane de expandare sau colapsare a întregului arbore.

Calculate

Se descompune la rândul ei în Calculate și Advanced.

Prima opțiune dă utilizatorului o posibilitate rapidă de a afla câte bucății dintr-o anumită componentă se găsesc în compoziția alteia. Micul dialog afișat permite selectarea celor două produse (ansamblul pe de o parte și subansamblul pe de altă parte) din cele două list_box-uri prezente.

A doua opțiune oferă posibilități mai avansate de calcul. Utilizatorul are posibilitatea să precizeze pentru mai multe produse cantitatea necesară pentru fiecare și să obțină cantitatea de subcomponente sau repere necesare obținerii cantității propuse.

Facilitățile de calcul :

În dialogul mai complex care apare, utilizatorul are la dispoziție patru seturi de list_box-uri grupate două câte două, cu următoarea semnificație și utilitate:

-primul set conține identificatorii și numele tuturor componentelor, din care utilizatorul va alege doar pe acelea care îl interesează.

-al doilea set păstrează componentele selectate din primul, fiecărei componente selectate putândui-se edita numărul de bucății dorite.

-al treilea set conține identificatorii și denumirile tuturor subansamblelor sau reperelor din care utilizatorul va selecta pe cele dorite.

-al patrulea set păstrează componentele selectate din cel precedent, și în plus pentru fiecare componentă numărul de bucății calculat.

Toate aceste șase facilității pot fi accesate altfel decât prin selectarea opțiunilor corespunzătoare din meniuri, prin acționarea celor șase butoane prezente în forma principală. Fiecare buton are atașată o imagine ce se dorește sugestivă pentru genul de operație dorit.

4.3 Implementare

Din parcurgerea secțiunii de utilizare a programului reiese că există în total un număr de opt ferestre ce pot apărea, fiecare cu propria-i sarcină. Astfel, două ferestre (implementate prin Form_rel și Form_pro) asigură gestiunea tabelelor, două (Form_calc și Form_adv) asigură facilitățile de calcul, alte două (Form_asc și Form_des) realizează vizualizările, una se ocupă de deschiderea tabelelor baze de date (Form_o), una de selectarea fișierelor de tip bitmap (Image_view) și în sfârșit fereastra principală (Form_m).

În spatele fiecărei ferestre se află de fapt o formă – componentă de bază în Delphi ce aparține clasei TForm. Fiecărei forme îi corespunde un Unit cu același nume (în cazul acestui program), în care este implementată în cod ObjectPascal funcționalitatea formei.

Vom începe cu implementarea interfeței și în special a ferestrei principale.

4.3.1 Interfața programului

Am văzut anterior că principala componentă a acestei ferestre este meniul principal. El a fost implementat prin introducerea în interiorul formei a unei componente de tip TMenuItem specializată în crearea de meniuri. Cu ajutorul său am realizat cele patru opțiuni principale și apoi descompunerea fiecăreia în alte două opțiuni. Au rezultat astfel opt posibilității finale. Fiecare din aceste posibilității reprezintă o nouă componentă de tip TMenuItem. Pe lângă multitudinea de proprietății și metode pe care le are, acest tip de componentă oferă posibilitatea de a prelua de a prelua mesajul Windows "OnClick" survenit în urma selectării unui item din respectiva componentă. Astfel a fost posibil atașarea unui anumit cod mesajului respectiv.

În felul acesta, de exemplu, pentru a deschide o anume tabelă baze de date, am atașat rutinei de tratare a mesajului OnClick al opțiunii Open table "…" apelul Form_o.Show. "Form_o" reprezintă forma ce implementează dialogul de deschidere iar "Show" este metoda de afișare a cestei forme. Corespunzător acestui fapt, în unit-ul care se ocupă cu implementarea formei există declarată o anumită funcție ce aparține formei și care se apelează ori de câte ori selectăm un item din componenta ce reprezintă opțiunea meniului. În corpul acestei funcții se găsește de fapt apelul metodei "Show" al formei "Form_o".

Analog, pentru fiecare opțiune a meniului se atașează un apel de funcție specific.

La începutul programului utilizatorul nu va putea selecta majoritatea opțiunilor. Pentru fiecare dintre ele proprietatea Enabled este setată pe False și este nevoie de deschiderea ambelor tabele necesare pentru ca această proprietate să devină True și să se poată continua utilizarea programului.

Butoanele aflate sub bara de meniuri dau un acces rapid și direct la facilitățile existente și reprezintă fiecare în parte un obiect de tipul TBitBtn, ce reprezintă de fapt un buton obișnuit care are atașată o imagine afișată pe suprafața sa. Și această clasă oferă posibilitatea tratării mesajului OnClick așa că modul de lansare al facilității dorite este similar cu cel de la opțiunile meniului.

Pentru a putea afișa toate ferestrele dedicate ale programului, din unit-ul ce implementează fereastra principală se lansează metoda de afișare pentru fiecare din aceste ferestre în parte. Dar pentru a putea apela aceste metode este nevoie ca unit-urile corespunzătoare acestor forme să fie prezente în clauza "uses" a unit-ului principal. In general ori de câte ori dintr-un anumit unit se apelează o metodă sau se accesează o proprietate a unei componente ce figurează în alt unit, acesta din urmă trebuie să figureze în clauza "unit" a primului unit.

Deschiderea (selectarea) tabelelor

Se realizează în Unit_o corespunzător lui Form_o.

Forma conține două listbox-uri speciale de tipul TDirectoryListBox și TFileListBox care sunt construite special pentru afișarea tuturor directorilor și fișierelor de pe un anumit driver. Întrucât lista directorilor nu afișează decât directorii de pe drive-ul curent, este necesară posibilitatea schimbării acestui driver. De acest lucru se ocupă o altă componentă de tip TDriveComboBox care afișează toate discurile din sistem.

Utilizatorul are posibilitatea de a naviga prin toți directorii și toate driver-ele existente și să selecteze de acolo fișierele baze de date necesare. Toate cele trei liste sunt sincronizate așa încât selectarea uni disc din lista specifică duce automat la afișarea în lista directorilor a acelor directorii prezenții pe acel disc, în ierarhia corespunzătoare. La fel, selectarea unui director duce la afișarea în liste fișierelor tocmai a acelor fișiere prezente în directorul selectat. Sincronizarea se produce astfel: atât lista driver-elor cât și cea a directorilor au o proprietate specială ce le permite legarea la un TDirectoryListBox respectiv TFileListBox asociat. În cazul primei liste de exemplu, proprietatea acesteia DirList conține numele listei directorilor – în felul acesta realizându-se sincronizarea cu lista respectivă.

Deoarece singurele tipuri de fișiere necesare pentru a fi deschise sunt cele de tip baze de date, este de dorit ca lista fișierelor să afișeze doar acele fișiere care sunt de acest tip. Proprietatea Mask a componentei TFileListBox permite specificarea explicită a extensiilor (tipurilor) fișierelor ce vor fi afișate.

În Delphi, accesul efectiv la o tabelă baze de date se face prin intermediul componentelor TTable și TQuery. Deoarece a doua componentă este specifică tabelelor SQL, am folosit pentru accesul la date doar componente de primul tip. Astfel în orice formă care realizează un acces la o anume tabelă, fie el citire sau scriere, trebuie plasat un astfel de obiect Table. În momentul începerii programului locația tabelelor și tabelele înseși sunt necunoscute deci toate formele de mai sus sunt inoperabile.

Prin deschiderea unei anumite tabele se realizează de fapt două lucruri foarte importante. Mai întâi se specifică pentru fiecare componentă TTable aflată în program la care tabelă să se lege și apoi se deschide efectiv tabela.

În momentul deschiderii ferestrei de selectare, programul știe după opțiunea selectată care din cele două tabele va trebui selectată. Să presupunem că este vorba de tabela relațiilor. Odată selectat un fișier, pentru a-l deschide efectiv se apasă butonul Open sau pentru a anula selectarea – Cancel.

Acționarea butonului Open duce la execuția următoarelor operații:

-Pentru fiecare componentă TTable din program care trebuie să acceseze tabela relațiilor se invalidează legăturile ei cu orice altă tabelă, dacă ele există deja.

-Se specifică pentru fiecare TTable numele bazei de date unde se află tabela selectată. În fapt proprietatea DatabaseName a componentei este asignată cu valoarea proprietății Directory a obiectului TDirectoryListBox care conține directorul de unde provine tabela. Acest director este în cazul tabelelor Paradox și dBase chiar numele bazei de date.

-Se specifică numele tabelei de accesat. Proprietatea TableName a fiecărei TTable existente ia valoarea proprietății Filename a componentei TFileListBox ce conține numele fișierului selectat adică numele tabelei.

-Se deschide efectiv tabela.

-Dacă tabela nu are structura potrivită va trebui deschisă o alta.

-Dacă tabela este OK se testează dacă și tabela produselor a fost deschisă. Dacă da, se activează celelalte opțiuni și utilizarea programului poate continua. Dacă nu era deschisă, se așteaptă deschiderea ei, menținând în continuare invalide celelalte posibilități de utilizare.

Toate aceste operații sunt grupate în funcția de tratare a masajului OnClick a butonului Open , funcție declarată și implementată în Unit_o – unitul formei de deschidere fișiere.

4.3.2 Gestiunea tabelei produselor

Așa cum am amintit anterior, gestiunea celor două tabele care fac obiectul acestei aplicații reprezintă principalul scop al programului.

Ne vom ocupa de modul în care se implementează gestiunea corectă a produselor. Forma care facilitează acest lucru se numește Form_produse și este implementată prin unit-ul Unit_produse.

Accesarea datelor din tabelă se face prin intermediul unui obiect numit aici Table1, evident din clasa TTable, iar legăturile acestei componente cu tabela respectivă se realizează în momentul selectării ei de către utilizator prin selectarea opțiunii Open table "produse".

Pentru a putea vizualiza și modifica datele este nevoie de controale specifice din paleta de componente Delphi. Am ales un TDBGrid pentru asigurarea unui control cât mai amplu asupra datelor și un TDBNavigator pentru ușurința deplasării prin înregistrările tabelei. Legăturile acestor componente cu Table1 au fost realizate cu ajutorul unei clase specializate :TDataSet. Aceasta este pe de o parte legată la Table1 prin proprietatea sa DataSet setată cu numele "Table1" iar pe de altă parte figurează în fiecare componentă de control al datelor prin regăsirea numelui său în proprietatea DataSource a fiecărui control. Se observă că această componentă acționează ca o conductă între sursa de date și controalele prin care accesăm aceste date.

Prezența celor două controale specializate permite efectuarea întregii game de operații asupra bazei de date.

O atenție mai deosebită vom acorda câmpului Icon al tabelei, care așa cum am specificat anterior, are un format special:Graphic. Pentru a-l putea vizualiza am introdus în formă un obiect de tipul TImage, anume construit pentru afișarea de câmpuri grafice ale bazelor de date. Am setat proprietatea FieldName cu numele câmpului grafic : Icon, așa că acest control va afișa permanent bitmap-ul corespunzător câmpului grafic din înregistrarea curentă.

Dacă editarea câmpurilor numerice sau de caractere este evidentă – prin simpla introducere a noilor valori, cu câmpurile de tip grafic lucrurile stau altfel. În primul rând acestea nu se pot edita în momentul rulării programului decât dacă este lansat în execuție un program special de editare de bitmap-uri (PaintBrush ,ImageEdit, etc), soluție ce ar îndepărta prea mult programul de la scopul său inițial. Este de preferat să existe un director special care să conțină fișiere de tip bitmap gata create și de unde utilizatorul va putea alege. În al doilea rând modul de introducere a noi "valori" este puțin diferit. În cazul programului de față se va efectua dublu-click pe înregistrarea dorim săi atașăm o anumită imagine. Se va deschide o fereastră pentru a vizualiza și selecta fișiere de tip bitmap.

Forma care implementează această fereastră (ImageView) este întrucâtva similară cu cea de deschidere de tabele, cu câteva diferențe notabile. Componenta TFileListBox prezentă și aici este setată să afișeze doar fișiere cu extensia .bmp iar în momentul selectării unui astfel de fișier, o altă componentă TImage, afișează bitmapul conținut de fișier. Modul de realizare este simplu: componenta TFileListBox oferă posibilitatea de a prelua mesajul OnClick atunci când are loc pe unul dintre item-urile sale. În corpul funcției care va trata acest mesaj specificăm componentei TImage să afișeze fișierul selectat. TImage este construită așa încât să afișeze în permanență un anumit bitmap asociat ei. În acest scop ea are o proprietate: Bitmap, de tip bitmap, care stochează acest bitmap asociat. Prin simpla setare a acestei proprietății, se va afișa imaginea dorită.

Să presupunem acum că utilizatorul a hotărât care dintre imaginile existente deja va fi atașată câmpului Icon.

În cazul unor câmpuri clasice, noile valori introduse, dacă corespund exigențelor filtrelor, sunt automat atribuite câmpurilor. In cele de tip grafic se procedează altfel.

La secțiunea de prezentare a mediului de lucru în Delphi, am precizat că pentru fiecare câmp al unei tabele accesate, în momentul deschiderii ei Delphi creează automat un obiect de tipul TField, care are aceleași proprietății ca și câmpul din baza de date asociat.

În cazul câmpului nostru grafic este creată o componentă TGraphicField prin intermediul căreia câmpul poate fi modificat. TGraphicField are o metodă (LoadFromFile) care îi permite să atașeze un anumit fișier de tip bitmap câmpului grafic. Revenind acum la faza în care utilizatorul s-a decis pentru un anume bitmap, în momentul în care apasă butonul OK, se va apela metoda LoadFromFile a componentei TGraphicField pentru înregistrarea curentă. Parametrul necesar acestei funcții va fi tocmai numele fișierului bitmap selectat, păstrat în proprietatea FileName a componentei TFileListBox. Codul necesar acestei operații se va regăsi în corpul funcției de tratare a mesajului OnClick pentru butonul OK, situată în unit-ul ImageView. În felul acesta câmpul Icon din înregistrarea curentă se va "umple" cu imaginea dorită.

Dacă ar fi să asigurăm doar aceste facilității de editare de date, tot ce s-a făcut până acum ar fost suficient și nu ar mai trebui să adăugăm în plus nici o linie de cod. Dar ținând cont de semnificația datelor tabelei, de legăturile sale cu restul aplicației precum și de caracterul său activ, va trebui proiectat un anumit mod de comportament al tabelei care să satisfacă cerințele de bază exprimate la începutul expunerii.

Tot în secțiunea de prezentare Delphi se spune că o tabelă baze de date se poate afla în mai multe stării: Browse, Edit, Insert, Post și că deasemenea o componentă TTable oferă posibilitatea "captări" anumitor mesaje cum ar fi acelea survenite la trecerea dintr-o stare într-alta : Before/After Open, Before/After Delete, etc. Am atașat acestor mesaje un anumit cod care se va executa la apariția lor și care va induce tabelei comportamentul dorit.

Vom prezenta mesajele care au fost tratate și modul lor de tratare.

AfterOpen

Survine după ce tabela a fost deschisă deci este practic primul mesaj în legătură cu tabela care intervine după ce ea a fost selectată de utilizator.

Pentru a folosi facilitățile de calcul și de vizualizare oferite de program, utilizatorul va trebui de cale mai multe ori să selecteze anumite componente din mai multe liste puse la dispoziție, liste ce conțin fiecare identificatorul sau denumirea produselor. După deschiderea tabelei, toate aceste liste sunt "populate" cu datele necesare, direct de la sursă adică din tabelă. Listele sunt de fapt niște obiecte de tipul TListBox sau TComboBox, iar item-urile conținute de ele sunt organizate într-un vector de șiruri de caractere, fiecare item reprezentând un astfel de șir. Accesul la acest vector se face prin proprietatea Item a componentelor. Această proprietate este un obiect care are la rândul său alte proprietății și metode ce-i permit adăugarea, scoaterea, inserarea sau accesarea de item-uri.

Pentru a menține item-urile numerice ordonate crescător am creat o nouă funcție de adăugare de item-uri care bazându-se pe cea standard de adăugare, adaugă un item numeric exact în poziția corespunzătoare valorii sale.

Astfel, după ce tabela a fost deschisă cu succes, se parcurge de la început și până la sfârșit și se adaugă valorile câmpurilor Nr. sau Denumire, sub formă de item-uri tuturor listBox-urilor sau combobox-urilor ce trebuie să conțină aceste valori.

Aceste liste sunt răspândite pe diferite forme și accesarea lor se face precizând pentru fiecare forma părinte. Pentru exemplificare, adăugarea unui nou item listbox-ului ce conține denumirile produselor din forma de calcul Form_adv se realizează astfel:

Form_adv.ListBox_Denumire.Item.Add(Table1.Fields[1].Value);

Prin Table1.Fields[1].Value se accesează câmpul cu numărul 2 – Denumire din tabela produselor.

Before Delete

Apare înainte ca o înregistrare să fie ștearsă.

Acest eveniment este foarte important pentru gestiunea tabelei, monitorizarea lui inducând un caracter activ acesteia. Mai precis, în momentul în care are loc acest eveniment, adică se șterge o înregistrare, conținutul atât al tabelei produselor cât și al tabelei relațiilor se schimbă pentru a reflecta exact situația creată iar această schimbare are loc fără intervenția utilizatorului, producându-se automat.

Înainte de trece la implementarea acestui comportament, trebuie să introducem în discuție un element esențial în buna desfășurare a programului. Acest element este de fapt o matrice pătratică booleană, de dimensiune maximă 100*100, 100 fiind și numărul maxim de înregistrări pe care le poate avea tabela produselor. Matricea are următoarea semnificație : dacă de exemplu produsul X conține în mod direct subprodusul Y, atunci valoarea matricei pe poziția xY va fi True iar dacă X nu conține direct pe Y atunci va fi False. Deci ea păstrează de fapt tabloul relațiilor ce formează întreg arborele componentelor. Evident, această ierarhie reiese foarte bine și din tabela relațiilor. Însă este mult mai ușor și mai rapid să se testeze dacă există o relație directă între două componente accesând o matrice decât să se caute în toată baza de date a relațiilor după înregistrarea care să conțină ambele componente. Un alt avantaj insurmontabil al folosirii matricei este acela că este posibil să se testeze dacă nu cumva ierarhia conține cicluri, posibilitate greu accesibilă prin tabela relațiilor.

Administrarea acestei matrici este realizată separat într-un unit special : unit_a și va fi subiectul unei prezentări ulterioare, deocamdată ne-am referit doar la una din facilitățile matricei : determinarea existenței unei relații directe între două componente.

Pentru a putea prezenta mai bine derularea evenimentelor în momentul ștergerii unei componente, vom da un exemplu de o anumită situație existentă, exemplu ce se dorește sugestiv.

R

2 4

B C D

3 2

F E G E

60 60

R, B, C, … denumiri ale produselor.

Produsul B conține 3 bucăți din produsul E, acesta având un preț total de 60 DM.

Să presupunem că produsul E este șters. În mod evident costul total al acestui produs odată șters, va trebui eliminat din costul total al tuturor produselor ce conțin pe E, indiferent la ce nivel. Acest lucru se realizează prin intermediul funcției "actualizare". Vom prezenta această funcție pe exemplul de mai sus.

Funcția este recurentă și are doi parametri : nodul care generează modificările și modificarea survenită în costul său. În cazul nostru, al ștergerii unui produs, la început funcția va fi apelată cu parametri E și -60 = – costul său total. Iată schema funcției :

-Se determină din matricea relațiilor (notată cu A) toate produsele care conțin direct produsul pasat ca parametru (E). În fapt se parcurge linia E a matricei A și se rețin coloanele pentru care valorile găsite sunt True. În cazul nostru se determină pe B și C.

-Pentru fiecare din produsele determinate mai sus se fac următoarele operații:

-se determină din tabela relațiilor numărul de bucății în care E se găsește în produsul determinat anterior (B sau C). Acest lucru este posibil prin apelarea metodei speciale de căutare de înregistrări cu o anumită valoare a componentei TTable. Pentru a accesa date din tabela relațiilor, a mai fost introdusă în formă o componentă TTable – Table2 legată la această tabelă, prin care se face accesul dorit. Metoda de căutare este FindKey (aparține lui Table2) și primește ca parametrii vectorul format din două valori: B și E. Căutarea se face după valorile primelor două câmpuri ale tabelei ce constituie dealtfel indexul primar al acesteia, așa cum am specificat la începutul expunerii programului. Această metodă poziționează pointer-ul tabelei pe înregistrarea găsită și în felul acesta se poate accesa câmpul al treilea al respectivei înregistrări (3).

-se actualizează valoarea costului subcomponentelor pentru B sau C adunând la vechea valoare cel de-al doilea parametrul transmis (-60) înmulțit cu numărul de bucății determinat anterior (3).

-se apelează din nou funcția "actualizează" de data aceasta cu noi parametrii: B sau C și o nouă valoare rezultată din cea veche (-60) înmulțită cu numărul bucăților determinat anterior (3).

În final după mai multe apeluri succesive se ajunge la produse care nu mai sunt conținute de altele și astfel procedura se oprește iar toate produsele ce au în componență pe E vor avea costul subcomponentelor modificat corespunzător.

Tot în această situație (a ștergeri unei înregistrări) mai există o altă modificare care trebuie efectuată și care de data aceasta afectează tabela relațiilor. Trebuie șterse toate relațiile care au în componența lor produsul șters. Acest lucru se face prin apelul funcției "Actualizez_rel" ce face parte din forma relațiilor – Form_rel și care va fi descrisă ulterior. Funcția primește doi parametri : primul este identificatorul produsului șters iar al doilea este valoarea 0 care indică funcției că este vorba de o ștergere.

Modificarea datelor din tabelă survenită în urma executării codului acestui eveniment ar duce automat și la apariția mesajelor Before/After Post. Aceste mesaje au atașate un anumit cod care trebuie să execute ori de câte ori utilizatorul le provoacă. Întrucât ele apar și atunci când nu utilizatorul este cel ce le determină direct (exemplu – procedura actualizează) este de dorit ca acel cod să nu se execute în acel caz. Se presupune că dacă au apărut automat nu ele trebuie să fie răspunzătoare de reactualizarea datelor ci evenimentul sau procedura care le-a generat. În acest sens am declarat o variabilă globală:
"extern" care este setată pe "true" de fiecare dată când are loc un proces automat, cum este cazul modificărilor survenite în timpul execuției procedurii actualizează. La fiecare tratare de mesaje se testează valoarea lui "extern" . Dacă este "true" înseamnă că modificarea survine automat deci codul de tratare este nenecesar, iar dacă este "false" atunci suntem siguri că utilizatorul a generat direct mesajul și deci este cazul să se execute codul de tratare atașat mesajului respectiv.

Nici codul mesajului AfterDelete nu face excepție de la regulă, astfel încât înainte de a se apela cele două metode de actualizare, este setată pe "true" variabila "extern" atât cea din unit-ul curent – Unit_produse cât și cea din unit-ul ce se ocupă cu gestiunea tabelei relațiilor – Unit_relați .

Dacă au fost șterse toate relațiile tată-fiu care aveau în componența lor produsul șters, trebuie actualizată și matricea relațiilor directe – A. Astfel întreaga linie corespunzătoare produsului șters va trebui să fie "false" și deasemenea și întreaga coloană corespunzătoare lui (produsul odată șters nu va mai fi în nici o relație cu nimeni, nici ca tată nici ca fiu).

Pe lângă matricea relațiilor directe, mai există și o a doua matrice de referință – B, de același tip și de aceiași dimensiune cu A, matrice ce conține evidența tuturor relațiilor (directe sau indirecte) dintre produse. Dacă produsul X, de exemplu, intră în componența lui Y (indiferent la ce nivel), atunci în matricea B pe poziția [Y,X] va fi "true". Această matrice este de fapt matricea închiderii tranzitive și modul său de determinare va fi prezentat ulterior. Revenind la tratarea mesajului BeforeDelete , este evident faptul că eliminarea unor legături între componente ca urmare a ștergerii unui produs, poate modifica substanțial matricea B. Reactualizarea acesteia astfel încât să reflecte o situație exactă nu mai este la fel de simplă ca la A ci necesită o recalculare a lui B pornind de la matricea A actualizată.

O ultimă operație de efectuat este actualizarea tuturor listelor de item-uri (ListBox-uri sau ComboBox-uri) care conțin fie identificatorii fie denumirile produselor. În acest scop se apelează pentru fiecare din aceste liste metoda Delete a proprietății Item , metodă care șterge itemul specificat ca parametru (valoarea câmpului "Număr" sau "Denumire" din înregistrarea ce urmează a fi ștearsă).

Iată așadar pe scurt, care sunt operațiile necesare în cazul ștergerii unei componente:

-actualizarea costurilor subcomponentelor pentru fiecare produs ce conține produsul șters.

-actualizarea relațiilor ce conțin fie în stânga fie în dreapta identificatorul produsului șters.

-actualizarea matricilor relațiilor directe și indirecte ( A și B).

-actualizarea listelor cu denumiri sau identificatori utilizate în program.

BeforeInsert

Apare înaintea inserării unei noi înregistrări.

În cadrul tratării mesajului AfterPost, ultimul mesaj tratat generat de modificarea unei înregistrări, este neapărată nevoie să se știe de care tip sunt modificările survenite: editare sau inserare. Pentru aceasta am declarat o variabilă globală de tip caracter numită "sursa" care este setată pe "i" în cazul unei inserări și pe "e" în cazul unei editări.

Așadar în acest mesaj care apare înaintea celui de BeforePost , setăm variabila "sursa" pe "i" astfel încât în momentul tratării mesajului de BeforePost să se știe exact că modificările survin în urma unei inserări.

Costul subcomponentelor unui produs este o valoare care nu trebuie să poată fi modificată de utilizator în mod direct, ea reflectând costul total al tuturor subcomponentelor ce intră în componența acelui produs. La inserarea unei noi înregistrări acest cost este setat pe 0, reflectând exact starea curentă : până când un produs nu are altele în componența sa, acel cost va fi 0.

BeforeEdit

Apare în momentul în care utilizatorul începe editarea unei înregistrări.

Este foarte important ca datele modificate prin editarea unei înregistrări să fie accesibile în forma lor inițială într-un anumit moment. Se prea poate ca noile valori introduse să nu corespundă cerințelor programului, deci modificarea va trebui anulată. În acest caz vechile valori ale datelor vor trebui asignate înregistrărilor.

În acest scop toate valorile câmpurilor numerice sau alfanumerice aflate în înregistrarea ce urmează a fi editată, vor fi memorate în variabile special declarate. Acestea vor fi folosite ulterior în codul de tratare a mesajului BeforePost .

Deasemenea și aici variabila "sursa" despre care am discutat anterior va fi setată corespunzător, cu valoarea "e" pentru a se specifica în momentul tratării mesajului BeforePost că modificările înregistrate survin în urma unei editări.

BeforePost

Survine înaintea comiterii modificărilor.

Tratarea acestui mesaj este deosebit de importantă. Pe de o parte se implementează filtrele necesare pentru eliminarea datelor eronate și pe de altă parte se specifică ce trebuie făcut în cazul modificărilor datelor existente sau inserări unora noi.

O precizare importantă este aceea că acest cod atașat mesajului nu se va executa dacă modificările nu provin direct de la utilizator și sunt inițiate de un proces automat. O clauză la începutul tratări mesajului prevede că dacă variabila "extern" descrisă mai înainte este "true", atunci se iese din procedura de tratare.

Vom specifica mai întâi toate constrângerile de integritate ce fac conținutul filtrului prin care datele noi introduse vor trece.

Câmpul "Număr" se supune următoarelor restricții:

-este cheie unică deci nu pot exista două înregistrări cu aceeași valoare pentru acest câmp și deasemenea valoarea sa nu poate lipsi.

Câmpul "Denumire":

-nu pot exista două înregistrări cu aceleași valori pentru acest câmp.

-nu poate fi vid.

Câmpurile "Cost_asamblare" și "Cost_subcomponente" trebuie să fie pozitive.

Câmpul "Cost_subcomponente" nu poate fi modificat direct de către utilizator , el trebuind să reflecte doar costul subcomponentelor produsului și de acest lucru nu se ocupă utilizatorul ci doar programul de gestiune a tabelei.

Așadar la începutul rutinei de tratare se verifică dacă modificarea vine sau nu direct de la utilizator . Dacă survin automat se iese din procedură, dacă nu se continuă execuția ei.

Există două moduri de tratare a mesajului în funcție de tipul modificărilor survenite: editare sau inserare.

Se testează la începutul rutinei sursa modificărilor și în funcție de aceasta (inserare sau editare) se execută codul asociat.

Ne ocupăm mai întâi de modul de tratare a inserări unei noi înregistrări.

Pentru început se aplică filtrul precizat mai sus asupra noilor date. Astfel se testează dacă nu cumva valorile câmpurilor "Număr" și "Denumire" există deja în baza de date sau dacă valorile câmpurilor nu se încadrează în domeniile precizate. Dacă se detectează cumva astfel de nereguli, modificările (inserarea de fapt) sunt anulate și se iese din rutină. Anularea unor modificări odată începute se realizează prin metoda Cancel a componentei Table1. Ea returnează baza de date în starea de dinaintea începeri modificărilor și nu schimbă nici o valoare din tabelă.

Dacă noua înregistrare satisface cerințele filtrului, înregistrarea va fi efectiv introdusă în baza de date și pe lângă aceasta rutina mai actualizează și listbox-urile ce conțin informații din tabelă.

În cazul listbox-urilor ce conțin denumirile produselor, inserarea unui nou item se face printr-o funcție ce figurează în lista de metode a componentei: funcția Add asociată proprietății Item a obiectului ListBox sau ComboBox.

Listele ce conțin identificatorii produselor sunt ordonate crescător și din această cauză inserarea unui nou item se face cu o funcție proprie, locală, care inserează un item numeric într-o listă de astfel de item-uri exact pe poziția corespunzătoare valori sale.

Atât într-un caz cât și în celălalt funcțiile de adăugare fie ele standard sau implementate local primesc ca și parametru valoarea câmpului "Număr" sau "Denumire" a noi înregistrări.

Prezentăm în continuare tratarea editării unei înregistrări.

Și aici ca și în cazul unei inserări, se filtrează noile date apărute în urma editării. Dacă ele nu corespund cerințelor filtrului, tot prin apelarea metodei Cancel a lui Table1 invalidăm modificările apărute și baza de date se va întoarce la starea de dinaintea editării. Valorile înregistrării editate vor fi tot cele vechi.

Dacă datele vor trece de filtru se începe o operație care determină care din câmpurile înregistrării au fost efectiv modificate, în fiecare din cazurile care pot să apară luându-se alte măsuri. Determinarea câmpurilor care au suferit modificări se face prin testarea valorilor curente cu cele vechi, memorate înaintea începeri editării, în cadrul mesajului BeforeEdit.

Vom analiza fiecare caz în parte.

1.Modificarea identificatorului.

Întrucât acest câmp se regăsește și în tabela relațiilor (o înregistrare din această tabelă constând din identificatorii produselor ce intră într-o relație tată-fiu), acesta va trebui actualizat automat pentru a reflecta schimbarea survenită. De această actualizează se ocupă funcția "actualizez relații"- declarată și construită în unit-ul ce implementează gestiunea relațiilor. Funcția va primi doi parametri ce semnifică vechea și noua valoare a identificatorului. Modul efectiv de actualizare a tabelei se va prezenta ulterior în secțiunea de descriere a gestiuni tabelei relațiilor.

Matricile de evidență a relațiilor directe și indirecte vor suferi și ele modificări ca urmare a modificării identificatorului. Să presupunem că un identificator – X a fost schimbat în Y. Se parcurge toată linia X a matrici A și în cazul când se întâlnesc valori de "true", acestea se pun pe "false. Se procedează la fel cu coloana Y a lui A. După ce matricea A a fost astfel actualizată se trece la recalcularea matricei B pornind de la A, prin apelul unei funcții special construită, numită "test". În felul acesta cele două matrici vor reflecta situația reală.

Actualizarea listbox-urilor ce conțin identificatori se face în două faze: eliminarea vechi valori și introducerea celei noi. Prin apelul funcției NameToIndex membru al obiectului Item – proprietate a lui ListBox, funcție ce primește drept parametru vechea valoare a identificatorului, se determină indexul acestei valori în lista de item-uri. O altă funcție – Delete, cu parametru indexul rezultat din apelul funcției de mai înainte, va elimina efectiv vechea valoare din listă. Cele două apeluri se realizează într-o singură linie de cod:

ListBox.Item.Delete(ListBox.Item.NameToIndex(old_id));

în variabila "old_id" stocându-se vechea valoare a câmpului identificator.

Introducerea noi valori se face la fel ca în cazul în care se inserează o nouă valoare – prin apelul funcției locale "Add_sort" ce ia drept parametru noua valoare introdusă.

2.Modificarea denumirii.

În acest caz nu se vor mai înregistra alte modificări decât la nivelul listelor ce conțin aceste denumiri. Și aici se va determina indexul vechi valori și se va elimina aceasta iar noua valoare va fi introdusă în listă. Aceste operații se fac identic cu cele de la pasul anterior.

3.Modificarea costului de asamblare.

Costul de asamblare al unui produs este foarte important întrucât se regăsește în costul fiecărui alt produs ce îl conține. Din această cauză modificarea acestui cost atrage după sine modificarea costului altor produse. Procedura care realizează automat aceste modificări se numește "actualizează" și a fost descrisă anterior în codul de tratare a lui BeforeDelete. Ea va primi drept parametri două valori : identificatorul produsului al cărui cost se schimbă și un număr ce reprezintă diferența dintre noua și vechea valoare a costului. Pornind de la acest produs toate produsele care îl conțin direct își vor actualiza costul subcomponentelor în funcție de valoarea celui de-al doilea parametru și de numărul de bucăți din care produsul transmis ca parametru se regăsește direct în componența lor. În felul acesta toate produsele care conțin componenta modificată, vor avea costul schimbat în concordanță cu noul cost de asamblare al acelei componente.

În continuare vom prezenta schematic toată gama de operații ce constituie rutina de tratare a mesajului BeforePost.

-Dacă modificările survin în urma unei inserări:

-se filtrează valorile înregistrării introduse.

-dacă valorile sunt corecte se actualizează listbox-urile utilizate.

-Dacă modificările survin în urma unei editări:

-se filtrează noile valori ale înregistrării editate.

-se actualizează tabela relațiilor (dacă s-a modificat câmpul identificator).

-se refac matricile relațiilor directe și indirecte (dacă este cazul).

-se actualizează listbox-urile folosite (dacă este cazul).

-se actualizează tabela produselor dacă s-a modificat costul de asamblare.

Este posibil ca în cazul editării toate cele trei valori să se schimbe; în acest caz se vor efectua cumulat toate operațiile necesare fiecărei modificări în parte.

Este ușor de observat că în acest unit (al gestiuni produselor) se accesează sau se modifică date din tabela relațiilor care este gestionată de alt unit. Acest lucru este posibil prin introducerea în forma produselor a unei noi componente TTable – Table2 care va reprezenta tabela relațiilor, fiecare acces la această tabelă făcându-se prin intermediul ei.

4.3.3 Gestiunea tabelei relațiilor

Forma prin care se realizează acest lucru se numește Form_rel și este implementată în unit-ul Unit_rel.

Accesul la datele din tabela relațiilor se face tot printr-o componentă TTable – Table1 iar legăturile acestei componente cu tabela respectivă se realizează în momentul selectării ei de către utilizator prin selectarea opțiunii Open table "relații".

Controalele de vizualizare și editare sunt materializate printr-un TDBGrid și un TDBNavigator. Utilizatorul va putea naviga liber prin baza de date și modifica orice dată dorește neexistând câmpuri ce necesită un tratament special. Legătura dintre aceste controale și dataset-ul reprezentat de Table1 se realizează printr-o componentă TDataSource special construită pentru această legătură.

Și în acest unit am preluat mesajele apărute în urma diverselor operații pe care utilizatorul le efectuează asupra bazei de date. Acesta este principalul mod de a induce și acestei tabele un caracter activ așa cum l-am descris la începutul lucrări. Caracterul activ al tabelei relațiilor este deosebit de important întrucât dacă în tabela produselor puteam puteam edita sau insera înregistrări care nu afectau restul tabelei, în acest caz orice modificare sau inserare schimbă esențial datele din tabela produselor. Mai exact ne referim la costul subcomponentelor pentru fiecare produs, cost dependent de înregistrările tabelei relațiilor.

Rutinele atașate mesajelor apărute vor fi descrise mai jos grupate în funcție de mesajele respective.

AfterOpen

Apare în momentul deschideri tabelei deci imediat după ce tabela a fost selectată de utilizator din dialogul de deschidere a tabelelor.

Într-o secțiune anterioară am introdus matricea relațiilor dintre produse -A. Spuneam atunci că are o dimensiune egală cu numărul maxim de înregistrări și că pentru fiecare legătură de genul "produsul cu identificatorul X conține produsul cu identificatorul Y " avem în matrice pe poziția XY valoarea true.

Aici este momentul să inițializăm această matrice pentru a oglindi exact situația relațiilor dintre produse. Iată cum se face acest lucru: Se parcurge tabela relațiilor înregistrare cu înregistrare și în matricea A pe pozițiile Câmp1,Câmp2 se pune valoarea true.

Există posibilitatea ca tabela relațiilor să fie modificată în alt mediu decât cel al acestui program, mediu ce nu-i va asigura integritatea. Prin integritate mă refer în special la inexistența a două situații:

1- O legătură să fie reprezentată prin aceleași produse (X conține pe X).

2- În ierarhia produselor, pe un nivel oarecare, un produs să se conțină pe sine însuși.

Situațiile prezentate compromit în mod evident semnificația și corectitudinea datelor atât în tabela relațiilor dar mai ales în cea a produselor. Ele pot apărea dacă tabela relațiilor este modificată fără să se țină seama de apariția lor.

Este necesar deci ca după deschiderea tabelei să se testeze dacă nu cumva există vreuna din situațiile de mai sus. Din matricea A odată inițializată se va putea determina acest lucru. În unit-ul ce se ocupă cu gestionarea acestei matrici am construit o funcție care examinează matricea A și returnează "true" în cazul detectări unei din cele două situații nedorite. Revenind la unit-ul discutat aici, prin simpla apelare a acestei funcții numite "test" se testează integritatea tabelei.

Dacă tabela conține astfel de date eronate , utilizatorul nu va mai putea continua utilizarea programului. El va primi informațiile necesare cu privire la eroarea apărută (care produs este imbricat) și va avea doar posibilitatea editări tabelei relațiilor. Dacă consideră că greșelile au fost remediate va trebui să deschidă din nou tabela relațiilor și dacă înregistrările nu mai sunt corupte va putea utiliza în continuare programul.

Iată pe scurt operațiile descrise mai sus:

-inițializarea matricei relațiilor

-testarea tabelei

-dacă tabela nu este corectă se invalidează celelalte opțiuni ale meniului , se pune tabela în starea Edit permițându-se editarea ei; se așteaptă corectarea tabelei și redeschiderea sa pentru continuarea utilizării programului.

Modul de dezactivare a celorlaltor opțiuni ale meniului (mai puțin cea de deschidere de tabele) este direct: prin setarea proprietăților Enabled pe "false", proprietăți ce aparțin componentelor TMenuItem ce stau în spatele fiecărei opțiuni.

În cazul determinări a unor erori tabela este pusă în starea Edit prin apelul metodei proprii Edit, iar în urma eventualelor modificări, caracterul activ al său nu va fi activat – codul atașat mesajelor de răspuns la modificări nu va fi executat. Ținând cont de acest aspect este greu să se refacă integritatea datelor după o editare necorespunzătoare a tabelei relațiilor. Aceasta va putea fi refăcută prin corectarea directă a înregistrărilor corupte, însă tabela produselor nu se mai actualiza automat ci va trebui refăcută manual de către utilizator, lucru însă destul de dificil.

Același lucru este valabil și pentru modificarea tabelei produselor în alt mediu decât acest program. Nu se testează acest lucru și deci utilizatorul poate continua execuția programului dar și în astfel de cazuri corectitudinea datelor este alterată , răspunzător de acest fapt putând fi doar utilizatorul.

O concluzie reiese clar de aici: Modificarea celor două tabele în medii unde caracterul lor activ este nul și restricțiile de integritate inexistente, poate duce în mod ireversibil la compromiterea datelor iar refacerea lor nu se va mai putea face decât manual, cu un efort considerabil în cazul unor tabele mai mari sau a unei ierarhi de produse cu o structură complicată.

Când am descris modul de gestiune a produselor, la un moment dat am amintit de apelul unei proceduri – "actualizez_relațiile", apel ce survenea în urma modificări identificatorului sau ștergeri unei înregistrări. Procedura face parte din unit-ul relațiilor și este membru în aceiași formă – construită special să actualizeze relațiile în urma unor operații de genul amintit. Are doi parametri cu următoarele semnificații:

-în cazul ștergeri unei înregistrări procedura primește ca parametru identificatorul înregistrării șterse și valoarea 0.

-în cazul modificării identificatorului unui produs apelul se realizează cu parametri vechiul identificator și cel nou.

Modul de funcționare al proceduri este următorul:

-Se caută toate înregistrările cu valoarea primului câmp egală cu cea a primului parametru.

-Dacă valoarea celui de-al doilea parametru este 0 se șterge înregistrarea.

-Dacă nu, se înlocuiește valoarea primului câmp al înregistrării cu cel de-al doilea parametru.

-Se procedează la fel pentru celelalte înregistrări cu valoarea celui de-al doilea câmp egală cu cea a parametrului, iar câmpul care se modifică va fi tot al doilea.

Implementarea funcționalității acestei proceduri se bazează pe existența a doi indecși secundari, indecși care trebuie să existe în structura tabelei relațiilor. Unul indexează tabela după primul câmp iar al doilea index după cel de-al doilea câmp. Pentru a putea folosi la început primul index, proprietatea IndexName a lui Table1 va fi asignată cu numele primului index secundar.

Căutarea tuturor înregistrărilor cu valoarea primului câmp egală cu valoarea primului parametru se face apelând metoda de căutare FindKey cu parametru chiar această ultimă valoare. Se introduce acest apel într-un ciclu "while" care funcționează atâta timp cât funcția returnează valoarea "true", adică atâta timp cât există înregistrări care satisfac condiția de mai sus. Tot în interiorul acestui ciclu se fac operațiile asupra înregistrării găsite: fie se apelează metoda Delete pentru a șterge înregistrarea, fie prin proprietatea Fields a tabelei se accesează câmpul dorit al înregistrării și astfel poate fi modificat (Table1.Fields[0]:=second_param). Mai înainte de modificare se trece tabela în starea Edit pentru a se permite acest lucru (Table1.Edit) . După modificare, pentru a se comite efectiv trebuie pusă tabela în starea Post (Table.Post).

Căutarea înregistrărilor după valoarea celui de-al doilea câmp se face prin cel de-al doilea index secundar. Pentru a-l activa este deajuns ca valoarea lui IndexName să primească numele său. În continuare totul este similar cu procedeele de mai sus.

Analog unit-ului produselor și în acest unit este declarată o variabilă specială : "extern", care indică faptul că modificările asupra bazei de date survin direct de la utilizator sau sunt inițiate automat. Întrucât în rutina funcției de mai sus tabela este modificată, valoarea variabilei "extern" este setată pe "true" înainte de a se face modificarea. Acest lucru împiedică activarea rutinelor de tratare a celorlalte mesaje ce pot să apară ca urmare a modificări făcute.

Trecem în continuare la descrierea modului de răspuns la acțiunile utilizatorului de ștergere, modificare sau inserare de înregistrări. Întrucât fiecare din aceste operații este precedată și urmată de anumite mesaje, am preluat aceste mesaje și le-am atașat rutine speciale. Sarcina lor principală este acea de actualiza tabela produselor ca urmare a schimbărilor survenite între relațiile dintre produse.

Voi descrie pentru fiecare mesaj în parte rutina corespunzătoare.

BeforeDelete

Apare înaintea ștergeri unei înregistrări.

Întrucât matricea relațiilor este strâns legată de tabela relațiilor, ștergerea unei relații trebuie să afecteze evident și această matrice. Actualizarea sa este simplă: pe poziția determinată de valoarea primului câmp și de cea a celui de-al doilea câmp, se va înlocui "false" cu "true".

Odată modificată matricea A trebuie refăcută și matricea tuturor relațiilor notată cu B. Pentru a înțelege mai bine să considerăm următorul exemplu: Se șterge relația X-Y (produsul cu identificatorul X conține produsul cu identificatorul Y). Probabil că din matricea tuturor legăturilor B reiese că X conține direct sau indirect, printre alte produse și pe Y, Y1, Y2,… unde Yi sunt produsele conținute de X via Y. Odată se legătura X-Y a dispărut putem ști cu exactitate că nu mai există o legătură directă între X și Y , dar nu putem ști dacă mai există legături indirecte cu Y sau Yi. Este posibil ca Y să fie conținut de X prin intermediul unui alt produs și în acest caz toate legăturile indirecte între X și Y și Yi rămân în continuare. Este deci necesară recalcularea matrici B pe baza noi matrici A. În felul acesta se va putea determina situația exactă a legăturilor dintre produse. Recalcularea matricei B este realizată prin apelul funcției "test" care este implementată la un loc cu gestionarea celor două matrici A și B și care printre alte funcționalități poate determina pe B din A.

Ștergerea unei înregistrări (a unei relații între produse) afectează esențial costul unor produse. De acest lucru ne vom ocupa în continuare.

Reactualizarea tabelei produselor se face explicit prin apelul unei proceduri : "reactualizez_produse", procedură implementată laolaltă cu gestiunea produselor dar a cărui descriere o vom prezenta acum pentru o mai bună înțelegere a sa.

Procedura are doi parametri cu următoarea semnificație: primul reprezintă componenta "tată" iar al doilea componenta "fiu" ce intervin în relația modificată. În unit-ul ce realizează implementarea sa mai există o variabilă notată cu "m" care conține diferența dintre vechea și noua cantitate în care produsul "fiu" se găsea direct în produsul "tată". Schema proceduri este următoarea:

-Se determină în tabela produselor înregistrarea care are ca identificator valoarea celui de-al doilea parametru (se apelează metoda de căutare FindKey).

-Se înmulțește variabila "m" cu costul total (cost_asmblare + Cost_subcomponente) al produsului determinat anterior. Accesul la aceste costuri se face prin intermediul proprietății Fields.

m:=(Table1.Fields[2]+Table1.Fields[3])*m;

Se obține astfel costul modificărilor survenite în relație.

-Se determină înregistrarea cu identificatorul egal cu valoarea primului parametru.

-Se modifică costul subcomponentelor acestui produs adăugându-se acestuia valoarea lui "m".

-Se lansează procedura recurentă "actualizează" care primește drept parametri valoarea identificatorului produsului de mai sus și diferența dintre vechiul și noul său cost al subcomponentelor.

În cazul ștergeri unei relații variabila "m " ia o valoare egală cu opusul numărului ce reprezintă câte bucăți implicate în relație (valoarea câmpului nr.3). Astfel, toate sumele care se adaugă la costul subcomponentelor pentru anumite produse vor fi negative , deci costul acelor produse se va micșora.

Nici această rutină nu face excepție de la regulă și mai înainte de a apela procedura "actualizează", va seta valoarea variabilei "extern" a unit-ului produselor pe "true" împiedicând ca modificările efectuate ulterior automat asupra tabelei produselor să ducă la execuția rutinelor atașate mesajelor ce vor apărea.

Iată pe scurt operațiile efectuate de rutina de tratare a acestui mesaj:

-actualizarea matricei relațiilor.

-recalcularea matricei tuturor relațiilor.

-setarea valori variabilei "m".

-reactualizarea tabelei produselor prin apelul funcției "reactualizez_produse".

BeforeEdit

Intervine în momentul începeri editării unei înregistrări de către utilizator.

Valorile înregistrările înainte ca acestea să fie modificate vor fi memorate în trei variabile, urmând a fi folosite ulterior. Accesul la cele trei valori ale înregistrării se face tot prin intermediul proprietăți Fields a componentei Table1. Această proprietate este de fapt un obiect de tipul TField, creat de Delphi automat la deschiderea unei tabele și atașat fiecărui câmp. Prin Fields(Nr) se accesează de fapt câmpul cu numărul Nr+1 (numărătoarea câmpurilor începând de la 0). Astfel ordinea în care câmpurile tabelei sunt declarate nu contează, pentru fiecare câmp fiind declarat explicit( prin FieldEditor) un obiect de tipul TField, ordinea acestor declarații este cea care va fi luată în calcul. Chiar dacă, spre exemplu câmpul "Denumire" este declarat ultimul în definiția tabelei, obiectul TField asociat lui este declarat al doilea deci oriunde ar fi poziționat de fapt câmpul "Denumire" , accesul la el se face prin Fields(2-1).

Este foarte important ca atunci când urmează să fie comise modificările survenite în tabelă, adică în rutina de tratare a mesajului BeforePost, să se cunoască sursa modificărilor : editare sau inserare. În acest scop există și aici o variabilă numită tot "sursa" care în momentul editării va fi setată cu valoarea "e" pentru a indica mai târziu că modificările survin în urma unei editări.

BeforeInsert

Apare în urma intenției utilizatorului de insera o înregistrare.

Singura operație care se efectuează este setarea variabilei "sursa" cu valoarea "i" pentru a indica tipul modificării.

BeforePost

Apare înaintea comiteri efective a modificărilor asupra tabelei.

Tratarea acestui mesaj este deosebit de importantă. Pe de o parte se implementează filtrele necesare pentru eliminarea datelor eronate și pe de altă parte se specifică ce trebuie făcut în cazul modificărilor datelor existente sau inserări unora noi.

Modul de tratare a acestui mesaj este diferit în funcție de tipul modificări: editare sau inserare.

Vom trata fiecare din aceste cazuri în parte.

Inserare

Mai întâi se aplică un filtru care testează dacă identificatorii indicați ai produselor "tată" și "fiu" există în tabela produselor. Nu poate exista o relație în care unul din produse este inexistent. Dacă datele trec de acest prim filtru, în matricea A, pe poziția determinată de cei doi identificatori se pune valoarea "true".

Urmează în continuare aplicarea unui alt filtru ce determină dacă noua relație poate duce la apariția unor produse imbricate , care se conțin direct sau indirect pe ele însele. Întrucât prin această testare se distruge vechea matrice B a tuturor relațiilor, se realizează o copie a acestei matrici. Dacă datele nu vor trece de acest filtru matricea A va fi repusă în starea inițială, adică valoarea care fusese setată pe true va fi resetată pe false iar matricea B se va reface pornind de la copia realizată anterior, prin aceeași funcție cu care a fost realizată această copie. Utilizatorul va primi un mesaj prin care va fi atenționat de greșeala comisă și i se va indica identificatorul produsului imbricat. Inserarea înregistrării va fi anulată iar tabela se va întoarce în starea de dinainte de Insert.

Dacă datele vor trece și de acest filtru se trece la modificarea tabelei produselor ca urmare a apariției unei noi relații între produse.

Iată schematic aceste operații și modul de realizare a fiecăreia:

-Se aplică primul filtru noi înregistrări. Se apelează de două ori funcția FindKey a componentei TTable – Table2- corespunzătoare tabelei produselor cu parametri respectiv valoarea primului câmp din noua înregistrare și valoarea celui de-al doilea. Dacă ambele valori booleene returnate de funcție sunt "true" ( au fost găsiți identificatorii produselor implicate în relație) atunci datele au trecu filtru, altfel nu.

-Dacă datele au trecut de primul filtru se actualizează (deocamdată temporar) matricea A.

A[Table1.Fields(0),Table1.Fields(1)]:= True;

-Se face o copie a matricei B prin apelul funcției speciale "back_up", funcție implementată în unit-ul ce se ocupă cu gestiunea matricilor.

-Se aplică cel de-al doilea filtru prin apelul funcției "test", implemenatată laolaltă cu cea de mai sus.

-Dacă valoarea returnată de funcția "test" este "true" atunci înregistrarea conduce la imbricare a produselor. Se afișează cu procedura ShowMessage un mesaj de avertizare și identificatorul produsului imbricat. Matricea A se reface

A[Table1.Fields(0),Table1.Fields(1)]:= False;

iar matricea B își recapătă forma inițială pe baza copiei realizată anterior prin apelul aceleiași funcții "back_up" de data acesta cu un alt parametru de control.

-Dacă valoarea returnată este "false" se realizează următoarele:

-variabila "m" aparținând unit-ului produselor va lua valoarea numărului de bucăți din relația nou introdusă.

-se apelează funcția "reactualizează_produse" cu parametri identificatori celor două produse implicate în relație. În felul acesta costul tuturor produselor afectate de această relație se va modifica în concordanță cu modificările survenite.

Editare

Ca și în cazul unei inserări se filtrează noile date apărute în urma editării. Dacă identificatorii indicați se regăsesc în tabela produselor, se continuă rutina de tratare ; altfel rutina este întreruptă și editarea este anulată.

În continuare se determină care din cele trei câmpuri ale înregistrării au fost efectiv modificate. Funcție de aceasta se realizează următoarele:

Modificarea numărului de bucăți implicate în relație.

Se reactualizează tabela produselor pentru modificarea necesară asupra costurilor de asamblare a diferitelor produse ce conțin produsul "tată" implicat în relație.

Modificarea unuia dintre identificatori.

Nu are importanță care dintre identificatori au fost modificați sau dacă amândoi au fost editați, important este că a intervenit o schimbare în relația dintre produse.

Matricea A se actualizează ca urmare a acestei modificări și deasemenea se realizează o copie a matricei B. Se filtrează noile valori pentru a se determina eventualele produse imbricate. Dacă se determină astfel de produse, utilizatorul va fi avertizat, matricile A și B vor fi refăcute iar editarea se va anula. Se iese deasemenea din rutina de tratare.

Dacă datele corespund exigențelor se trece efectiv la reactualizarea tabelei produselor. Schimbarea de exemplu a relației X – Y în relația X – Z este echivalentă de fapt cu ștergerea relației X – Y și inserarea alteia noi X – Z.

Operațiile efectuate sunt pe scurt:

-Filtrarea datelor apărute. Si aici se apelează funcția FindKey cu aceiași parametri ca în cazul inserări. Dacă datele nu corespund, prin apelul metodei Cancel se anulează editările efectuate și se iese din rutina de tratare a mesajului.

-Se determina care dintre câmpuri a fost efectiv modificat. Se compară noile valori ale câmpurilor de dinainte de editare, memorate in momentul BeforeEdit cu noile valori existente.

-Dacă sa modificat unul dintre identificatori se realizează următoarele:

-se actualizează matricea A (pe poziția determinată de vechii identificatori se pune "false" și pe poziția determinată de cei noi se pune "true".

-se face o copie a matricei B.

-se testează dacă a apărut un produs imbricat.

-dacă datele nu trec de acest filtru se refac cele două matrici A și B. Prin apelul metodei Cancel modificările sunt anulate și se iese din rutină.

-dacă datele trec și de acest ultim filtru se realizează următoarele:

-variabila "m" ia valoarea vechiului număr de bucăți al relației (poate coincide cu cel nou) dar cu semnul "-" .

-se apelează procedura "reactualizez_produse" cu parametri vechile valori existente înainte de editare.

-variabila "m" ia noua valoare a numărului de bucăți din relație.

-se apelează procedura "reactualizez_produse" cu parametri noile valori ale identificatorilor.

4.3.4 Gestiunea matricei relațiilor.

Pentru operațiile de gestionare atât a matricei relațiilor directe cât și a matricei tuturor relațiilor directe sau indirecte este rezervat un unit special: unit_mat. Este singurul unit care nu implementează și facilități vizuale (nu este folosit direct de utilizator) și de aceea nu are atașată nici o formă.

Înainte de a prezenta gestionarea celor două matrici vom prezenta semnificația și necesitatea folosirii lor.

Matricea notată cu A reprezintă matricea relațiilor directe dintre componente. Este pătratică și un element poate avea doar două valori: true și false, dimensiunea sa este de 100*100. Din prezentările anterioare am văzut că orice produs este caracterizat printr-un identificator unic și cuprins între 0 și 100. Dacă două produse cu identificatorii X și Y sunt în relație directă de apartenență, adică X conține pe Y, în matricea A elementul (X,Y) va fi setat pe "true". Inițial toate valorile acestei matrici sunt "false". În momentul deschiderii tabelei relațiilor matricea A va conține situația exactă a relațiilor între componente. Iată cum: se parcurge întreaga tabelă a relațiilor și pentru fiecare înregistrare găsită matricea A va fi setată corespunzător.

A[Table1.Fields[0].Value,Table1.Fields[1].Value]:=true;

Să considerăm ierarhia componentelor drept un graf G =(X,R), unde X=1…100 și reprezintă în fapt mulțimea identificatorilor iar relația R este relația de apartenență între două componente X1 și X2 din X ( X1 R X2 X1 conține direct pe X2 ).

Lui G i se poate atașa matricea pătratică M= ||aij|| i,j= 1… #X a tranzacțiilor care compun relația R ale cărei elemente sunt definite de

1, dacă (Xi, Xj) sunt din R

aij:=

0, dacă (Xi, Xj) nu sunt din R

M este matricea booleană a tranzacțiilor grafului G și reprezintă latura teoretică a matricei relațiilor.

Matricea notată cu B reprezintă matricea tuturor relațiilor între componente. Determinarea ei se face pornind de la matricea A, printr-o procedură specială a unit-ului.

Revenind în plan teoretic , matricea M este matricea booleană a drumurilor de lungime 1. Dacă Mk este puterea booleană a acestei matrici, rezultă că în M2:

-linia "i" arată existența drumurilor de lungime 2 de la Xi la vârfurile în coloanele cărora apare 1 pe această linie.

-coloana "j" arată existența drumurilor de lungime 2 incidente interior în Xj și venind de la vârfurile pe a căror linie apare 1 în această coloană.

Analog în M3, M4, … pentru drumurile de lungime 3,4,… . Deci M2, M3,… sunt matricile booleene ale tuturor drumurilor de lungime 2,3,… existente în graful considerat.

Dacă într-una din aceste matrici se întâlnește 1 pe diagonală, atunci pentru elementul respectiv există un drum la el însuși.

Coeficienții ce nu aparțin diagonalelor acestor matrici ne dau informații despre drumuri (dacă Mk(i,j) =1 => există un drum de lungime k de la Xi la Xj), iar cei de pe diagonală despre circuite.

Matricea închiderii tranzitive a unui graf G = (X , R) este prin definiție închiderea tranzitivă a relației sale R care asociază fiecărui vârf Xi o submulțime a lui X formată din Xi și din toate vârfurile accesibile din Xi în baza lui R.

Această matrice a închiderii tranzitive reprezintă baza teoretică pentru matricea tuturor relațiilor.

Vom prezenta succint necesitatea și avantajele folosirii acestor matrici.

Informațiile deținute de matricea A reies foarte bine și din tabela relațiilor, de fapt A fiind un fel de copie acestei tabele; există însă mai multe aspecte care trebuie luate în considerare:

-Pentru a testa dacă există o relație directă între două componente este mult mai ușor să se testeze elementul corespunzător lor din matricea A (dacă este true sau false) decât să se parcurgă toată tabela relațiilor în căutarea unei înregistrări ce ar confirma acest lucru. Mai mult, existența unei relații indirecte între două componente nu se poate determina decât prin accesarea matrici B, în acest caz tabela relațiilor fiind insuficientă.

-Determinarea eventualelor imbricări ale produselor ce pot să apară prin introducerea unei noi relații, nu se poate face altfel decât prin procedee specifice teoriei grafurilor, în care matricea A este factorul principal.

-Vizualizarea grafică a ierarhiei componentelor se bazează pe realizarea unui arbore ce copiază această ierarhie. Este evident că realizarea unui astfel de arbore se face mult mai ușor pornind de la o matrice ce reprezintă practic ierarhia respectivă.

Din cele de mai sus reiese că matricea A reprezintă elementul principal în verificarea unei părți a corectitudinii datelor și punctul de plecare în realizarea facilităților de vizualizare și de calcul ale programului. Importanța ei deosebită necesită actualizarea sa ori de câte ori utilizatorul efectuează modificări asupra tabelelor. Astfel:

-Schimbarea identificatorului unui produs din 50 în 60 spre exemplu, duce la negarea tuturor elementelor setate pe "true" care se află pe linia sau pe coloana 50 în cele două matrici și setarea pe "true" a tuturor elementelor corespunzătoare celor negate dar de pe linia și coloana 60.

-Ștergerea unui produs duce la negarea tuturor elementelor setate pe "true" care se află pe linia sau pe coloana egală cu identificatorul produsului șters în matrice A și recalcularea matrici B.

-Inserarea unei noi relații, de pildă 45 – 23, duce la setarea elementului (45,23) pe "true" și recalcularea lui B.

Toate aceste operații se produc în interiorul unit-urilor care administrează tabelele modificate, în rutinele mesajelor ce survin datorită modificărilor.

În unit-ul ce gestionează aceste două matrici sunt implementate trei funcții cu ajutorul cărora se gestionează matricile : "is_final", "test" și "back_up".

Funcția "test"

Realizează două operații:

-determină matricea B pornind de la matricea relațiilor A .

-testează apariția ciclurilor în graful (ierarhia produselor).

Determinarea matricei tuturor relațiilor.

Pornind de la matricea A se calculează puterea ei booleană – A2 ce reprezintă toate relațiile de gradul 2. Matricea B devine suma booleană între A și A2. În continuare se repetă procedeul înmulțind matricea B cu A obținându-se A3, A4, … până la A100 sau până când Ai are toate elementele "false" . În felul acesta se obține matricea tuturor relațiilor – B.

Fundamentul teoretic al acestei determinări îl prezentăm în continuare:

Notăm cu R^ închiderea tranzitivă a relației R. Putem scrie că:

R^(x)={x} U R(x) U R2(x) U … orice x din X.

R2=R*R, R3=R2*R, …

Deoarece R^ este tot o relație internă a lui X, îi putem asocia o matrice booleană. Precizăm următorul procedeu prin care se poate obține matricea booleană asociată relației interne R^.

Se formează mai întâi M^ = M + M2 (este vorba de putere și sumă booleană) pe a cărei linie i se va păstra 1 peste tot unde există în M și va apărea în plus în toate coloanele vârfurilor accesibile din Xj prin drumuri de lungime 2. Aceasta ne dă pentru orice x, imaginea {x} U R{x} U R2{x}. Se continuă formarea matricilor M^3 = M^2 + M3, M4 = M^3 + M4 … etc. până când se obține Mk invariant la continuarea iterației. În acest caz Mk este matricea închiderii tranzitive a lui G prin R. Existența unor asemenea k este evidentă deoarece sau G nu are circuite caz în care există p pentru care Mk = 0, pentru orice k >= p, sau G are cicluri și în acest caz există p pentru care Mk se reproduce pentru orice k >= p după ce au fost epuizate drumurile elementare de lungime minimă.

Este evident că prin trecerea de la matricea M^p la matricea M^p+1, toate elementele egale cu 1 în M^p se păstrează în M^p+1 dar (este posibil numai dacă M^p nu este invariantă) apar și alți în plus. În general închiderea tranzitivă se obține înainte să se fi epuizat drumurile de lungime maximă deoarece vârfurile terminale ale acestora pot fi accesibile și prin alte drumuri mai scurte iar matricea M^k prin chiar modul în care se construiește le pune în evidență pe măsură ce ele sunt atinse pe drumurile de lungime minimă existente între Xi și aceste vârfuri.

În momentul introducerii unei noi relații între produse, în unit-ul ce gestionează tabele relațiilor, se apelează funcția "test" pentru a determina eventualele produse imbricate. Această funcție șterge toate valorile ale matricei B și pornind de la A începe reconstruirea lui B. Pe parcursul acestei construcții după fiecare determinare a lui Ai , i= 1, 2, 3, … se testează existența unui drum de lungime i de la un vârf la el însuși. Practic se parcurge toată diagonala lui Ai și se determină elementele devenite "true". Dacă există un astfel de element se renunță la continuarea calculării lui B și se afișează un mesaj ce-i comunică utilizatorului că modificarea făcută generează un ciclu și i se indică produsul imbricat.

Determinarea unei situații ca cea de mai sus este monitorizată și funcția va returna "true". Deci ea returnează "false" dacă modificarea survenită nu generează un ciclu și "true" altfel.

Funcția "is_final"

Este util ca în unele situații să se știe despre un anumit produs dacă mai conține la rândul său alte produse. Acest lucru se face apelând la funcția de mai sus. Ea returnează "true" dacă produsul al cărui identificator este transmis ca parametru este o "frunză" în arborele produselor și "false" altfel.

Se parcurge toată linia matrici B egală cu parametru transmis și dacă conține "true" atunci produsul nu este final; în caz că toată linia este "false" atunci produsul nu face parte din nici o relație în care este "tată" deci este produs final.

Funcția "back_up"

Atunci când se face testarea unei modificări a matricei A, vechea matrice B este distrusă și trebuie determinată noua B. Este posibil ca modificarea utilizatorului să fie greșită deci modificările asupra lui A și lui B nu mai sunt valabile.

Matricea este foarte ușor de restaurat însă cu matricea B este altfel. Sigur, se poate reface B prin reconstruirea sa pornind de la A. Am preferat acestei soluții reținerea într-o altă matrice a vecii B și refacerea ei după această copie dacă este cazul.

Funcția are un parametru boolean care dacă este "true" atunci matricea B este copiată într-o matrice copie; dacă parametrul este "false" această copie va fi transpusă

peste matricea B.

4.3.5 Vizualizarea ierarhiei componentelor.

Una din cele trei facilități de bază ale programului este oferirea utilizatorului a unei vederi grafice cât mai semnificative asupra ierarhiei componentelor. Acest lucru este realizat în două unit_uri fiecare ocupându-se cu vederea descendentă respectiv ascendentă asupra acestei ierarhii. Cele două unit-uri au atașate două forme prin care se materializează funcționalitățile lor.

Întrucât cele două ferestre ascendente și descendente sunt similare ne vom ocupa doar de realizarea vederii descendente.

Așa cum reiese din secțiunea de utilizare, odată fereastra de vizualizare afișată, utilizatorul va trebui să-și aleagă produsul ai cărui descendenți dorește să-i vadă, prin alegerea sa dintr-o listă ce conține toate produsele. Lista conține de fapt doar identificatorii tuturor produselor în ordine crescătoare. În partea dreaptă a ferestrei va fi afișat un arbore care are ca nod rădăcină produsul selectat iar celelalte noduri reprezintă produsele ce derivă din cel selectat.

Fiecare nod al arborelui cuprinde toate informațiile despre produsul reprezentat : identificator, denumire, număr de bucăți cu care intră în relație. Mai mult, icon-ul atașat fiecărui produs este afișat odată cu celelalte informații. Pentru a realiza toate acestea am introdus în forma ferestrei trei componente standard de bază : TComboBox, TTreeView și TImageList.

Prima componentă TComboBox deține identificatorii tuturor produselor. Lista de item-uri este populată în momentul deschiderii tabelei produselor. Modificările precum ștergerea sau inserarea unei noi înregistrări sau modificarea identificatorului actualizează această listă astfel că în permanență utilizatorul poate alege exact produsul dorit.

TTreeView este o componentă mai deosebită în Delphi și este special proiectată pentru afișarea unei liste de date în formatul unui arbore. Fiecare nod al arborelui pe lângă informația afișată mai deține și un nume al unui alt nod la care este legat. Ca orice componentă standard care lucrează cu liste și TTreeView are o proprietate numită Item ce reprezintă de fapt o listă de noduri ce compun arborele. Fiecare nod este la rândul său un obiect de tipul TTreeNode; acesta prin proprietatea sa Data deține informațiile atașate nodului.

O proprietate foarte importantă pentru noi a lui TTreeView este Images. Aceasta conține numele unei liste de imagini din care fiecărui nod i se poate atașa una sau două. A treia componentă importantă prezentă în formă este TImageList. Ea este o listă de obiecte TImage , unde TImage este o clasă ale cărei obiecte stochează imagini. Așadar specificând numele componentei TImageList în proprietatea Images a lui TTreeView, acesta din urmă va fi legat la o listă de imagini ce vor fi atașate nodurilor sale.

Acestea sunt instrumentele cu ajutorul cărora vom construi arborele produselor.

Declanșarea construirii arborelui începe în momentul acționării butonului OK de către utilizator. În rutina de tratare a mesajului OnClick asupra acestui buton se începe construirea arborelui. Mai întâi atât lista de imagini cât și lista de noduri se "golesc" de orice înregistrări existente dintr-o fază anterioară. Pentru acest lucru există o procedură specială : Clear care se regăsește între metodele lui TImageList și între cele ale obiectului Item ca proprietate a lui TTreeView.

Se formează informația necesară nodului rădăcină care corespunde produsului ales de utilizator. Informația trebuie să fie sub forma unui șir de caractere și conține identificatorul, denumirea și numărul de bucăți ale produsului implicate în relație. Identificatorul produsului selectat se obține prin proprietatea Text a componentei TComboBox. Din tabela produselor se obține denumirea produselor prin proprietatea Fields a componentei TTable corespunzătoare tabelei. O situație mai aparte o reprezintă afișarea câmpului Icon al produsului.

Fiecare nod al arborelui – un obiect TTreeNode, are o proprietate ImageIndex ce reprezintă un index care leagă acel nod de o imagine conținută în componenta TImageList atașată arborelui. În cazul nodului rădăcină acel index este 1 – primul element al listei de imagini. Lista are o metodă specială pentru adăugarea de noi bitmap-uri ce primește ca parametru obiectul de tip TBitmap ce trebuie adăugat. În cazul nostru acest obiect trebuie să fie exact informația grafică păstrată în câmpul Icon al produsului .

Informația grafică păstrată în câmpul Icon este accesată prin intermediul componentei TGraphicField creată explicit pentru acest câmp. Metoda SaveToFile permite salvarea câmpului într-un fișier de tip bitmap numit "aux.bmp". Se declară o variabilă globală de tip TBitmap. Un astfel de obiect are o metodă LoadFromFile prin care bitmap-ul este încărcat dintr-un fișier transmis ca parametru acestei funcții. În cazul nostru acest parametru este chiar fișierul "aux.bmp". Am obținut deci un obiect se tip bitmap ce conține exact valoarea câmpului Icon. Acest obiect este pasat ca parametru metodei Add a componentei TImageList, prin aceasta bitmapul fiind adăugat în listă.

Odată informația ce se dorește a fi afișată este construită, se apelează metoda AddChild a proprietății Item pentru introducerea primului nod în arborele de construit. Metoda primește doi parametri: nodul tată al noului nod introdus și un șir de caractere ce reprezintă informația de afișat. Pentru primul nod, nodul tată va fi unul generic ce reprezintă rădăcina arborelui și care practic nu va fi afișat (se obține din proprietatea TopItem).

Iată reprezentarea transpunerii în cod a celor prezentate mai sus:

// formarea informației de afișat în șirul de caractere denumit "info"

info:=Table1.Fields[0].AsString + "(" + Table1.Fields[1].Value + ")";

// introducerea noului nod în arbore

new_node:=TreeView1.Item.AddChild(TreeView1.TopItem, info);

// obținerea bitmapului atașat produsului și atașarea lui noului nod introdus.

Table1.Icon.SaveToFile(aux);

new_bmp.LoadFromFile(aux);

ImageList1.Add(new_bmp);

new_node.ImageIndex:=1;

Cu introducerea nodului rădăcină al arborelui se începe practic construirea acestuia. Aceasta se derulează în continuare prin apelul funcției "add_tree" implementată local și descrisă în continuare.

Funcția "add_tree" este recurentă și are ca funcționalitate construirea arborelui pornind de la nodul rădăcină și bazându-se în principal pe matricea relațiilor. Funcția primește doi parametri, unul de tip întreg și care reprezintă identificatorul produsului părinte și al doilea de tip TTreeNode ce reprezintă nodul la care se va lega noul nod (fiecare apel de funcție construiește câte un nod).

Vom descrie pas cu pas operațiile efectuate de această funcție.

-Se determină toate produsele ce fac parte direct din produsul transmis ca parametru (inițial acesta este nodul rădăcină). Se parcurge matricea A pe linia produsului tată și se rețin toate coloanele pentru care valoarea elementului este "true".

-Pentru fiecare din produsele astfel determinate realizează următoarele:

-se formează informația de afișat a noului arbore accesând identificatorul și denumirea produsului din tabela produselor și numărul de bucăți în care produsul se regăsește în cel transmis ca parametru. Toate aceste informații se formatează și se concatenează într-o singură variabilă de tip string.

-se obține noul nod prin adăugarea sa în arbore. Practic se apelează apelează metoda AddChild cu parametri obiectul TTreeNode transmis prin apelul funcției și variabila de tip string obținută anterior.

-se accesează câmpul Icon al produsului și se introduce în listă. Indexul obținut prin această inserție este transmis proprietății ImageIndex a noului nod obținut.

new_node:=ImageList.Add(bmp);

-se apelează din nou procedura "add_tree" de data aceasta cu parametri identificatorul produsului determinat anterior și nodul TTreeNod proaspăt format.

Astfel întreaga ierarhia a componentelor va fi fidel reprodusă în arborele format.

Fereastra mai conține și două butoane radio prin a căror acționare se expandează sau se colapsează întregul arbore. Expandarea și colapsarea arborelui sunt implementate prim două metode Expand și Colapse amândouă aparținând lui TTreeView. Ele au fost apelate în momentul acționării butoanelor reprezentate prin componente TRadioButton.

Pentru a putea accesa informații necesare din cele două tabele a fost necesară introducerea a două componente TTable: Table1 și Table2, una legată la tabela produselor și cealaltă la cea a relațiilor.

4.3.6 Facilitățile de calcul

Aceste facilități se materializează în două ferestre. Prima oferă o facilitate simplă și rapidă de a calcula numărul de bucăți în care un anumit produs se găsește în altul. Acest lucru se realizează prin intermediul unui mic dialog în care se pot selecta cele două componente de referință (ansamblul pe de o parte și subansamblul pe de altă parte).

A doua fereastră oferă posibilității mai avansate de calcul. Utilizatorul are posibilitatea să precizeze pentru mai multe produse cantitatea necesară pentru fiecare și să obțină cantitatea de subcomponente sau repere necesare obținerii cantității propuse.

În cazul primului dialog nu sunt necesare decât două componente TComboBox și TEditBox și binecunoscutele butoane Ok și Cancel. Cele două liste conțin amândouă același lucru: identificatorii tuturor produselor. Semnificația lor este însă diferită. Din prima listă utilizatorul trebuie să aleagă identificatorul produsului "tată" iar din cea de a doua identificatorul subprodusului sau reperului. Actualizarea listelor aste unitară și se face ori de câte ori se șterge, se inserează sau se modifică semnificativ o înregistrare.

Rezultatul dorit este numărul de bucăți în care produsul selectat din a doua listă se găsește în produsul selectat din prima listă. Calculul acestei valori se face printr-o procedură numită "calculate" pe care o vom prezenta în continuare.

Această funcție este și ea recursivă și primește ca parametru un număr pozitiv ce reprezintă identificatorul unui produs. Acest produs este produsul "tată" de la care se începe calculul. Identificatorul produsului final al cărui număr de bucăți se dorește a fi calculat se găsește în variabila globală "bottom".

Pentru a înțelege mai bine vom considera următoarea situație :

1,2,3,… identificatori ai unor 1

produse 2 1 4

2 3 6

3 2

4 6 5 6

Produsul 1 conține două bucăți din produsul 2, patru bucăți din produsul 6, ș.a.m.d.

Presupunem că dorim să calculăm în câte bucăți se găsește 6 în 1. Pentru acest lucru se apelează funcția "calculate" cu parametru =1. Iată ce se întâmplă prin acest apel:

-Se inițializează variabila "rezultat" cu 0 .

-Se determină toate subprodusele directe ale lui 1 – 2,3,6. Se parcurge linia 1 a matricei A și se rețin coloanele pentru care A(1,j)="true";

-Pentru fiecare produs astfel determinat operăm următoarele:

– dacă produsul este același cu cel dorit, la rezultatul deja existent se adaugă numărul de bucăți în care acest produs se găsește în părintele său direct. Mai întâi se determină acest număr din tabela relațiilor localizând înregistrarea cu primele două câmpuri egale cu identificatorii produsului tată și fiu. În exemplul nostru se caută înregistrarea 1-6 și se obține numărul de bucăți = 4.

rez := rez + Table1.Fields[2].Value;

-altfel, dacă există o legătură de apartenență între produsul determinat și cel final la rezultatul deja existent se adaugă rezultatul returnat de un nou apel al funcției, de data asta cu parametrul identificatorul produsului determinat.

rez := rez + Table1.Fields[2].Value +calculate(i);

Pentru determinarea unei legături de apartenență între două produse se va consulta matricea B a tuturor relațiilor.

Iată cum decurge procedura pe exemplul de mai sus:

calculate(1)

rez := 0+2*calculate(2);

rez := 0+3;

rez := 6+1*calc(3);

rez := 0+2;

rez := 8+4 =12;

Deci calculate(1) = 12 unde "bottom" = 6;

Voi descrie în final modul de sincronizare a listbox-urilor dintr-un anume grup.

Prin toate listbox-urile afișate se poate naviga independent dar setarea unui item într-o astfel de listă duce la selectarea automată a item-urilor corespunzătoare celui selectat, item-uri situate în listele din același grup. Astfel, dacă selectăm item-ul ce reprezintă identificatorul 10 din prima listă a celui de-al doilea grup, automat în celelalte liste ale grupului se vor selecta item-urile corespunzătoare denumirii și numărului de bucăți asociate produsului cu identificatorul 10.

Bibliografie

Database Application Developer's Guide – Help online.

Byte ( Nr. 10/1996 ; 1/1997 ).

PCWord ( Nr 8/1996 )

Ioan Despi ; Lucian Luca .1996. Baze de date orientate pe obiecte – Baze de date active.

Similar Posts

  • Retelele Neuronale

    CUPRINS INTRODUCERE CAPITOLUL I. REȚELELE NEURONALE 1.1. Modelarea neuronului artificial 1.2. Arhitecturi de rețele neuronale 1.3. Tipologia rețelelor neuronale 1.3.1. Criterii de aplicabilitate 1.3.1.1. Rețea neuronală de tip perceptron 1.3.1.2. Perceptronul multistrat 1.3.1.3. Rețele backpropagation 1.3.1.4. Rețele neuronale care se bazează pe funcții radiale 1.3.1.5. Rețeaua neuronală Hopfield 1.3.1.6. Rețeaua neuronală Kohonen 1.3.2. Domenii de…

  • Algoritmi de Sortare Paralela

    INTRODUCERE……………………………………………..……………1 CAPITOLUL I IΝΤRОDUCЕRЕ ÎΝ ΡRОIЕCΤΑRЕΑ ΑLGОRIΤМILОR……………2 Dеfiniții……………………………………………………………………2 Algoritm, program, programare………………………………………………………3 1.2.1 DЕSCRIЕRЕΑ ΑLGОRIΤМILОR…………………………………………..4 Оbiеctul disciрlinеi…………………………………………………..…..6 Ρrорriеtăți alе algоritmilоr…………………………………………..…..7 Мăsuri dе реrfоrmanța……………………………………………………9 Datе……………………………………………………………….……..11 Τiрuri dе рrеlucrări………………………………………………………..11 Ехеrciții……………………………………………………………….…..12 CAPITOLUL II ALGORITIMI DE SORTRE 2.1. Limbaj algоritmic…………………………………………………………………………….17 2.2. Sреcificarеa datеlоr……………………………………………..……….19 2.3. Verificare corectitudinii…………………………………………………..22 2.4 МЕΤОDЕ ЕLЕМЕΝΤΑRЕ DЕ SОRΤΑRЕ 2.4.1 Ρrоblеmatica sоrtării ……………………………………….……25 2.4.2 Sоrtarе рrin insеrțiе……………………………………….….26 2.4.3…

  • Aplicatie Web Creata In Microsoft Visual Studio 2013

    Cuprins ………………………………………………………………………………………….. 3 Introducere …………………………………………………………………………………….. 4 Integrarea temei într-un context teoretic general …………………………………. 5 Programarea …………………………………………………………………………….. 5 Tehnologii ……………………………………………………………………………………… 6 Microsoft Visual Studio …………………………………………………………….. 6 Limbaje de programare ………………………………………………………………7 Limbajul C# ………………………………………………………………………7 Prezentarea generală a temei………………………………………………………………8 Arhitectura N-Tier………………………………………………………………………8 ASP și ASP.NET………………………………………………………………………12 Structura paginilor ASP.NET……………………………………………..13 Procesarea paginilor ASP.NET……………………………………………15 SEO – Search Engine Optimization……………………………………..17 JavaScript, jQuery…………………………………………………………………….18 HTML5…

  • Aplicatie Intranet Pentru Unitatile de Invatamant Preuniversitar

    APLICAȚIE INTRANET PENTRU UNITĂȚILE DE ÎNVĂȚĂMÂNT PREUNIVERSITAR Cuprins Capitolul I Introducere În lucrarea de față se va urmării dezvoltarea aplicațiilor educaționale specifice școlilor preuniversitare, o privire de ansamblu asupra a tot ceea ce implică software educațional și a modalitaților de folosire ale acestuia. În acest sens, va fii necesară prezența calculatoarelor și a tehnologiei informației…

  • Proiectarea Unei Aplicatii Magazin Online Folosind Php Si Mysql

    Cuprins Capitolul 1. Introducere 1.1. Tehnologia in ziua de azi…………………………………………………………………..2 1.2. Site-uri si aplicații web………………………………………………………………………….3 1.3. Introducere a conținutului lucrării……………………………………………..7 Capitolul 2. Prezentarea tehnologiilor utilizate 2.1. Internet…………………………………………………………………………………………………9 2.2. World Wide Web…………………………………………………………………………………..11 2.3. HTML…………………………………………………………………………………………………..12 2.4. JavaScript……………………………………………………………………………………………..18 2.5. CSS……………………………………………………………………………………………………….22 2.6. XAMPP…………………………………………………………………………………………………25 2.7. PHP……………………………………………………………………………………………………….26 2.8.MySql……………………………………………………………………………………………………..31 2.9. Apache……………………………………………………………………………………………………40 Capitolul 3. Proiectarea și Implementarea aplicației 3.1. Baza de date utilizată………………………………………………………………………………42…

  • Subsistemul Informational

    Subsistemul informațional reprezintă un ansamblu de date, informații, fluxuri și circuite informaționale, procedure informaționale și mijloace de tratare a informațiilor ierarhice și organizatorice, care va permite realizarea obiectivelor planificate. După cum constatăm, subsistemul informațional este complet diferit de sistemul informatic care prelucrează numai electronic niște date. Componentele sistemului informațional sunt: Data, care reprezintă o exprimare…