samedi 21 décembre 2002

Delphi 7 studio



Ce livre aborde la programmation sous Windows et Linux.

Ce livre ne touche pas uniquement à Delphi, on y aborde des sujets tels que l'ergonomie, uml... Delphi est mise en avant première à travers les nombreux concepts vus.

Même si ce livre est consacré à la version 7 de Delphi, il est fort utile pour les autres versions de Delphi. Comme la majorité des livres, il manque d'exemples approfondis et on survole parfois trop certains sujets.

Ce livre demeure tout de même la référence présentement des livres en français sur Delphi.

lundi 16 décembre 2002

Software Engineering: Theory and Practice




Ce livre introduit les concepts du génie logiciel. Le développement avec l'approche procédurale et orienté objet y sont discuté.

L'emphase du livre est mise sur la modélisation. Les concepts montrés sont appuyés par deux exemples: un système d'information et un système temps réel.

En plus de cet aspect, la planification et la gestion de projet y fait partie. L'analyse chez le client afin de pouvoir définir les spécifications, le design du système ainsi que les tests font partie des chapitres que couvre ce livre.

Un livre très complet qui permet de faire le pont entre la théorie et la pratique.

dimanche 8 décembre 2002

Html


HTML

Le HTML est lisible sur toute plateforme qui possède un navigateur internet. On peut ainsi dire la majorité. Il peut alors être intéressant d'utiliser cette compatibilité afin de toucher le plus de gens possible. Bien sûr il existe divers formats de type de fichier, mais il arrive trop souvent que le logiciel permettant de lire un type de ficher donnée ne se trouve sur la plateforme désiré, que le rendu n'est pas semblable d'un système à un autre ou que notre confidentialité est mise en doute.

Nous pouvons alors utilisé le HTML pour créer divers types de document qui sera lisible à peut prêt partout et qui à l'avantage d'être très léger.

TPageProducer

Ce composant permet de créer des pages web à partir d'un modèle. Il existe aussi possible d'utiliser le TDataSetPageProducer (bd). Il est alors possible de créer des lettres génériques. Les mots génériques créés par nous sous la forme " <#nom_du_tab> seront par la suite changé par une valeur désirée.

Modèle

<HTML>
  <HEAD>
    <TITLE> </TITLE> 
  </HEAD>
  
<BODY>

<p> <#date>  </p>
<p> Traitement Automatisé des Fiches de salaire.</p>


<p> <#nom_employe> </p>

<p>Nos archives indiquent que n'avez pas pris de journée de congé au cours des <#annee> dernière(s) 
année(s).

Vous n'êtes pas sans savoir que notre entreprise accorde 2 semaines de congés payés aux employés 
par an, ou bien leur offre la possibilité de prendre l'équivalent en salaire.

En outre, en accord avec les conventions signées avec les syndicats vous accorde une 
semaine supplémentaire de congés payés pour chaque période de cinq années de travail dans l'entreprise.
</p>

<p>Bien à vous,</p>

<p>La comptabilité</p>
</body>
</html>
 
Nous pouvons voir que la lettre ci-dessus est générique. Elle pourrait par exemple être envoyé au employé d'une entreprise. Elle présente quelques tags personnalisés.
  • <#date>
  • <#nom_employe>
  • <#annee>
Il serait possible par exemple de prendre des valeurs dans une base de donnée et de remplacer ces tag. Les possibilités sont alors immenses: envois de courriel, lettre...

Fonctionnement

Il faut spécifier le nom de notre fichier HTML générique par la propriété HTMLFile du TPageProducer. Il faut ensuite indiquer les tags devant être modifiés et spécifier leurs valeurs.

var      
  S:string;
begin
  S := PageProducer1.Content; //Renvoi le contenu après les modifications des tags.
  if Length(S) > 0 then
    with TFileStream.Create('tmp.htm', fmCreate) do
    try
      Write(S[1],Length(S));
    finally
      Free;
end;
 

Le remplacement des tags par les valeurs souhaitées se déroule dans l'évènement HTMLTag.
Note que dans notre code nous avons écris <#date> mais qu'il faut seulement écrire date dans cet
évènement.

procedure TForm1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
  const TagString: String; TagParams: TStrings; var ReplaceText: String);
begin
 if TagString = 'date' then
    ReplaceText := '12 juillet'
 else if TagString = 'nom_employe' then
    ReplaceText := 'Mr. Gendron'
 else if TagString = 'annee' then
    ReplaceText := '10';
end;
 

Nous avons utilisé des valeurs statiques dans cet exemple, mais il aurait été possible
d'utiliser des valeurs d'une base de données.

mardi 3 décembre 2002

Algorithme de tri


ALGORITHME DE TRI

Il existe de nombreux algorithmes de tri, je tenterais seulement de vous montrer les plus populaires et les plus utilisés.

Il n'y a pas vraiment de tri qui soit meilleur que les autres, la vitesse d'exécution d'un algorithme dépend des donnés à trier. Certains algorithmes sont plus efficaces dans certaines circonstances c'est ce que nous tenterons de vous montrer

Complexité

Dans le but de mesurer la complexité d'un algorithme de tri,deux quantités sont à observer :
  • le nombre d'échanges effectués,
  • le nombre de comparaisons effectués entre éléments du tableau.

Grand O

Le grand O  est utilisé pour indiquer la croissance d'une fonction. Le symbole utilisé est O.

Grand oméga

Le grand oméga est utilisé pour avoir la limite inférieur d'une fonction, en d'autre mot le minimum. Le symbole utilisé est Ω

Les tries

Buble sort

Ce type de tri est le plus simple, mais souvent le moins efficace. Il peut s'avérer utile pour des petits tris étant donné sa simplicité, il y a peu de chance de faire des erreurs.
L'idée du buble sort est de comparer un élément à son voisin et de les permuter si l'élément est plus grand que celui-ci.

procedure BubbleSort(var tabNote: array of Integer);
var
  i,j: integer;
  temp: integer;
begin
  for i := high(tabNote) downto low(tabNote) do 
    for j := low(tabNote) to high(tabNote)-1 do 
      if tabNote[j] >tabNote[j + 1] then 
      begin
        temp := tabNote[j];
        tabNote[j] := tabNote[j + 1];
        tabNote[j + 1] := temp;
      end;
end;
 
Comme vous pouvez le voir un tel algorithme est lent, imaginez trier 1000 000 de donnés avec ça. Vous pouvez remarquer que lorsqu'on change de position l'élément, il arrive souvent que le tableau soit trié avant que l'algorithme soit fini. On peut aisément améliorer cela en ajoutant une petite vérification.

procedure BubbleSort(var tabNote: array of Integer); 
var 
  i,j: integer;
  temp: integer; 
  Test: Boolean; 
begin 
  for i := low(tabNote) to high(tabNote) do begin  
    Test := false;
    for j := low(tabNote) to high(tabNote) - 1 do begin 
      if tabNote[j] > tabNote[Succ(j)] then begin 
        temp := tabNote[j]; 
        tabNote[j] := tabNote[Succ(j)];
        tabNote[Succ(j)] := temp;
        Test := true; 
      end; 
    end; 
    if not Test then break; 
  end; 
end;

Complexité

La complexité est O(n^2) pour des données mélangées, mais l'approche est O(n) si la liste est presque trié au début.

Insertionsort

Cette méthode est semblable à celle que nous utilisons lorsqu'on trie des cartes. Nous prenons chaque nouvelle carte et nous l'insérons à la bonne position dans le paquet déjà trié. Ce tri est particulièrement rapide lorsqu'il y a peu de désordre.

procedure Insertionsort(var tabNote: array of Integer); 
var 
  i, j: Integer; 
   temp: Byte; 
begin 
  tabNote[0] := 0;          //garde l'algo hors de l'alignement 
  for i := 2 to high(tabNote) do begin 
                           //débute à 2 puisque le premier élément 
                           //n'est ni plus petit 
                           //ni plus grand que lui-même
    temp := tabNote[i] ;
    j := i; 
    while tabNote[Pred(j)] > temp do 
    begin
      tabNote[j] := tabNote[Pred(j)]; 
      dec(j);
    end; 
     tabNote[j] := temp;  //insère l'élément à la position trouvé  
  end; 
end;

Complexité

Le meilleur cas est n et le pire cas est O(n2)

Shellsort

