menu
  Home  ==>  articles  ==>  bdd  ==>  display_clientdataset_xml   

display_clientdataset_xml - John COLIBRI.


1 - Introduction

Nous allons présenter ici un utilitaire qui affiche le contenu d'un tClientDataset.

Un tClientDataset est un composant qui gère des données sous forme de table en mémoire. Ces données peuvent provenir:

  • soit d'une création par code
  • soit d'un Serveur de bases de données (Oracle, Interbase, MySql, Sql Serveur...)
  • soit d'un fichier disque ayant un format particulier
Ce composant permet la "gestion nomade" de tables:
  • l'utilisateur récupère des données du Serveur distant le matin sur son portable
  • sauve ces données sur son disque dans un fichier
  • chez ses clients, il rallume son portable, recharge les données, effectue des modification (ajout, effacement, modification) et sauvegarde ses données sur disque
  • le soir, de retour au bureau, il recharge les données et met à jour le Serveur distant
Le composant permet aussi de gérer des tables indépendantes sans avoir à utiliser aucun moteur de base de données (le tClientDataset sait sauver et relire des données dans un fichier disque local).

Ce qui nous intéresse ici est l'affichage du contenu des données d'un tClientDataset.




2 - Principe du stockage

2.1 - Analyse mémoire et Analyse disque

Le tClientDataset stocke les lignes de la Table dans une structure mémoire. Le source de tClientDataset n'a pas été publié par Borland. Lorsque Kylix et Delphi 6 ont été lancés, j'ai beaucoup travaillé sur la toute nouvelle architecture dbExpress dont le tClientDataset est un des composants essentiels. J'avais alors analysé le contenu mémoire en utilisant des dumps directs de la mémoire (les INTERFACE permettent de récupérer l'adresse de l'objet et donc en analysant la mémoire il est possible de retrouver la structure). Ces techniques permettaient aussi d'analyser les paquets de données échangées entre le tClientDataset et le DatasetProvider (envoi du schéma et des contraintes, envoi des données, envoi des mises à jour, envoi de la liste des erreurs).

Il existe toutefois une façon plus simple d'analyse des données d'un tClientDataset: il suffit de sauvegarder ses donnée sur disque et d'analyser les données sur disque. Le format n'est bien sur pas identique (en mémoire, les données sont sous forme de colonnes, sur disque, elles sont sous forme de lignes). Mais puisque le tClientDataset sait relire les données du disque sans perte de substance, l'analyse des données du fichier fournit une image fidèle de ce que contient les données en mémoire.

2.2 - Intérêt

Quel intérêt y a-t-il à analyser les données d'un tClientDataset ?

Eh bien, comparé à des Tables relationnelles, ce composant a un comportement très "riche": il peut annuler entièrement ou partiellement des modifications effectuées auparavant, utiliser des clones etc. La bonne connaissance du contenu des données facilite alors grandement l'apprentissage et la compréhension du fonctionnement du tClientDataset.

2.3 - Les formats disques

Le tClientDataset peut être sauvegardé sur disque sous format binaire ou .XML. Nous allons analyser le format .XML qui est plus simple à présenter.




3 - Création du fichier .XML

3.1 - Création des données

La création d'une nouvelle table est très simple:
  • nous définissons chaque champ en appelant:

        FieldDefs.Add

  • la table est créée par

         CreateDataset

  • nous ajoutons des données par

    AppendRecord



Par conséquent:
   créez une nouvelle application et appelez-la "display_cds_xml"

   sélectionnez dans la page "dbExpress" de la Palette le composant ClientDataset:

et posez-le sur la tForm

   sélectionnez dans la page "Data Access" de la Palette le composant DataSource:

et:

  • posez-le sur la tForm
  • sélectionnez sa propriété DataSet et placez-y ClientDataset1
   sélectionnez dans la page "Data Control" de la Palette le composant dbGrid:

