u_c_tstringlist - John COLIBRI. |
- mots clé:template - modèle - encapsulation de tStringList
- logiciel utilisé: Windows 98, Delphi 5.0
- matériel utilisé: Pentium 500Mhz, 128 M de mémoire
- champ d'application: Delphi 1 à 6 sur Windows, Kylix
- niveau: débutant en Pascal et Delphi
- uses: u_c_basic_object, u_c_display, (u_c_log,
u_loaded, u_strings, u_types_constants, u_c_basic_file,
u_c_file_name, u_dir)
- plan:
1 - Introduction Ce texte explique comment utiliser la classe-type c_stringlist. Cette classe encapsule la tStringList de base. Cette liste permet de gérer des
liste de chaînes de caractères et, accessoirement un pointeur. Traditionnellement le programmeur utilise le pointeur pour désigner: - soit une donnée dont la taille ne dépasse pas 4 octets, en surtypant le pointeur
- soit un RECORD ou autre type Pascal alloué par NEW ou GETMEM et en surtypant le pointeur
- soit une classe Delphi, en surtypant toujours.
Le programme utilisateur doit donc en permanence surtyper, ce qui est gênant au niveau de la maintenance: la personne qui a créé le programme connaît les précautions à prendre du fait du surtypage, le malheureux qui récupérera le
projet six mois plus tard risque de s'arracher les cheuveux. Traditionnellement la tStringList est alors encapsulée dans une CLASS, qui manipule et pointeur. Nous bénéficions ainsi des avantages de l'encapsulation:
- le programmeur créant la classe isole les surtypages dans une unité
- l'utilisateur n'a pas besoin d'en avoir connaissance: son programme manipule les données pointées sans savoir qu'il s'agit d'un surtypage
- si un problème intervient, tout est proprement traité dans une unité, et non pas réparti dans un programme gigantestque dont ce n'est pas le souci principal
Comme nous utilisons fréquemment ces tStringList, il s'avère que les trois ou quatre procédures de base sont toujours les mêmes: créer l'encapsulateur, ajouter un élément avec sa chaîne et les données annexes, lister le contenu etc.
Nous avons donc crée une unité standard contenant l'ébauche de ce type d'encapsulation. Ne sachant si la classe finale contiendra des noms de répertoires (c_path_list), des noms de fichiers .ZIP (c_zip_list), nous avons
utilisé l'abréviatioon xxx que nous remplaçons soit par path, soit par zip. Nous avons désigné ce type de classe Delphi par le terme de "squelette". Jadis
nous utilisions "shell", mais ce terme a une signification toute différente sous Windows. Et Template est aussi déjà utilisé ailleurs. Si vous avez un
meilleur nom à proposer, je suis toute ouie à jcolibri@jcolibri.com. 2 - Utilisation 2.1 - Un exemple simple
Supposons que nous souhaitions manipuler une liste de deux chaînes. Par exemple le répertoire et le nom de fichier (pour savoir dans quel répertoire se trouvent nos fichiers). Pour utiliser le squelette u_c_tstringlist:
- chargez l'unité u_c_xxx_tstringlist dans Delphi
- sauvegardez dans votre répertoire sous u_c_file_and_path_list
- renommez partout "xxx" en "file"
- renommez partout "yyy" en "path"
- ajoutez u_c_file_list à votre projet
- compilez
Comme notre squelette prévois que chaque élément de la liste comportera une String, tout fonctionne déjà: - ajoutez à votre projet un tButton
- créez son événement OnClick et ajoutez quelques couples (chemin / nom):
with c_file_list.create_file_list('test_u_c_tstringlist') do
begin
add_file('pascal_to_html.pas', c_file.create_file('pascal_to_html.pas', Nil, 'c:\programs\'));
add_file('site_editor.pas', c_file.create_file('site_editor.pas', Nil, 'c:\programs\'));
display_file_list; Free;
end; // with c_file_list | Et voici le résultat:
test_u_c_tstringlist 2 pascal_to_html.pas c:\programs\ site_editor.pas c:\programs\ |
2.2 - Un exemple avec calcul de taille Un tout peu plus compliqué: la liste des fichiers par répertoire, en affichant une seule fois le nom des répertoires et en alignant les noms de fichiers. Voici ce que nous entrons:
c:\programs\ et pascal_to_html.pas c:\programs\ et site_editor.pas c:\utilities\ et http_raw_logs.pas c:\utilities\ et uses_list.pas |
Voici ce que nous souhaitons: c:\programs\ | pascal_to_html.pas | |
site_editor.pas | c:\utilities\ | http_raw_logs.pas | | uses_list.pas | | Pour cela il faut:
- chargez l'unité u_c_tstringlist dans Delphi
- sauvegardez dans votre répertoire sous u_c_path_list
- renommez partout "xxx" en "path"
- renommez partout "yyy" en "file"
- ajoutez u_c_path_list à votre projet
- compilez
Il faut à présent personnaliser l'affichage: - pour calculer la taille max des noms de répertoires,
- ajoutez Math à la liste des USES de l'IMPLEMENTATION de u_c_file_name_list
- ajoutez le calcul de la chaîne de taille maximale dans le CONSTRUCTOR de chaque cellule:
Constructor c_path.create_path(p_name: String; p_c_path_list: c_path_list;
p_file: String); begin
Inherited create_basic_object(p_name);
m_c_parent_path_list:= p_c_path_list;
m_file:= p_file;
with m_c_parent_path_list do
m_path_length_max:= Max(m_path_length_max, Length(p_name));
end; // create_path | - pour l'affichage formaté:
- ajoutez la méthode display_the_path_and_files dans la CLASS c_file_list
- voici le corps de cette méthode:
procedure c_path_list.display_the_path_and_files;
var l_path_and_file: Integer;
l_path, l_previous_path: String;
begin
display(m_name+ ' '+ IntToStr(f_path_count));
l_previous_path:= '';
for l_path_and_file:= 0 to f_path_count- 1 do
with f_c_path(l_path_and_file) do
begin
l_path:= m_name;
if l_path= l_previous_path
then l_path:= '';
display(Format('%-'+ IntToStr(m_path_length_max)+ 's ', [l_path])
+ m_file);
l_previous_path:= m_name;
end; // for, with
end; // display_the_path_and_files | 2.3 - La classe c_xxx
La définition de la cellule de base c_xxx est:
type c_xxx_list= Class; // forward
c_xxx= // one "xxx"
Class(c_basic_object)
public
// -- not the same name as the tStringList
m_c_parent_xxx_list: c_xxx_list;
m_yyy: String;
Constructor create_xxx(p_name: String; p_c_xxx_list: c_xxx_list;
p_yyy: String); Virtual;
function f_display_xxx: String;
Destructor Destroy; Override;
end; | Pour les données:
- m_yyy: un exemple de donnée stockée dans chaque cellule (en plus de m_name qui appartient à c_basic_object)
- m_c_parent_xxx_list: un lien de chaque cellule vers la structure à laquelle
elle appartient. Les méthodes de chaque cellules peuvent au besoin accéder à des champs propres à la structure (dans l'exemple ci-dessus, la taille maximale des chemins)
Notez que la classe c_xxx_list a été déclarée par anticipation pour pouvoir définir l'attribut m_c_parent_xxx_list. Et pour les méthodes: - create_xxx assure la création (le CONSTRUCTOR sera en général modifié
pour ajouter les attributs propres à la cellule utilisée, comme vu plus haut)
- f_display_xxx retourne simplement une String pour permettre les affichages de mise au point
- le DESTRUCTOR n'a rien à faire puisqu'aucune données n'est allouée dans cette cellule élémentaire
2.4 - La classe c_xxx_list L'interface est la suivante:
c_xxx_list= // "xxx" list
Class(c_basic_object)
public
m_c_xxx_list: tStringList;
Constructor create_xxx_list(p_name: String); Virtual;
function f_xxx_count: Integer;
function f_c_xxx(p_xxx: Integer): c_xxx;
function f_index_of(p_xxx: String): Integer;
function f_c_find_by_xxx(p_xxx: String): c_xxx;
procedure add_xxx(p_xxx: String; p_c_xxx: c_xxx);
procedure add_element(p_xxx, p_yyy: String);
procedure add_unique_xxx(p_xxx, p_yyy: String);
procedure display_xxx_list;
Destructor Destroy; Override;
end; | Pour les données:
- m_c_xxx_list est la tStringList. Cet attribut pourrait d'ailleurs être en PRIVATE.
Et pour les méthodes: - create_xxx_list alloue la tStringList
- f_xxx_count: retourne le nombre d'éléments. Il serait possible d'utiliser m_c_xxx_list.Count directement, mais la création d'une fonction encapsule mieux notre tStringList: le programme utilisateur devient indépendant de
notre choix de mise en oeuvre de liste
- f_c_xxx: retourne une cellule élémentaire lorsque l'utilisateur fournit l'indice
- f_index_of encapsule m_c_xxx_list.IndexOf
- f_c_find_by_xxx: retourne la cellule lorsque nous fournissons le nom
- add_xxx: ajoute une cellule créée par l'utilisateur
- add_element: ajoute une cellule fournissant le nom et les différents champs
à placer dans la cellule (ici un seul)
- add_unique_xxx: ajoute une cellule en vérifiant que le nom est unique
- display_xxx_list: affiche tous les éléments
- Destroy: libère la liste
2.5 - Répertoires et Directives de Compilation L'unité est prévue pour être placée dans: C: programs colibri_skelettons
colibri_helper_skelettons classes Vous pouvez naturellement changer cette organisation par Projet | Options | Directories
Les directives de compilation sont: - R+ (vérification des intervalles)
- S+ (vérification de la pile)
- pas d'optimisation
3 - Programmation
La classe c_xxx_list applique simplement le principe d'encapsulation: - f_xxx_count renvoie tStringList.Count:
function c_xxx_list.f_xxx_count: Integer;
begin
Result:= m_c_xxx_list.Count;
end; // f_xxx_count | - et f_c_xxx renvoie le pointeur surtypé (c'est LA le réel bénéfice de toute
cette unité):
function c_xxx_list.f_c_xxx(p_xxx_index: Integer): c_xxx;
begin
Result:= c_xxx(m_c_xxx_list.Objects[p_xxx_index]);
end; // f_c_xxx | Notons que:
- nous pourrions effectuer des vérifications d'intervalle dans f_c_xxx, en retournant NIL en cas d'erreur.
- lorsque chaque cellule doit contenir une chaîne nous pouvons:
- soit utiliser m_name, qui appartient à c_basic_object
- soit conserver m_name pour la mise au point, et ajouter un champ ayant le nom correct par rapport à l'application.
Le fait que chaque cellule contienne une chaîne avec le nom de la classe (par exemple 'url') n'est pas gênant car Delphi utilise un cache de chaînes. 3.1 - Voire aussi Pour d'autres exemples de squelettes, voyez aussi: 4 - Améliorations
Si les éléments de chaque cellule contiennent des données sans traitement majeur à effectuer sur ces données, il peut être plus simple d'utiliser: - soit une valeur surtypée du pointeur associé à chaque chaîne
- soit un pointeur vers un RECORD
Ces deux simplifications nous font économiser l'écriture du CONSTRUCTOR et du DESTRUCTOR. Et bénéficions d'une réduction de consommation mémoire (les
octets des tObject, et en plus le champ m_name que nous avons ajouté à tous nos objets). 5 - Télécharger le source Vous pouvez télécharger:
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" :
- 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. |