Ce tri est similaire à l'insertionsort. Il est un peu plus performant. L'insertionsort n'est pas très rapide puisque les éléments sont bougés d'un espace à la fois. Nous pouvons le faire bouger de plusieurs espaces, jusqu'à ce que nous trouvions le bon emplacement. Pour arriver à une telle solution, nous pouvons réduire la grosseur de k, chaque fois que nous constatons que (j-k) ième élément est plus grand que l'élément actuel. Nous pouvons diviser k par 3 chaque fois.

procedure ShellSort(var A : ArrayType; N : integer);
var
  Done : boolean;
  Jump,I,J : integer;
begin
  Jump := N;
  while (Jump > 1) do
  begin
    Jump := Jump div 2;
    repeat
      Done := true;
      for J := 1 to (N - Jump) do
      begin
        I := J + Jump;
        if (A[J] > A[I]) then
        begin
          Swap(A[J], A[I]);
          Done := false
        end;
      end;
    until Done;
  end
end;

Quicksort

Quicksort est l'algorithme de tri le plus populaire. Sur une entré aléatoire, Quicksort est le plus rapide des algorithmes que je connais. Quicksort est implanté de façon récursif, il peut ainsi être plus complexe à comprendre.
1. On doit spécifier un élément x pour établir l'alignement.
2. Chaque élément plus petit que x sera mis à droite et ceux plus grand à gauche.
3. Quicksort s'appelle et passe les deux parties. Lorsqu'il est appelé avec un tableau de grosseur 1, un élément a été trié.
4. Lorsque quicksort revient de la récursion, les deux parties ont été triées et peuvent être mises ensemble.

procedure Quicksort(var tabNote: TArray,longueur, r: Integer); 
var 
  i, j: Integer; 
  temp, old: Byte; 
begin 
  old := tabNote[(longueur + r) div 2]; 
  i := longeur;
  j := r;  
  repeat   
    while tabNote[i] < old do inc(i);   
    while old < tabNote[j] do dec(j);   
    if i <= j then 
    begin   
      temp := tabNote[i];
      tabNote[i] := tabNote[j];   
      tabNote[j] := temp;    
      inc(i);     
      dec(j);   
    end; 
    until i > j;
    if longeur < j then Quicksort( longueur, j); 
    if i < r then Quicksort( i, r); 
end;

Complexité

Le pire cas est O(n2).
La moyenne des résultats : O(n log n)..

Selection Sort

Ce tri parcourt le tableau à trier, cherche l'élément à remplacer et le permute. Ce tri peut s'avérer intéressant, s'il y a peu d'affectation (fichier).

procedure SelectionSort(var tabNote: array of Integer);
var
  I, J, temp: Integer;
begin
  for I := Low(tabNote) to High(tabNote) - 1 do
    for J := High(tabNote) downto I + 1 do
      if tabNote[I] > tabNote[J] then
      begin
        temp := tabNote[I];
        tabNote[I] := tabNote[J];
        tabNote[J] := temp;
      end;
end;

Complexité

La moyenne des résultats :O( n^2).
Un programme utilisant de nombreux algorithmes de tri est disponible ici.

lundi 2 décembre 2002

Procédure stockée


PROCÉDURE STOCKÉE

Une procédure stockée ressemble à une fonction en delphi. Cette procédure est entreposée sur le serveur de base de données, le client peut l'appeler. Une procédure peut avoir 0 ou plusieurs paramètres et retourné 0 ou plusieurs valeurs. Une procédure stockée est compilée sur le serveur lorsqu'elle est créée. Une requête est compilée à chaque fois qu'elle est envoyée au serveur. Il y a quelques avantages à utiliser les procédures stockées:
  • Plus rapide, car elle est déjà compilée sur le serveur
  • Diminue le trafic réseau car seuls les paramètres et le nom de la requête transitent par le réseau
  • Partage des ressources, plusieurs programmes peuvent utilisé la procédure
  • facilitée de maintenance, la mise à jour de la procédure profite à tous les programmes

Syntaxe d'une procédure stockée


CREATE PROCEDURE name
[( param datatype [, param datatype ...])]
[RETURNS ( param datatype [, param datatype ...])]
AS
<procedure_body>;
<procedure_body> = [< déclaration_variables >]
< block>
< déclaration_variables> =
DECLARE VARIABLE var datatype;
[DECLARE VARIABLE var datatype; ...]
<block> =
BEGIN
< compound_statement>
[< compound_statement> ...]
END
< compound_statement> = {<block> | statement;}

Suppression d'une procédure stockée

La syntaxe de suppression de la procédure stockée est fort simple.
drop procedure nom_procedure;

Déclaration de variable

La déclaration d'une variable doit suivre la convention: <DECLARE VARIABLE nomVariable Type de donnée>. Les variables globales n'existent pas.

/*Déclaration de variable nécessaire à la procédure*/
DECLARE VARIABLE tmpChar CHAR(5);
DECLARE VARIABLE tmpInt integer;
DECLARE VARIABLE tmpNum NUMERIC(15,2);

Déclaration de variable

Condition

La syntaxe du if else... ressemble étrangement à celle présents sous delphi.
IF <Expression de la condition> THEN
  <code>
ELSE
  <code>