et:

  • posez-le sur la tForm
  • sélectionnez sa propriété DataSource et placez-y DataSource1
   placez un tButton "create_" sur la Forme et créez sa méthode OnClick. Placez-y les instructions de création des données de ClientDataset. Dans notre cas nous créons une table contenant quelques articles de ce site:

    procedure TForm1.create_Click(SenderTObject);
      begin
        with ClientDataset1 do
        begin
          Close;

          FieldDefs.Clear;

          with FieldDefs do
          begin
            Add('m_id'ftInteger, 0);
            Add('m_titre'ftString, 20);
            Add('m_date'ftDate, 0);
          end// with FieldDefs

          CreateDataSet;

          AppendRecord([1, 'Site Editor''1/9/2001']);
          AppendRecord([1, 'Pascal To Html''1/10/2001']);
          AppendRecord([1, 'Blobs Interbase''2/1/2002']);
          AppendRecord([1, 'CGI Form''1/9/2002']);
          AppendRecord([1, 'Find USES''5/6/2003']);
          AppendRecord([1, 'Tutorial Interbase''5/3/2004']);
          AppendRecord([1, 'Dump INTERFACE''5/6/2004']);
          AppendRecord([1, 'Interprète d''expression''5/9/2004']);
          AppendRecord([1, 'ISO CD Extractor''6/9/2004']);
          AppendRecord([1, 'GOF Design Patterns''3/9/2004']);
          AppendRecord([1, 'dbExpress Interbase''15/10/2004']);

          Open;
        end// with ClientDataset1
      end// create_Click

   compilez, exécutez, et cliquez le bouton:




Pour sauvegarder les données:
   placez un tEdit sur la Forme et initialisez Text à "resu"
   placez un tButton "save_" sur la Forme et créez sa méthode OnClick. Placez-y les instructions de sauvegarde du ClientDataSet sur disque:

    procedure TForm1.save_Click(SenderTObject);
      begin
        ClientDataSet1.SaveToFile(Edit1.Text'.xml');
      end// save_Click

   compilez, exécutez et cliquez "save_"
   le programme sauvegarde le ClientDataSet1


Nous pouvons examiner le contenu du fichier .XML en cliquant sur ce fichier dans un Explorateur Windows: comme .XML est associé à Internet Explorer (ou tout autre Browser que vous utilisez), nous voyons apparaître les données contenues dans ce fichier:



3.2 - Chargement

Nous pouvons bien sûr recharger le tClientDataset en utilisant LoadFromFile.

ATTENTION: le format des fichiers utilisés par le tClientDataset est spécial. Vous ne pouvez pas charger n'importe quel .XML dans un tClientDataset.

Dans notre cas:
   sélectionnez dans la page "Data Control" de la Palette le composant FileListBox:

et:

  • posez-le sur la tForm
  • sélectionnez sa propriété Mask et placez-y la valeur *.XML
  • créez son événement OnClick et chargez les données dans ClientDataset1:

        procedure TForm1.FileListBox1Click(SenderTObject);
          var l_file_nameString;
          begin
            with FileListBox1 do
              l_file_name:= Items[ItemIndex];
            ClientDataSet1.LoadFromFile(l_file_name);
          end// FileListBox1Click

   compilez et exécutez
   l'application présente les fichiers .XML disponibles. Cliquez sur l'un d'entre eux: le ClientDataset charge les données


3.3 - Modifications de données

Pour nous placer dans le cas d'utilisation général d'un tClientDataset, nous allons modifier les données qu'il contient. Nous pouvons le faire en tapant dans le dbGrid, mais pour pouvoir répéter les séquences, nous allons utiliser du code:
   placez un tButton "delete_" sur la Forme et créez sa méthode OnClick. Placez-y les instructions d'effacement de la ligne 4 de ClientDataSet1:

    procedure TForm1.delete_5_Click(SenderTObject);
      begin
        with ClientDataset1 do
        begin
          First;
          NextNextNextNext;
          Delete;
        end// with ClientDataset1
      end// delete_4_Click

   placez un tButton "append_" sur la Forme et créez sa méthode OnClick. Placez-y les instructions d'ajout d'une ligne à ClientDataSet1:

    procedure TForm1.append_Click(SenderTObject);
      begin
        with ClientDataset1 do
        begin
          AppendRecord([112, 'display ClientDataset .XML''16/10/2004']);
        end// with ClientDataset1
      end// append_Click

   placez un tButton "modify_interbase_" sur la Forme et créez sa méthode OnClick. Placez-y les instructions qui vont modifier le mois de l'article "Interbase Tutorial":

    procedure TForm1.modify_interbase_Click(SenderTObject);
      var l_yearl_monthl_dayWord;
      begin
        with ClientDataset1 do
        begin
          if Locate('m_titre''Tutorial Interbase', [])
            then begin
                DecodeDate(FieldByName('m_date').AsDateTimel_yearl_monthl_day);
                Inc(l_month);
                if l_month= 13
                  then begin
                      l_month:= 1;
                      Inc(l_year);
                    end;

                Edit;
                FieldByName('m_date').AsDateTime:= EncodeDate(l_yearl_monthl_day);
                Post;
              end;
        end// with ClientDataset1
      end// modify_interbase_Click

   compilez et exécutez