Dans l'expression de la condition, il est possible d'utiliser certain opérateur SQL
  • BETWEEN ... and ...
  • LIKE ...
  • IN (..., ..., ...
  • EXISTS (sous-requête)
  • ANY (sous-requête)
  • ALL (sous-requête)
  • IS NULL
  • IS NOT NULL

Boucle

Cet opérateur permet comme nous le savons d'exécuter du code une certaine quantité de fois
WHILE (<Expression de la condition>) DO
        <code>

Nous allons créer une table pour notre exemple.
 
CREATE TABLE personne
(
  nopersonne INTEGER NOT NULL,
  age    INTEGER NOT NULL,
  pays         CHAR(8)  NOT NULL,
CONSTRAINT PK_nopersonne PRIMARY KEY (nopersonne)
);


Imaginons maintenant que cette table renferme des milliers d'enregistrements et que nous désirons obtenir l'âge moyen des personnes vivant dans un pays donné. Il serait possible d'écrire la requête ci-dessous et de l'assigner à un ibquery, ibsql ou à un ibdataset.
 
select avg(age)from  personne where pays = :PPAYS


Maintenant regardons la solution avec une procédure stockée

create procedure GET_AGE (PPAYS CHAR(8))
returns (AVG_AGE DECIMAL(8,2))AS
DECLARE VARIABLE i integer;
begin
  WHILE (i <= 5) DO
  begin
    select avg(age)
    from personne
  where pays = :PPAYS
  into :AVG_AGE;
  IF (AVG_AGE = 0) THEN
       AVG_AGE = 1;
  i = i +1;
  end
end
 

Dans cette procédure stockée, le select ne retourne qu'une valeur, si plusieurs valeurs auraient été retournées, il
aurait fallu utilisé: FOR SELECT...DO

Notez que c'est la valeur de avg_age qui va être retournée. En utilisant le composant: IBStoredProc, vous n'avez qu'à renseignez la propriété database et transaction. Ensuite, vous allez voir toutes les procédures stockées dans la propriété StoredProcName, vous n'avez qu'à sélectionner celle que vous désirez. Dans notre exemple ça sera: GET_AGE. Il est possible de voir les paramètres d'entrée et de sortie de la procédure en cliquant dans la propriété Params du composant IBStoredProc. Lorsque nous voulons appeler la procédure stockée dans notre code delphi et obtenir la valeur de retour, il suffit de faire pour notre exemple:

var
  avgAge:float;
begin
  IBStoredProc1.ExecProc;  //IBStoredProc1 étant le nom du composant dans delphi
  avgAge := IBStoredProc.ParamByName('AVG_AGE').AsFloat; 
end;

Il est possible d'exécuter toute sorte de requêtes avec les procédures stockées. La procédure ci-dessous supprime
les personnes ayant l'âge passé en paramètre.

create procedure AGEINF18 (persage int) as
begin
  delete from personne
  where age = :persage;
end

On a vu rapidement l'utilisation et la création de procédure stockée. Une connaissance de programmation et 
de SQL facilite leur utilisation  et permet d'alléger le client.

samedi 30 novembre 2002

Trigger


TRIGGER

Un trigger est associé à une table ou vue et permet d'exécuter du code selon un évènement donné. Tout comme les procédures stockées, tout s'exécute sur le serveur. Le trigger n'est jamais appelé directement. C'est lorsque l'évènement auquel est associé le trigger survient, le code est alors exécuté. Le langage utilisé par la base de données peut être utilisé pour le trigger, point besoin d'être limité à du SQL. Il est donc possible d'effectuer des tâches très complexes très aisément en utilisant ce langage au lieu d'avoir à gérer de complexe et lourde requête SQL.

Il est donc possible de se passer des triggers, mais un plus grand effort sera nécessaire pour parvenir au même objectif. Un trigger est exécuté sur le serveur, il n'y a donc pas de trafic réseau qui est généré telle que lors de requête SQL. Cette centralisation permet que plusieurs applications puissent y avoir accès. Si un changement doit se faire, il n'est pas nécessaire de recompiler l'application. L'utilisation de cette technique peut alors s'avérer beaucoup plus rapide que son équivalent en SQL. Un trigger n'a aucun paramètre d'entré et de sortie.
Il est possible d'utiliser les triggers pour diverses raisons:
  • Gérer l'intégrité des données
  • Gérer les règles d'affaires
  • Sécurité
  • ...

Syntaxe d'un trigger

CREATE TRIGGER name FOR { table | view}
[ACTIVE | INACTIVE]
{BEFORE | AFTER} {DELETE | INSERT | UPDATE}
[POSITION number]
AS < trigger_body >
< trigger_body > = [< variable_declaration_list >] < block >
< variable_declaration_list > =DECLARE VARIABLE variable < datatype >;
[DECLARE VARIABLE variable < datatype > ;...]
< block > =
BEGIN
< compound_statement > [< compound_statement >...]
END
< compound_statement > = {< block > | statement;}

Il existe tel qu'on peut le voir certaines ressemblances avec la syntaxe des procédures stockées

Opérateur

Les conditions et les boucles sont identiques à celles qu'on a pu voir dans les procédures stockées
Un trigger peut être exécuté pour les évènements suivants
  • BeforeInsert
  • BeforeUpdate
  • BeforeDelete
  • AfterInsert
  • AfterUpdate
  • AfterDelete
Lors du tutoriel des Générateurs, nous avons rapidement vu le code d'un trigger
SET TERM ^;
CREATE TRIGGER TRIG_CLIENT_GenId FOR CLIENT
ACTIVE BEFORE INSERT AS
BEGIN
  IF ( NEW.NOCLIENT IS NULL ) THEN
    NEW.NOCLIENT = GEN_ID(TRIG_CLIENT_GenId,1);
END

Ce trigger permet automatiquement de renseigner la clé primaire de la table. Il n'est alors plus nécessaire
d'intervenir, Interbase le fait pour nous. Une telle approche permet d'éviter les erreurs en cas d'omission.
Création d'une table

create table tclient(
 noms char(12),
 prenom char(12),
 motpasse char(12)
 );

SET TERM ^;
CREATE TRIGGER TRIG_CLIENT_GenId FOR CLIENT
ACTIVE AFTER INSERT AS
BEGIN
  IF ( NEW.motpasse IS NULL ) THEN
    NEW.motpasse = 'tmp';
END

La création de cette table ainsi que la procédure stockée permet de mettre un mot de passe si aucun n'a été inclus.Il
est possible de faire appel à une procédure stockée ou un UDF dans un trigger

UDF

UDF signifie fonction définie par l'usager (user defined function). Ces UDF sont bien entendu exécutés sur le serveur, la majorité du temps, le langage utilisé est le c ou le c++. Il est possible d'utiliser d'autre langage. Dans les répertoires d'Interbase, il y a de nombreux exemples.

Les UDF sont utilisés pour ajouter des fonctionnalités non disponibles dans SQL.
Les UDF ont 2 défauts: lorsqu'une UDF est exécuté, aucun accès à la BD ne peut survenir (affecte interbase inférieur à 7). Le second est que si le besoin de changer de machine pour une quelques raisons, on doit installer tous les UDF avant de restaurer la sauvegarde de la bd.

Lorsqu'on créer une UDF, on doit garder à l'esprit qu'elle devrait être le plus simple possible. Il est tout à faire possible dans une UDF d'exécuter un programme ou de faire des insertions, suppression... mais une telle approche peut mettre diminuer significativement les performances du serveur de plus ça pourraient occasionner une congestion des transactions.
Interbase inclus plusieurs UDF dont:
  • lower: converti une chaîne de caractère en minuscule
  • strcat: concatène deux chaînes
  • substr: retourne une portion d'une chaîne
  • trim: enlève les espaces d'une chaîne
  • trunc: trunc une chaîne
  • sysdate: retourne la date du système
  • sqrt: retourne la racine carré d'un double
  • abs : retourne la valeur absolue d'un double

Création d'une UDF avec delphi

Nous allons créer une UDF qui retournera la taxe (7%) sur un chiffre passé en paramètre
  • Choisir l'expert dll
  • Ajouter une unité au projet
  • Sauvegarder l'unité sous le nom MathFct
  • Sauvegarder le projet sous le nom brudflib
L'unité MathFct à l'allure suivante
unit MathFct;

interface

function TaxeTps(var i:Double): Double; cdecl; export;

implementation

function TaxeTps(var i:Double): Double; cdecl; export;
begin
  result := i*0.07+i;
end;

end.

Voici maintenant le code du projet
library brudflib;

uses
  SysUtils,
  Classes,
  MathFct in 'MathFct.pas';

{$R *.res}

exports
  TaxeTps;

begin
end.

Notez la présence du exports. TaxeTps fait partie de cette section, elle pourra donc être utilisé par un autre programme.
Il aurait été possible dans l'unité MathFct d'avoir plusieurs fonctions, mais que seulement quelque unes soit accessible
de l'extérieur.Ne pas oublier que tous les paramètres dans une UDF sont passés par pointeur donc il ne faut pas oublier
de mettre var devant nos paramètres.

En exécutant le projet, une dll est créé brudflib.dll. Cette dll doit être placée dans le répertoire UDF d'Interbase, habituellement c'est: disqu:\Borland\InterBase\UDF\. Il faut par la suite déclarer cette fonction pour l'utiliser dans Interbase.

Syntaxe de la déclaration de l'UDF pour Interbase

Voici la syntaxe d'une UDF:
DEFINE EXTERNAL FUNCTION name [<datatype> | CSTRING (int)
[, <datatype> | CSTRING (int) ...]]
RETURNS {<datatype&gt; [BY VALUE] | CSTRING (int)}
ENTRY_POINT "<entryname>"
MODULE_NAME "<modulename>" ;

Déclarons maintenant la fonction à Interbase
Declare external function f_TaxeTps float
  returns float by value
  entry_point 'TaxeTps' module_name 'brudflib.dll';

Il est maintenant possible d'utiliser cette fonction dans Interbase. Nous pouvons donc effectuer ce genre de
requête:
select f_TaxeTps(12.5) from rdb$database

L'exécution de cette requête retournera bien attendu 13,375. Les fonctions ainsi créées peuvent être ensuite utilisées dans n'importe quelle requête. Imaginons que nous avons une table logicielle avec un champ prix. Nous pourrions connaitre le coût du logiciel après taxe en faisant: select f_TaxeTps(prix) from logiciel.

String

Jusqu'à maintenant, nous avons utilisé que des nombres, nous allons maintenant utilisé des chaînes de caractères. Les strings utilisés par Delphi, lui sont propres. Interbase ne les utilise pas. Nous allons donc être obligés d'utiliser la fonction PChar.

function MixStr(Incoming: PChar):PChar;cdecl; export;
var OutStr, IncomingStr: string;
    i, j: Integer;
begin
  IncomingStr := StrPas(Incoming);
  OutStr := '';
  while Length(IncomingStr) > 0
  do begin
       i := Random(Length(IncomingStr) - 1) + 1;
       OutStr := OutStr + IncomingStr[i];
       Delete(IncomingStr, i, 1);
     end;
  StrPCopy(Incoming, OutStr);
  result := Incoming;
end;

Tappez ce code sous votre outil de gestion de bd:
Declare external function f_MixStr cstring(64)
  returns cstring(64)
  entry_point 'MixStr' module_name 'brudflib2.dll';
select f_MixStr('linux') from rdb$database 
Vous devriez voir que les lettres du mot Linux ont changé de position.

Suppression

Il suffit de taper drop external function nom_de_la_fonction pour la supprimer de la bd. Pour notre fonction, ça serait:

drop external function f_MixStr;

jeudi 28 novembre 2002

Requête paramétrée


REQUÊTE PARAMÉTRÉE

Les requêtes paramétrées servent à effectuer des requêtes SQL dynamiques. Le contenu de la requête peut donc varier. C'est très utile lorsque la requête dépend de la saisie d'un utilisateur venant d'un textbox, maskedit... De plus, ça permet d'économiser de la bande passante ce qui n'est pas le cas avec un dblookupbox.

Dans cet exemple nous avons une table client qui contient les champs: nom, prénom, adresse, téléphone, nopermis. Après avoir tapé un numéro de permis de conduire (nopermis) l'information du client sera affichée dans la propriété SQL d'un SQLquery, ibquery... nous entrons par exemple

select NOM, NOPERMIS, PRENOM from CLIENT where nopermis = :nopermis

Cette requêtant étant dans la propriété SQL du ibquery. Le «:» devant nopermis signifie que c'est un paramètre. Il ne reste plus qu'à mettre la valeur du paramètre comme étant notre textbox, maskedit... J'effectue souvent cette opération soit après avoir cliqué sur un bouton ou après être sortie du composant en question.

procedure TfrmLocation.MaskEditExit(Sender: TObject);
begin
  datamodule.query.Active := False;
  datamodule.query.Params[0].AsString := MaskEdit.Text;
  datamodule.query.Active := True;
end;
 

Il est possible d'utiliser la propriété ParamByName au lieu de Params. Le risque d'erreur est plus grand avec Params
lorsqu'on a quelques paramètres. Il est plus difficile de se rappeler ce qu'est le Params[0], Params[1]... que de faire
référence directement à son nom.
Le code ci-dessous permet de voir comment effectuer la même chose, mais avec un ibSQL.

ibsTmp.Close;
ibsTmp.SQL.Clear;
ibsTmp.SQL.Add('select NOM, NOPERMIS, PRENOM from CLIENT where nopermis = :nopermis');
ibsTmp.ParamByName('nopermis').AsString := MaskEdit.Text;
ibsTmp.ExecQuery;


Si la requête ci-dessus nous retournait une valeur, on pourrait affecter ces valeurs à des tlabel.
 
lblNom.Caption := ibsTmp.FieldByName('nom').AsString;
lblPrenom.Caption := ibsTmp.FieldByName('prenom').AsString;
lblNoPermis.Caption := ibsTmp.FieldByName('nopermis').AsString;

samedi 23 novembre 2002

Premier programme Interbase


PREMIER PROGRAMME INTERBASE

Nous allons créer une petite application qui utilisera Interbase, elle permettra de nous familiariser avec les composants de Delphi.

DataModule

Démarrez Interbase ensuite, démarrez Delphi, créez un nouveau projet ensuite ajoutez un datamodule




Au lieu de mettre nos composants de base de donnée sur chaque forme qu'on crée, il est possible de les mettre tous sur un DataModule. On centralise nos composants, il est ainsi beaucoup plus aisé de gérer le tout.
Je vous rappelle ici notre schéma de table



Nous déposerons donc tous nos composants liés à notre bd sur ce DataModule.
Déposons 5 DataSource, 1 ibDataBase,5 ibDataset et ibTransaction sur le DataModule.
Le DataModule devrait ressembler à ceci après avoir mis tous les composants nécessaires dessus et leur avoir donnée leur nom. J'ai mis qu'une transaction pour IbdVille et IbdProvince, puisque nous allons effectuer leur transaction sur la même fiche. Le nom du DataModule est dmGestion et sera sauvegardé sous le nom datamodule.





Lorsque nous ajouterons des formulaires au projet, pour avoir accès au composant de base de donnée, il faudra ajouter datamodule dans le unit du formulaire.




Information des composants

Composant Propriété Valeur
idbdLocation DatabaseName E:\divers\exemple\Exemple1.gdb
idbdLocation LoginPrompt False
idbdLocation Params user_name=SYSDBA password=masterkey
dsVille DataSet ibdVille
dsProvince DataSet ibdProvince
dsVehicule DataSet ibdVehicule
dsClient DataSet ibdClient
dsLocation DataSet ibdLocation

Composant Propriété Valeur
ibdVille Database idbdLocation
ibdVille SelectSQL select * from VILLE
ibdVille InsertSQL insert into VILLE (NOMVILLE) values ( :NOMVILLE)
ibdVille DeleteSQL delete from VILLE where NOMVILLE = :OLD_NOMVILLE
ibdVille Transaction ibtAutres

Composant Propriété Valeur
ibdProvince Database idbdLocation
ibdProvince SelectSQL select * from Province
ibdProvince InsertSQL insert into Province (NOMProvince) values ( :NOMProvince)
ibdProvince DeleteSQL delete from Province where NOMProvince= :OLD_NOMProvince
ibdProvince Transaction ibtAutres

Composant Propriété Valeur
ibdVEHICULE Database idbdLocation
ibdVEHICULE SelectSQL select * from VEHICULE
ibdVEHICULE InsertSQL insert into VEHICULE (NOVEHICULE, MARQUE, ANNEE, KM) values (:NOVEHICULE, :MARQUE, :ANNEE, :KM)
ibdVEHICULE DeleteSQL delete from VEHICULE where NOVEHICULE = :OLD_NOVEHICULE
ibdVEHICULE RefreshSQL Select NOVEHICULE, MARQUE, ANNEE, KM from VEHICULE where NOVEHICULE = :NOVEHICULE
ibdVEHICULE ModifySQL update VEHICULE set NOVEHICULE = :NOVEHICULE, MARQUE = :MARQUE, ANNEE = :ANNEE, KM = :KM where NOVEHICULE = :OLD_NOVEHICULE and

Composant Propriété Valeur
ibdClient Database ibdbLocation
ibdClient SelectSQL select * from CLIENT
ibdClient Transaction ibtClient
ibdClient InsertSQL insert into CLIENT (NOCLIENT, NOM, PRENOM, ADRESSE, NOVILLE, NOPROVINCE, TELEPHONE, NOPERMIS, DATEEXPERATION, CODEPOSTAL) values (:NOCLIENT, :NOM, :PRENOM, :ADRESSE, :NOVILLE, :NOPROVINCE, :TELEPHONE, :NOPERMIS, :DATEEXPERATION, :CODEPOSTAL)
ibdClient DeleteSQL delete from CLIENT where NOCLIENT = :OLD_NOCLIENT
ibdClient RefreshSQL Select NOCLIENT, NOM, PRENOM, ADRESSE, NOVILLE, NOPROVINCE, TELEPHONE, NOPERMIS, DATEEXPERATION, CODEPOSTAL from CLIENT where NOCLIENT = :NOCLIENT
ibdClient ModifySQL update CLIENT set NOCLIENT = :NOCLIENT, NOM = :NOM, PRENOM = :PRENOM, ADRESSE = :ADRESSE, NOVILLE = :NOVILLE, NOPROVINCE = :NOPROVINCE, TELEPHONE = :TELEPHONE, NOPERMIS = :NOPERMIS, DATEEXPERATION = :DATEEXPERATION, CODEPOSTAL = :CODEPOSTAL where NOCLIENT = :OLD_NOCLIENT

Composant Propriété Valeur
ibdLocation Database ibdbLocation
ibdLocation SelectSQL select * from Location
ibdLocation Transaction ibtLocation
ibdLocation InsertSQL insert into LOCATION (NOLOCATION, NOCLIENT, NOVEHICULE, PRIX, DATE_EMPRUNT, DATE_RETOUR) values (:NOLOCATION, :NOCLIENT, :NOVEHICULE, :PRIX, :DATE_EMPRUNT, :DATE_RETOUR)
ibdLocation DeleteSQL delete from LOCATION where NOLOCATION = :OLD_NOLOCATION and
ibdLocation RefreshSQL Select NOLOCATION, NOCLIENT, NOVEHICULE, PRIX, DATE_EMPRUNT, DATE_RETOUR from LOCATION where NOLOCATION = :NOLOCATION
ibdLocation ModifySQL update LOCATION set NOLOCATION = :NOLOCATION, NOCLIENT = :NOCLIENT, NOVEHICULE = :NOVEHICULE, PRIX = :PRIX, DATE_EMPRUNT = :DATE_EMPRUNT, DATE_RETOUR = :DATE_RETOUR where NOLOCATION = :OLD_NOLOCATION

Composant Propriété Valeur
ibtAutres DefaultDatabase idbdLocation
ibtVehicule DefaultDatabase idbdLocation
ibtClient DefaultDatabase idbdLocation
ibtLocation DefaultDatabase idbdLocation

Ajoutez dans la propriété params de chaque transaction le code suivant
read_committed
rec_version
nowait

Ce code permettra de voir les valeurs validées par les autres transactions

Générateur

Comme je l'ai mentionné plutôt, chaque table a un champ unique et qu'il ne peut être nul. Nous utilisons un générateur pour incrémenter cette valeurs. Puisque que nous connaissons pas la valeur des champs uniques devant être inséré, nous devons mettre la propriété required de ces champs à false. Dans l'évènement AfterOpen de chaque ibDataSet, faudra donc mettre la propriété de son cham unique à false.
Exemple
  • Sélectionner ibdVille
  • Aller dans l'inspecteur d'objet
  • Sélectionner l'onglet Évènement
  • Cliquer sur AfterOpen
  • Entrer le code suivant
DataSet.FieldByName('NOVILLE').Required := False;
L'opération prédente doit être effectué pour chaque ibDataset, il suffit de remplacer le champ NOVILLE, par le champ unique du dataset.

Formulaire

Villes et provinces

Nous allons maintenant créer les formlaires nécessaires pour l'application. La première forme sera celle qui permettra de naviguer entre les formes. Nous parlerons plus en détails du TDBLookupListBox dans le prochain chapitre.













Composant Propriété Valeur
DBLookupListBox1 Name dblcVille
dblcVille ListSource dmGestion.dsVille
dblcVille ListField NOMVILLE
dblcVille KeyField NOVILLE
DBLookupListBox2 Name dblcProvince
dblcProvince ListSource dmGestion.dsProvince
dblcProvince ListField NOMPROVINCE
dblcProvince KeyField NOPROVINCE
Edit1 Name edtVille
Edit2 Name edtProvince

Ajout et suppresion

Dans ce formulaire, nous n'avons pas utilisé de dbnavigator. De cette façon, on pourra apprendre commet on fait pour ajouter et effacé manuellement.
Avant d'ajouter, on va regarder s'il existe une donnée ayant la même valeur que l'on désire insérer. Cette opération sera effectuée grâce à la méthode locate du ibDataset. Le code suivant doit être ajouté dans l'évènement onclick du bouton ajouter (ville).

if not(dmGestion.ibdVille.Locate('NomVille',edtVille.Text,[])) then
  begin
    dmGestion.ibdVille.Append;
    dmGestion.ibdVille.FieldValues['NomVille'] := edtVille.Text;
    dmGestion.ibdVille.Post;
    edtVille.Text := '';
  end;

Append, insère un enregistrement vide. Ensuite on met la valeur du edit box dans le
champ Ville. Finalement, post envoit les données à Interbase.
Il faudra aussi ajouté ce code pour les provinces.
Maintenant pour effacer, il sélectionnant un item, ce dernier devient actif. Il reste plus qu'à faire appel à la méthode delete du ibDataset

if dblcVille.SelectedItem <> '' then
    dmGestion.ibdVille.Delete;

Transcation

Les transactions ont déjà été abordées précédemment. La transaction est déjà active. Lorsqu'on quitte la fiche, on tente de sauvegarder sinon on revient dans l'état initial. Il est préférable d'utiliser le CommitRetaining et le RollbackRetaining pour des raisons de performances puisque la transaction demeure ouverte. En employant commit et Rollback, la transaction doit se fermer et ouvrir de niveau ce qui pas nécessaire pour une simple mise à jour. Adapter ce code pour les autres transactions et ajouter le dans l'évènement OnClose de chaque form.

try
    dmGestion.ibtAutres.CommitRetaining;
  except
    dmGestion.ibtAutres.RollbackRetaining;
  end;

Véhicule

 


Composant Propriété Valeur
DBNavigator1 Name dbnVehicule
dbnVehicule DataSource dmGestion.dsVehicule
dbEdit1 Name dbeMarque
dbeMarque DataSource dmGestion.dsVehicule
dbeMarque DataField Marque
dbEdit2 Name dbeKm
dbeKm DataSource dmGestion.dsVehicule
dbeKm DataField Km
dbEdit3 Name dbeAnnee
dbeAnnee DataSource dmGestion.dsVehicule
dbeAnnee DataField Annee

Client

 


Champ persistent

On doit entrer un numéro de téléphone ainsi qu'un code postal pour un client. Ces données ont un certain format pour qu'il soit valide. C'est là que les champs persistent entre en action. Il est possible de
  • mettre des mises en forme particulière
  • d'appliquer des contraintes de saisi
  • d'effectuer des validations
En effectuant de telle opération au niveau client, on évite du traffic réseau et on personnalise d'avantage notre application.
Pour ajouter des champs persistent, il faut faire un clique droit de la souris sur notre ibdClient




Une image comme celle-ci apparaitra





Ensuite on fait un clique droit de la souris et on choisis l'option "Ajouter tous les champs"

Tous les champs sont maintenant ajoutés




Nous allons maintenant changer la mise en forme du champ téléphone.
  • Sélectionner le champ téléphone
  • Cliquer dans sa propriété EditMask
La fenêtre suivant s'ouvrira


La section du masque permet de définir les critères de saisi possible. Aller dans l'aide pour de plus ample renseignement sur les codes disponibles. Si vous ne voulez pas sauvegardé les caractère unilatéraux (dans l'exemple de tel caractère sont ()-, décochez la case "Enregistrer les caractères unilatéraux. Ensuite vous pouvez tester votre masque dans la section définie à cet effet. 

Faire de même pour le code postal.



Il est aussi possible d'effectuer des validations sur des champs, la propriété validate remplit cette fonction.

Composant Propriété Valeur
DBNavigator1 Name dbnClient
dbnClient DataSource dmGestion.dsClient
dbEdit1 Name dbeNom
dbeNom DataSource dmGestion.dsClient
dbeNom DataField NOM
dbEdit2 Name dbePrenom
dbePrenom DataSource dmGestion.dsClient
dbePrenom DataField PRENOM
dbEdit2 Name dbeAdresse
dbeAdresse DataSource dmGestion.dsClient
dbeAdresse DataField Adresse

TDBLookupComboBox

Je prends un petit moment pour vous expliquer d'avantage ce composant. Vous l'avez surement remarqué, dans la table client, il y a les champs noville et noprovince. Ces champs font naturellement références aux tables. Il faut donc trouver un moyen d'avoir le noville de la table Ville et d'envoyer cette valeur dans le noville de Client. Il est possible de le faire avec les TDBLookupComboBox.




On remplira le TDBLookupComboBox comme d'habitude.

Composant Propriété Valeur
DBLookupComboBox1 Name dblcVille
dblcVille ListSource dmGestion.dsVille
dblcVille ListField NOMVILLE
dblcVille KeyField NOVILLE

La propriété ListSource permet d'identifier la source de donnée qui seront affiché dans le contrôle. Nous utilisons le ibDataSet de Ville qui fait référence à notre table Ville.
La propriété ListField identifie le champ qui sera affiché dans le DBLookupComboBox. Nous affichons le nom de la ville. Sachez qu'il est aussi possible d'afficher plus valeur, il suffit de les séparer par un ";".

La propriété KeyField identifie le champ auquel sa valeur sera envoyée au datafield. Ce champ a la valeur NoVille car dans notre table Client nous y faisons référence grace à noville.

Composant Propriété Valeur
dblcVille DataSource dmGestion.dsClient
dblcVille DataField NoClient
DataSource représente la table qui va reçevoir la valeur du champ noville (propriété KeyField). dans notre cas c'est le dataset Client.

Finalement le champ DataField, représente le champ du dataset Client qui reçevra la valeur de la propriété KeyFiel. Cette valeur est noclient

Location



Un programme montrant les acquis est disponible ici.
Quelques éléments du programme sont manquants, vous pouvez les ajouter et compléter votre formation.

mercredi 16 octobre 2002

Comment avoir la ligne courante dans un memo?

Comment avoir la ligne courante dans un memo?
procedure TForm1.MemExpKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  LigneNum: LongInt;
begin
  if (Key=VK_UP)or(Key=VK_DOWN) then
  begin
    LigneNum:=MemExp.Perform(EM_LINEFROMCHAR, MemExp.SelStart, 0);
    Label1.Caption:='Line - '+IntToStr(LigneNum+1);
  end;
end;

dimanche 6 octobre 2002

Comment créer un composant à l'exécution

Comment créer un composant à l'exécution
procedure TForm1.Button1Click(Sender: TObject);
var
  Button: TButton;
begin
  Button:=TButton.Create(Form1);
  try
    with Button do 
    begin
      Parent:=Self;
      Left:=Random(Form1.ClientWidth-Width);
      Top:=Random(Form1.ClientHeight-Height);
      Caption:='Button';
    end;
  except
    Button.Free;
  end;
end;

mardi 1 octobre 2002

Quel librairie ce programme utilise?


Quel librairie ce programme utilise?

Un gros programme risque d'avoir une longue liste de librairie... ci-dessous seul quelques fichiers sont affichés
ldd /opt/kde3/bin/kopete
linux-gate.so.1 =>  (0xffffe000)
libkopete.so.1 => /opt/kde3/lib/libkopete.so.1 (0x4002e000)
libkabc.so.1 => /opt/kde3/lib/libkabc.so.1 (0x40125000)
libkutils.so.1 => /opt/kde3/lib/libkutils.so.1 (0x401dd000)
libkio.so.4 => /opt/kde3/lib/libkio.so.4 (0x40242000)
libkdeui.so.4 => /opt/kde3/lib/libkdeui.so.4 (0x40583000)
libkdecore.so.4 => /opt/kde3/lib/libkdecore.so.4 (0x40860000)
libdcop.so.4 => /opt/kde3/lib/libdcop.so.4 (0x40aa2000)

vendredi 30 août 2002

Web Services Development with Delphi

Ce livre m'a un peu déçu, je l'achètais en ayant en tête d'apprendre à développer des webservices avec des bases de données interbase au minimum. 

 Le livre ne fait que présenter des exemples anodin. 

Ce livre aborde les actives x, com, com+, ado, dbexpress, datasnap, application multi-tiers, les sockets, le xml. Malheuresement, rien n'est amplement approfondie, les exemples sont trop basiques.

mercredi 24 juillet 2002

Composant Ibx


COMPOSANT IBX

IbTable

Ce composant encapsule une table dans une base de donnée. Il est souvent utilisé pour migrer plus aisément du bde à Interbase. Puisque Interbase est orienté client/serveur, les IbTable ne devraient pas être utilisé.

IbQuery

Ce composant permet d'exécuter des instructions SQL. On peut ainsi accéder à plusieurs tables simultanément.

IBUpdateSQL

Ce composant met à jours des données en lecture seule. Ce composant est utilisé conjointement avec un IbQuery ou IbDataset. Il permet d'insérer, mettre à jour, effacé et de rafraichir un ensemble de donnée qui ne peut être modifié habituellement. Ces opérations peuvent être générées automatiquement en effectuant un clique droit sur le composant.

IBDataset

Ce composant rassemble les caractéristiques d'un IbQuery et d'un IBUpdateSQL. Il est donc possible de récupérer des données, de mettre à jour des données, de supprimer des données et de rafraîchir des données

IbSQL

Ce composant permet d'exécuter des instructions SQL. On peut ainsi accéder à plusieurs tables simultanément. Ce composant ne peut être lié à des composants orienté donnés tel que dbedit, dbgrid... Il faudra alors prendre les valeurs retournées (pour un select) et l'affecter à un composant. Il est possible de faire exécuter à peu près n'importe commande SQL avec ce composant. Ce composant est plus rapide que les autres dû au fait qu'il n'offre pas d'interface orienté donné. Ce composant est unidirectionnel. Il faut donc ré exécuter la requête pour retourner au début des enregistrements. (pour un sélect).

IBStoredProc

Ce composant encapsule une procédure stockée sur un serveur Interbase. Une telle procédure ressemble à une fonction en pascal. On lui passe des paramètres et nous renvois un résultat. Elle est exécutée sur le serveur, ce qui allége le traffic réseau.

IBDataBase

Ce composant encapsule une connexion à Interbase. Toute application désirant se connecté à Interbase doit en posséder un.

IBTransaction

Ce composant fournit des transactions sur une connexion sur la base de donnée. Il est conseillé dans avoir un pour chaque composant exécutant des requêtes sur Interbase. On isole ainsi nos transactions, il est plus aisé de revenir en arrière si un problème survient lors d'un commit.

IBClientDataSet

Ce composant regroupe en fait un IBDataset et un DatasetProvider afin de retrouver des données et d'effectuer des mises à jour. Ce composant est très utile si vous désirez créer une application n-tiers.

Quelques-uns de ces composants seront utilisés dans notre programme afin de mieux les comprendre

samedi 13 juillet 2002

Intitiation à Interbase


INITIATION

Avant de pouvoir effectuer ce tutoriel adéquatement, il faudra que vous ayez installé Interbase Server et le client ainsi que ibconsole sur votre ordinateur. Les composants ibx sont aussi nécessaire. J'ai utilisé Interbase 6.01 disponible gratuitement chez Borland et aussi Ibconsole disponbile au même endroit.

Ibconsole

Ibconsole permet de gérer et de maintenir Interbase server, il vous permet d'exécuter toutes commandes SQL. Tout se fait à l'aide d'une interface graphique fort conviviale.

Histoire d'Interbase

Interbase existe depuis le millieu des années 80. La fiabilité et la puissance de cette basse de donnée, ont fait sa renommée. Borland a ouvert ses sources en l'an 2000. Le serveur est disponible sous Windows, Linux et quelques Unix. Il est même possible de le faire fonctionner sous MacOS X. Peu de ressource et d'espace disque sont nécessaire au fonctionnement d'Interbase. Nokia, Motorola, Boeing... sont quelques unes des nombreuses compagnies qui utilisent cette base de donnée.

Préparation


Démarrer Interbase manager, l'icone Interbase Guardian apparaîtra dans la barre des tâches en bas à droite.

Démarrer Ibconsole,



cliquer ensuite sur le menu Server et ensuite register




Entrer SYSDBA dans la section <username> et masterkey dans la section <password> appuyer ensuite sur le bouton Ok.
L'image ci-dessous apparaîtra



Cliquer sur le +, différentes sections seront affichées. Sélectionner database, faites un clique droit de la souris et appuyer sur Create Database...





Entrer le nom d'alias et l'emplacement souhaité pour la base de donnée


Notez bien que le dialecte 1 et 2 ne devrait plus être utilisé. En dialecte 3, on doit déclarer nos champs de type TimeStamps si on désire avoir la date et heure (TDateTime). Si tout c'est bien déroulé, vous verrez l'écran ci-dessous apparaître.






Maintenant, vous pouvez créer des tables, générateur, trigger...
J'utiliserai un exemple de location de voiture réduit à l'essentiel afin de mieux vous faire comprendre l'utilisation d'Interbase grâce à delphi. Cet exemple sera utilisé tout au long de ce tutoriel.
Je vous invite à parfaire vos connaissances en SQL car je n'expliquerai pas en détail les commandes SQL effectuées. Vous pouvez vous renseigner sur le sujet à l'aide des sites que voici:
En cliquant sur l'onglet interractive SQL sous ibconsole et en allant dans le menu aide, vous avez un guide complet SQL.

Création d'une base de donnée

Notre exemple utilisera cinq tables.
Voici le diagramme de tables



Démarrer ibconsole, appuyer sur Interactive SQL. Entrer les valeurs suivantes
create table CLIENT (
NOCLIENT             INTEGER      not null,
NOM                  VARCHAR(30),
PRENOM               VARCHAR(30),
ADRESSE              VARCHAR(25),
NOVILLE              INTEGER,
NOPROVINCE           INTEGER,
TELEPHONE            VARCHAR(10),
NOPERMIS             VARCHAR(25),
DATEEXPERATION       VARCHAR(6),
CODEPOSTAL           VARCHAR(6),
constraint PK_CLIENT primary key (NOCLIENT)
);

create table LOCATION (
NOLOCATION           INTEGER not null,
NOCLIENT      INTEGER,
NOVEHICULE           INTEGER,
PRIX                 DECIMAL,
DATE_EMPRUNT         DATE,
DATE_RETOUR          DATE,
constraint PK_LOCATION primary key (NOLOCATION)
);

create table VEHICULE (
NOVEHICULE           INTEGER      not null,
MARQUE               VARCHAR(25),
ANNEE                INTEGER,
KM                   INTEGER,
constraint PK_VEHICULE primary key (NOVEHICULE)
);

create table VILLE (
NOVILLE              INTEGER                        not null,
NOMVILLE             CHAR(25)                       not null,
constraint PK_VILLE primary key (NOVILLE)
);

create table PROVINCE (
NOPROVINCE           INTEGER                        not null,
NOMPROVINCE          CHAR(25)                       not null,
constraint PK_PROVINCE primary key (NOPROVINCE)
);
Nous ajoutons ensuites les contraintes
alter table CLIENT
   add constraint FK_CLIENT_REFERENCE_VILLE foreign key (NOVILLE)
      references VILLE (NOVILLE);

alter table CLIENT
   add constraint FK_CLIENT_REFERENCE_PROVINCE foreign key (NOPROVINCE)
      references PROVINCE (NOPROVINCE);
      
alter table LOCATION
   add constraint FK_LOCCLIENT_REFERENCE_LOC foreign key (NOCLIENT)
      references CLIENT (NOCLIENT);
      
alter table LOCATION
   add constraint FK_LOCATION_REFERENCE_VEHICULE foreign key (NOVEHICULE)
      references VEHICULE (NOVEHICULE);

Toutes les contraintes ainsi que les tables sont créées. La prochaine étape est la création des générateurs.

jeudi 11 juillet 2002

Générateur dans Interbase


GÉNÉRATEUR

Les générateurs sont employés afin que chaque enregistrement soit unique. Sous Paradox, le type de donnée employé pour effectuer cette opération est autoinc. Puisque Interbase est orienté client/serveur, c'est le serveur qui doit créer la valeur et non le client. Si la valeur était créée au niveau client, il deviendrait alors possible d'avoir plusieurs champs avec la même valeur.

Un tel comportement doit être évité dans une architecture telle que celle d'Interbase. À chaque insertion d'une valeur dans une table un champ utilisé par un générateur sera incrémenté.

Nous continuer avec l'exemple du chapitre précédent. Nous allons créer 5 générateurs, 1 par table.

Création de générateur

La syntaxe est CREATE GENERATOR nom générateur
Dans la table client, nous avons un champ noclient, ce champ est unique à chaque client. Il ne peut donc y avoir deux valeurs identiques. Nous devons donc créer un générateur pour ce champ. Pour cette table il suffira de faire

CREATE GENERATOR CLIENT_GenId;

La création des autres générateurs se fera par
CREATE GENERATOR LOCATION_GenId;
CREATE GENERATOR VEHICULE_GenId;
CREATE GENERATOR VILLE_GenId;
CREATE GENERATOR PROVINCE_GenId;
 

J'ai l'habitude de rajouter _GenId afin de savoir plus aisément que c'est un générateur.
Il est plus facile de le reconnaître dans le code, si on doit l'utiliser.

Trigger

Nous utiliserons les triggers lors de l'évènement "before insert" afin incrémenter automatiquement les clé primaires. En laissant ce travail à Interbase, nous réduisons les erreurs possibles pouvant être faites par les programmeurs. Le code ci-dessus permettra de créer tous les triggers nécessaire au projet.

SET TERM ^;
CREATE TRIGGER TRIG_CLIENT_GenId FOR CLIENT
ACTIVE BEFORE INSERT AS
BEGIN
  IF ( NEW.NOCLIENT IS NULL ) THEN
    NEW.NOCLIENT = GEN_ID(CLIENT_GenId,1);
END


SET TERM ^;
CREATE TRIGGER TRIG_VILLE_GenId FOR VILLE
ACTIVE BEFORE INSERT AS
BEGIN
  IF ( NEW.NOVILLE IS NULL ) THEN
    NEW.NOVILLE = GEN_ID(VILLE_GenId,1);
END

SET TERM ^;
CREATE TRIGGER TRIG_LOCATION_GenId FOR LOCATION
ACTIVE BEFORE INSERT AS
BEGIN
  IF ( NEW.NOLOCATION IS NULL ) THEN
    NEW.NOLOCATION = GEN_ID(LOCATION_GenId,1);
END

SET TERM ^;
CREATE TRIGGER TRIG_PROVINCE_GenId FOR PROVINCE
ACTIVE BEFORE INSERT AS
BEGIN
  IF ( NEW.NOPROVINCE IS NULL ) THEN
    NEW.NOPROVINCE = GEN_ID(PROVINCE_GenId,1);
END

SET TERM ^;
CREATE TRIGGER TRIG_VEHICULE_GenId FOR VEHICULE
ACTIVE BEFORE INSERT AS
BEGIN
  IF ( NEW.NOVEHICULE IS NULL ) THEN
    NEW.NOVEHICULE = GEN_ID(VEHICULE_GenId,1);
END

Conseil

Puisque nous utilisons les triggers et que nos clés primaires (noclient, noville...) ne peuvent être nulles, il est possible de réduire le trafic réseau. Au niveau client, nous allons mettre la propriété Required des dataset, query... de nos clés primaire à false. Cette technique va être prochainement expliquée. Ce conseil m'a été donné par Sylvain James.

Sylvain James a créé un utilaire pour automatiser la création des générateurs et trigger, vous pouvez le télécharger ici

mercredi 13 février 2002

MyBase


MYBASE

MyBase est une mini base de donnée fonctionnant avec le composant TClientDataSet. MyBase nécessite aucun moteur de base de donnée. MyBase utilise des fichiers xml ou binaire. Lors de son utilisation, il suffit d'inclure midas.dll sous windows ou midas.so sous linux.

Il est aussi possible de mettre MidasLib dans la clause du unit afin de ne pas être obligé de déployer les librairies mentionné ci-haut.MyBase fonctionne sous windows et linux, son utilisation peut donc s'avérer très intéressante.

Afin de faciliter la compréhension de MyBase, nous allons créer un programme fort simple de gestion de courriel pour des écoles. Nous verrons ainsi les relations maitre-détails. Chaque courriel sera associé à une école.

Préparation de la bd

A partir de l'onglet "accès bd, déposé sur la fiche 2 TDataSource et 2 TClientDataSet.


Inscriver à la propriété name d'un TClientDataSet cdsEcole pour l'autre inscriver cdsCourriel. Ensuite pour les TDataSource inscriver pour leur nom dsEcole et pour l'autre dsCourriel.


Ensuite relier le TDataSource au TClientDataSet grâce à sa propriété dataset. Pour dsEcole, choisisé cdsEcole faite de même pour dsCourriel.



Création d'une base de donnée

Maintenant nous allons créer les champs pour chaque clientdataset. cdsEcole aura un champ NoEcole et Ecole. cdsCourriel aura un champ courriel et noecole.
  • Sélectionner cdsCourriel, faite un click droit de la souris dessus. Cliquer sur "Editeur de champs..."

  • Faite une click droit de la souris dans la fenêtre blanche qui apparaît et cliquer sur Nouveau champ


  • Inscriver NoEcole dans la case Nom, la case Composant s'inscrit automatiquement. Pour ce champ, nous allons sélectionner le type AutoInc. Le système se chargera d'attribuer à chaque ajout une nouvelle valeur à ce champ. Pour le champ Ecole et courriel, sélectionner le type string et inscriver 25 dans la case taille.


  • Faite un click droit de la souris sur cdsEcole, cliquer sur "Créer un ensemble de donnée". Ensuite cliquer sur "Enregistrer dans une table Xml MyBase..." et tapé ecole comme nom de base de donnée. Faite de même pour cdsCourriel et nommer sa base de donnée courriel.

  • A la propriété de cdsEcole inscriver le nom de la base de donnée que vous avez créer précédemment: ecole.xml pour cdsEcole et courriel.xml pour cdsCourriel.


Nous allons maintenant permettre d'ajouter des écoles. Pour ce faire, déposer 1 TBouton et 1 TEdit sur la fiche.




Dans la propriété click du bouton pour ajouter une école, nous allons ajouter quelques instructions.

procedure TMyBase.cmdEcoleClick(Sender: TObject);
begin
  if txtEcole.text <> '' then 
  begin
    cdsEcole.Insert;
    cdsEcole.FieldByName('Ecole').AsString; := txtEcole.Text;
    cdsEcole.Post;
    txtEcole.Text := '';
  end;
end;
Insert, permet d'ajouter un enregistrement vide.

La deuxième instruction copie la valeur du TEdit dans le champ courant Ecole. La propriété FieldByName permet de trouver un champ par son nom. On peut ainsi leur affecter des valeurs.

Il est aussi possible d'y accéder par un index. Je vous conseille vivement d'aller regarder les nombreuses propriétés des TClientDataSet.

Post envoit les enregistrements dans un journal de modification. Les données ne seront écrites dans le fichier xml que lorsqu'on appellera la méthode mergechangelog.

Nous allons ajouter un TDBLookupComboBox à la fiche. Ce composant permettra de voir toutes les écoles disponibles.





La propriété "ListSource", permet d'établir la source de donnée qui remplira la TDBLookupListBox.

La propriété "ListField" correspond aux champs qui seront affichés dans la TDBLookupListBox. Il est possible d'en afficher plusieurs en les séparants par un ";". Il aurait donc été possible d'afficher le champ NoEcole et Ecole par l'instruction "NoEcole;Ecole".

La propriété "KeyField" sera utile lorsqu'on établira le lien pour effectuer la relation maitre/détail.Nous en parlerons en détail un peu plus loin.

Dans l'évènement AfterPost de cdsEcole, nous pouvons ajouter "cdsEcole.MergeChangeLog;". Ainsi les ajouts, modification, suppression qui auront été notés dans le journal de modification sont appliqué au fichier xml en appelant la méthode MergeChangeLog.

Après avoir ajouté quelques écoles, voici le résultat





Nous allons maintenant nous attaquer au courriel. Poser un dbgrid sur la fiche ainsi qu'un bouton. Modifiez la propriété datasource du dbgrid pour dsCourriel.
Dans la propriété click du bouton pour ajouter des adresses courriels, nous allons ajouter quelques instructions.

procedure TMyBase.cmdCourrielClick(Sender: TObject);
begin
  if cdsCourriel.state in [dsInsert, dsEdit] then cdsCourriel.Post;
end;

Puisque nous utilisons un composant lié directement avec une base de donnée, nous devons vérifier dans quel état est le clientdataset avant de faire un post. Si nous n'effectuons pas une vérification et que le clientdataset ne serait pas en mode insertion ou modification, il y aurait un message d'erreur qui s'afficherait. Cette vérification n'était pas nécessaire tantôt puisqu'on appelait la méthode insert, on attribuait une valeur au champ et finalement on appelait la méthode post. En liant le dbgrid au clientdataset beaucoup de travail est fait pour nous.

Dans l'évènement AfterPost de cdsCourriel, nous pouvons ajouter "cdsCourriel.MergeChangeLog;". Ainsi les ajouts, modification, suppression qui auront été noté dans le journal de modification sont appliqués au fichier xml en appelant la méthode MergeChangeLog.

Nous pouvons dès maintenant débuter à ajouter des courriels, mais ils ne seront pas lié à une école automatiquement.

Relation maitre/détail

Établir une telle relation est assez facile.
1) Sélectionner cdsCourriel, inscrivé dsEcole pour sa propriété MasterSource





2) Cliquer sur la propriété MasterFields. C'est ici qu'on va établir la relation maitre(ecole), détail (courriel)





Maintenant après avoir sélectionné une école, lorsque vous entrez un courriel le champ noecole sera lié. Ceci est dû à la propriété KeyField du TDBLookupListBox.

À partir de ce moment, lorsque vous sélectionnerez une école, seul les courriels qui y sont lié seront affichés.

Vous pouvez visionner le contenu des fichier xml en tout temps à l'aide d'un navigateur web.
Vous pouvez télécharger les sources du projet ici.

dimanche 3 février 2002

Exception

EXCEPTION
Tout programme n’est pas à l’abri d’erreurs : valeur erronée, fichier introuvable, type de donnée incompatible... Se mettre dans la peau d’un usager aide à voir le type d’erreur qu’il pourrait faire. On doit gérer les erreurs d'un programme avec les exceptions, même s'il est possible de le faire autrement.
Répondre aux exceptions
Lorsqu'une exception survient, l'application lève une exception. On peut ainsi exécuter une partie de code fonctionnel et détruirel'erreur.
Certaine ressources devraient toujours être protégées
  • Les fichiers
  • La mémoire
  • Les ressources de Windows ou de Linux
  • Les objets
La syntaxe des traitements des exceptions est simple