Nous allons effectuer les trois types de modifications sur notre tClientDataset:
   créez (ou chargez) le tClientDataset
   cliquez "delete_5_"
   cliquez "append_"
   cliquez "modify_interbase_" 3 fois (pour changer 3 fois la même ligne)
   cliquez "save_"
   allez dans un Explorateur Windows et cliquez sur le fichier RESU.XML
   Internet Explorer affiche:




3.4 - Le contenu du fichier

En comparant les deux fichiers .XML, il est aisé de voir que:
  • le fichier comporte une partie méta-donnée et une partie lignes
  • les méta données comportent
    • la définition de chaque champ
    • le journal des modifications de chaque ligne sous forme de triplets contenant:
      • un numéro de ligne (débutant à 1 et, dans notre cas jusqu'à 15)
      • le numéro de la version précédente de cette ligne en cas de modification
      • un code de changement:
        • 4 pour un ajout
        • 8 pour une modification
  • les lignes sont codées ainsi:
    • le nom du champ et sa valeur
    • optionnellement, une valeur RowState

      Dans notre cas, comme toutes les lignes ont été créée, RowState est toujours présente. Mais si nous avions importé des données du Serveur Interbase, cette valeur RowState n'est pas présente pour les lignes importées (cf l'article Interbase dbExpress  )



Nous allons à présent construire un utilitaire qui relit ce fichier .XML pour nous présenter les lignes et leur histoire.




4 - Analyze du fichier .XML

4.1 - Principe

Nous allons procéder en deux temps:
  • une première unité va analyser le fichier .XML et afficher son contenu en reformatant les lignes
  • une seconde unité va contenir une classe qui contient les données de notre tClientDataset: la liste des lignes et pour chaque ligne, la liste des valeurs

4.2 - Analyse .XML

Le fichier est composé essentiellement de balises "< xxx >" et de retours chariot. Les balises sont de plusieurs type:
  • les balises "<xxx>" et "</xxx>"
  • les balises "<xxx yyyy zzz/>
Et dans chaque balise se trouve
  • la clé, comme METADATA FIELD ROW etc
  • éventuellement des paramètres qui sont au format "nom="valeur""
La classe c_cds_analyze_xml:
  • charge le fichier dans un tampon mémoire
  • analyse chaque élément du fichier en isolant les balises et les retours chariot
  • pour les balises:
    • si elles contiennent des paramètres, ceux-ci sont extraits
    • si la balise est une balise double, nous récursons pour retrouver les balises encapsulées