try avec except


try
  instruction
except
  on TypeException do begin
  instruction
end;
end;

Le mot try débute le bloc d’exception, on inscrit les instructions voulues. Si durant le 
déroulement du programme une exception de type TypeException est levé, les instructions de ce 
bloc seront exécutées 

try avec finally

Quoiqu'il arrive dans le déroulement du programme, le code contenu dans le bloc finally sera exécuté s'il y a une erreur ou non
try
   instruction
finally
   instruction
end;

var
  bit:tbitmap;
begin
  try
    bit  := tbitmap.Create;
  finally
    bit.free;
  end;
end;

Librairie d'exécution

Lorsqu'on utilise les librairies d'exécution (rtl)comme les fonctions, les procédures de fichiers, les conversions..., les rtl reportent les erreurs à l'application sous la forme d'exception. Un message généré à l'usager lui sera affiché. On peut définir nos propres exceptions lorsqu'on fait appel à ce type de librairie.

begin
  try
    Result:=Dividende div Diviseur;
  except
    on EDivByZero do
    begin
      result:=0;
      showMessage('Division par zéro impossible');
    end;
  end;
end;
On pourrait arriver aux mêmes résultats en utilisant un if, mais l'utilisation des exceptions est beaucoup plus efficace.
Code source disponible ici.

Définir ses exceptions

Afin de sécuriser nos applications contre d'éventuelles erreurs, on peut créer nos propres exceptions afin de les prévenir. Les exceptions sont des objets, alors nous devons les déclarer comme un objet.

type
   EMotInvalide = class(Exception);
Après avoir déclaré notre exception, on peut l'appeler à tout moment. On doit employer le mot raise pour appeler l'exception.
if Mot == MotInvalide then 
   raise EMotInvalide.create('Mot invalide');

Les exceptions silencieuses

Les applications de Delphi manipulent la plupart des exceptions que votre code ne manipule pas spécifiquement en affichant un message d'erreur. Il est possible d'arriver au même résultat en utilisant les exceptions dites silencieuses, qui n'afficheront pas de message d'erreur à l'usager. Ce type d'exception est particulièrement intéressante, lorsqu'un veut arrêter une partie d'un programme.

Pour ce faire, on utilise la procédure Abort;

samedi 26 janvier 2002

Enregistrement

ENREGISTREMENT
Les enregistrements sont un regroupement de données qui ont un lien entre elles. Un enregistrement équivaut à une structure en langage c. On regroupe donc plusieurs variables sous une seule. Une telle approche évite à devoir créé plusieurs variables. On travaille avec une variable au lieu de plusieurs, le risque d'erreur est diminué.