Voici quelques extraits
  • la fonction qui extrait les symboles:

    function f_get_symbol_recursiveString;

      // .. handle_opening_bracket and handle_return

      begin // f_get_symbol_recursive
        Result:= '';
        if m_buffer_index>= m_buffer_size
          then exit;

        repeat
          case m_pt_buffer[m_buffer_indexof
            '<' : handle_opening_bracket(l_exit);
            chr(13) : handle_return;
            else handle_other;
          end// case
        until (m_buffer_index>= m_buffer_sizeor (Result<> '');
      end// f_get_symbol_recursive

  • la procédure récursive qui analyse les balises:

    procedure handle_opening_bracket(var pv_exitBoolean);

      // -- ... here analyze_tag etc

      begin // handle_opening_bracket
        pv_exit:= False;

        // -- skip <
        inc(m_buffer_index);

        if m_pt_buffer[m_buffer_index]= '/'
          then begin
              l_slash:= '/';
              inc(m_buffer_index);
            end
          else l_slash:= '';

        l_start_index:= m_buffer_index;
        repeat
          inc(m_buffer_index);
        until (m_buffer_index>= m_buffer_size)
            or (m_pt_buffer[m_buffer_indexin k_tag_end);

        l_length:= m_buffer_indexl_start_index;
        if l_length> 0
          then begin
              l_tag:= f_extract_string_start_end(l_start_indexm_buffer_index- 1);
              l_tag:= l_slashUpperCase(l_tag);

              Result:= l_tag;

              // -- get command parameters
              if m_pt_buffer[m_buffer_index]<> '>'
                then begin
                    l_parameters:= '';

                    l_start_index:= m_buffer_index;
                    repeat
                      if m_pt_buffer[m_buffer_index]= '"'
                        then skip_apostroph
                        else inc(m_buffer_index);
                    until (m_buffer_index>= m_buffer_size)
                        or (m_pt_buffer[m_buffer_indexin ['>']);

                    l_parameters:= f_extract_string_start_end(l_start_indexm_buffer_index- 1);

                    if l_tag'PARAMS'
                      then analyze_change_log_params(l_parameters);
                  end// parameters

              // -- skip >
              if m_pt_buffer[m_buffer_index]= '>'
                then begin
                    inc(m_buffer_index);

                    if l_slash'/'
                      then Result:= l_tag
                      else analyze_tag(l_tagl_parameters);
                  end
                else display('*** could not find >');
            end
          else begin
              l_tag:= '';
              display('*** tag is void');
            end;
      end// handle_opening_bracket

  • lorsque la procédure précédente arrive au caractère ">" elle appelle la procédure qui examine le contenu d'une balise:

    procedure analyze_tag(p_tagp_parametersString);
      var l_row_stringl_log_list_stringString;
          l_row_indexInteger;
      begin
        add_line;

        if p_tag'ROW'
          then begin
              l_row_string:= f_spaces(l_indentation);
              l_row_string:= l_row_stringIntToStr(l_row_number+ 1);

              // -- find if a CHANGE_LOG entry has this number
              l_row_index:= l_c_merge_log_line_number_list.IndexOf(IntToStr(l_row_number+ 1));

              If l_row_index<> -1
                then l_log_list_string:= l_c_merge_log_list[l_row_index]
                else l_log_list_string:= '';
              l_row_string:= l_row_string' ('l_log_list_string') :';

              l_row_string:= l_row_string' <'p_tagp_parameters'>';
              add(l_row_string);

              // -- also add to the c_table structure
              if p_c_table<> Nil
                then p_c_table.f_c_add_line(l_row_numberl_log_list_stringp_parameters);

              Inc(l_row_number);
            end
          else add(f_spaces(l_indentation)+ '<'p_tagp_parameters'>');

        add_line;

        if (p_tag<> 'ROW'and (p_tag<> 'FIELD')
          then Inc(l_indentation, 2);

        add(f_spaces(l_indentation));

        // -- recurse
        Result:= f_get_symbol_recursive;

        if Result<> '/'p_tag
          then begin
              // -- backup
              pv_exit:= True;
              if (p_tag<> 'ROW'and (p_tag<> 'FIELD')
                then Dec(l_indentation, 2);
            end
          else begin
              add_line;
              Dec(l_indentation, 2);
              // -- ?? assume that the </xxx> has never any params
              add(f_spaces(l_indentation)+ '<'Result'>');
              add_line;

              add(f_spaces(l_indentation));

              // -- look for the next symbol or tag
              Result:= f_get_symbol_recursive;
            end;
      end// analyze_tag

    Les procédures add_line et add_text ajoute le texte dans une tStringList appelée m_c_result_list, que nous pouvons réafficher dans le programme appelant.

    De plus si nous recontrons la balise PARAMS CHANGE_LOG, nous décortiquons les triplets pour pouvoir les placer au début de leur ligne respective.



Incorporons donc ce traitement dans notre programme:
   placez un tButton "analyze_xml_" sur la Forme et créez sa méthode OnClick. Placez-y les instructions qui appelle l'analyse du fichier:

    procedure TForm1.analyze_xml_Click(SenderTObject);
      begin
        with c_cds_analyze_xml.create_cds_analyze_xml('xml''resu.xml'do
        begin
          analyze_xml;

          display_line;
          display_strings(m_c_result_list);

          Free;
        end// with c_analyze_xml
      end// analyze_xml_Click

   compilez et exécutez. Cliquez "analyze_xml_"
   le programme affiche:

Vous constatez que nous avons bien replacé les triplets en début de chacune des lignes.



4.3 - Stockage de la table dans une structure

Nous avons aussi créé une structure qui stocke les données de la table dans une structure de lignes / colonnes.

La structure est composée d'une tStringList de lignes, chaque ligne contenant une tStringList de colonnes, chaque cellule étant un objet (ne contenant dans la version actuelle que le texte contenu dans la cellule).

La définition des 3 classes est la suivante:

type
  c_columnClass(c_basic_object)
              // -- m_name: the content of the cell

              Constructor create_column(p_nameString);
              Destructor DestroyOverride;
            end// c_column

  c_tableClass// forward
  c_line// one "line"
          Class(c_basic_object)
            // -- m_name: the parameters of "<ROW ... >"

            m_c_parent_tablec_table;

            m_c_column_listtStringList;
            m_line_numberm_previous_line_numberm_update_codeInteger;

            Constructor create_line(p_nameString;
                p_c_parent_tablec_table;
                p_line_numberp_previous_line_numberp_update_codeInteger);

            function f_column_countInteger;
            function f_c_column(p_column_indexInteger): c_column;
            function f_index_of(p_column_nameString): Integer;
            function f_c_find_by_column(p_column_nameString): c_column;
            procedure add_column(p_column_nameStringp_c_elementc_column);
            function f_c_add_column(p_column_nameString): c_column;
            procedure display_column_list;

            Destructor DestroyOverride;
          end// c_line

  c_table// "line" list
           Class(c_basic_object)
             m_c_line_listtStringList;

             // -- the header of each column
             m_c_column_name_listtStringList;

             Constructor create_table(p_nameString);

             function f_line_countInteger;
             function f_c_line(p_line_indexInteger): c_line;
             function f_index_of(p_line_nameString): Integer;
             function f_c_find_by_line(p_line_nameString): c_line;
             procedure add_line(p_line_nameStringp_c_linec_line);
             function f_c_add_line(p_row_numberIntegerp_log_list_stringp_parametersString): c_line;

             procedure display_formatted_line_list;

             Destructor DestroyOverride;
           end// c_table

Il s'agit donc d'une encapsulation à 2 niveaux d'une tStringList (cf. le squelette d'une tStringlist ).

Nous utilisons cette structure de la façon suivante:

  • le programme principale crée un objet de type c_table
  • cet objet est passé comme paramètre à c_cds_analyze_xml.analyze_xml
  • chaque fois que c_cds_analyze_xml.analyze_tag a récupéré une balise, la procédure c_table.f_c_add_line est appelée en fournissant le numéro de la ligne, le triplet, les paramètres
  • c_table.f_c_add_line crée alors une nouvelle ligne, puis décompose les paramètres pour en extraire le titre de la colonne et la valeur de la colonne
  • c_table peut alors réaliser un affichage formaté:
    • les titres des colonnes sont affichés une fois uniquement
    • la taille maximale du texte de chaque colonne est calculé
    • pour chaque ligne, nous affichons
      • son numéro
      • les codes de modifications et RowState
      • la liste des valeurs


Par conséquent
   placez un tButton "display_table_" sur la Forme et créez sa méthode OnClick. Placez-y les instructions qui créent une c_table, appelle l'analyse du fichier, et affiche le résultat:

    procedure TForm1.display_table_Click(SenderTObject);
      var l_c_tablec_table;
      begin
        with c_cds_analyze_xml.create_cds_analyze_xml('xml''resu.xml'do
        begin
          l_c_table:= c_table.create_table('table');
          analyze_xml(l_c_table);

          display_line;
          l_c_table.display_formatted_line_list;
          l_c_table.Free;
          Free;
        end// with c_analyze_xml
      end// display_table_Click

   compilez et exécutez. Cliquez "display_table_"
   le programme affiche:




Le dernier affichage montre que:
  • la ligne 5 comporte bien une signature RowState d'effacement
  • rien ne distingue l'ajout de la ligne 12 des 11 lignes précédentes chargées depuis le fichier .XML
  • les lignes modifiées sont chaînées:
    • la ligne 13 désigne la ligne 6
    • la ligne 14 désigne la ligne 13
    • la ligne 15 désigne la ligne 14
    De plus les valeurs de RowState sont différentes pour la ligne originale (6), les lignes intermédiaires (13 et 14) et la dernière valeur connue (15)

5 - Améliorations

L'analyze du text .XML date de nos premières explorations de dbExpress / Midas sous Linux. Cela se voit: la récursion semble mal emboîtée. Si je devais réécrire le programme, je partirais d'une grammaire .XML correspondant aux fichiers Borland, et je laisserais le Con-Compilateur mettre en place l'analyseur syntaxique.

Les classes de stockage sont aussi un peu trop grosses. Actuellement nous pourrions nous passer de la classe c_column. De même, nous avons utilisé des tStringList, alors que des tList auraient suffi. Encore que nous pourrions utiliser les chaînes pour trier les lignes par exemple (chaque enregistrement et ses versions modifiées pourraient être regroupés).

En attendant le programme marche, et il avait surtout été sorti de la naphtaline pour afficher le contenu d'un tClientDataset pour l'article Interbase dbExpress.

Le module qui analysait directement le contenu mémoire d'un tClientDataset et qui nous servait aussi à surveiller les paquets échangés entre un tDatasetProvider et un tClientDataset n'a pas été publié pour le moment.




6 - Télécharger le source

Nous avons placé le projet dans un .ZIP qui comprend:
  • le .DPR, la forme principale, les formes annexes éventuelles
  • les fichiers de paramètres (le schéma et le batch de création)
  • toutes les librairies (Unit) nécessaires (le .ZIP est autonaume)
Ce .ZIP contient des chemins RELATIFS. Par conséquent:
  • créez un répertoire n'importe où sur votre machine
  • placez le .ZIP dans ce répertoire
  • dézippez et les sous-répertoires nécessaires seront créés
  • compilez et exécutez
Au niveau sécurité:
  • le .ZIP:
    • ne modifie pas votre PC (pas de changement de la Base de Registre, d'écrasement de DLL ou autre .BAT). Aucune modification de répertoire ou de contenu de répertoire ailleurs que dans celui où vous dézippez
    • ne contient aucun programme qui s'exécuterait à la décompression (.EXE, .BAT, .SCR ou autre .VXD) ou qui seraient lancés plus tard (reboot)
    • passez-le à l'antivirus avant décompression si vous êtes inquiets.
  • le programme ne change pas la base de registre et ne modifie aucun autre répertoire de votre machine
  • pour supprimer le projet, effacez simplement le répertoire.
Voici le .ZIP:

Comme d'habitude:
  • nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
  • tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
  • plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
    Nom :
    E-mail :
    Commentaires * :
     

  • et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.



7 - L'auteur

John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Created: jan-04. Last updated: mar-2020 - 250 articles, 620 .ZIP sources, 3303 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2020
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
      + programmation_oracle
      + interbase
      + sql_server
      + firebird
      + mysql
      + xml
      – paradox_via_ado
      – mastapp
      – delphi_business_objects
      – clientdataset_xml
      – data_extractor
      – rave_report_tutorial
      – visual_livebindings
      – migration_bde
    + web_internet_sockets
    + services_web_
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
    + admin
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Architecte Delphi Architecture, Refactoring, choix de technologies, revue de code, mise en place de test, optimisation - Tél 01.42.83.69.36
Formation Initiation Delphi L'outil de développpement, le langage de programmation, les composants - 3 jours