TInfoClient = record
Prenom:string[20];  
Nom:string[20];   
Adresse:string[30];   
Ville:string[20];   
CodePostal:string[6];
end;

Champs

Les éléments de l'enregistrement: Prenom, Nom... sont appelés des champs. Après avoir défini le type, on peut créer des instances, variables de ce type. On accède au champ de l'enregistrement en apposant un point devant le nom de l'enregistrement.
var
InfoClient:TInfoClient;
begin
  InfoClient.Prenom:='Paul';  
  InfoClient.Nom:='Smith';  
  InfoClient.Adresse:='3455 Cartier'; 
  InfoClient.Ville:='Montreal';  
  InfoClient.CodePostal:='J4S1R4';
end;

With

Lorsqu'on a plusieurs variables à écrire ça devient vite fastidieux. On peut alléger le code en utilisant «with».
var
InfoClient:TInfoClient;
begin
  with InfoClient do 
  begin      
    Prenom:='Paul';
    Nom:='Smith';
    Adresse:='3455 Cartier';
    Ville:='Montreal';
    CodePostal:='J4S1R4';
  end;
end;

Tableau d'enregistrement

Les tableaux peuvent aussi être incorporés dans les enregistrements. C'est lorsqu'on définit l'instance qu'on a créé le tableau.
var
   Etudiant:array [1..5] of TEtudiant;
begin
   Etudiant[1].Prenom:='Roger';
   Etudiant[1].Nom:='Demers';
   Etudiant[1].Matiere:='Informatique';
   Etudiant[2].Prenom:='Jean';
   Etudiant[2].Nom:='Ducharme';
   Etudiant[2].Matiere:='Chimie';
end;
Code source disponible ici.