mardi 31 décembre 2013

systemd: création répertoire dans /run

Nous voulons que notre serveur postgresql utilise un socket situé /run/postgresql.
Mais ce n'est pas ce qui est prévu au niveau de la distribution: /run/postgresql n'existe pas et donc notre serveur se plante.
Créer /run/postgresql par mkdir est d'une utilité toute relative car /run se trouve en mémoire vive et le répertoire aura disparu au prochain démarrage.
Il faudrait donc créer ce répertoire au démarrage.
Voici comment procéder si la distribution utilise systemd.
Dans /usr/lib/tmpfiles.d/, créer un fichier postgresql.conf contenant:

d /run/postgresql 2755 postgres postgres -

Par exemple comme ceci:



(Il reste à appuyer sur CTRL-D pour procéder)

Il faut ensuite s'assurer que le service postgresql sera bien activé au prochain démarrage:

[root@rigel ~]# systemctl enable postgresql.service

Bien sûr: ce est écrit ici peut être utilisé dans d'autre cas.
Il est également possible de créer des fichiers.

Pour plus de détails: man tmpfiles.d 

dimanche 29 décembre 2013

Postgresql upgrade

Lors du passage de Fedora 19 à Fedora 20, postgresql passe de la version 9.2 à la version 9.3
Aussi nous devons procéder manuellement à la migration des données postgresql.
Avant toute chose vérifions que la variable d'environnement PGDATA existe et qu'elle contient ce qui convient:

[toto@rigel ~]$ echo $PGDATA
/var/lib/pgsql/data

Ceci sera le cas si nous avons placé dans /etc/profile.d  un fichier pgsql.sh comme expliqué ici.

Installons postgresql-upgrade (si celui-ci ne l'est pas):

[toto@rigel ~]$ su -
Mot de passe :
[root@rigel ~]# yum install postgresql-upgrade

Nous serons amené à agir en tant qu'utilisateur postgres. Le plus simple pour ce faire est de procéder dans une autre fenêtre.


[toto@rigel ~]$ su -
Mot de passe :
[root@rigel ~]# su - postgres
-bash-4.2$ mv /var/lib/pgsql/data/ /var/lib/pgsql/data_9.2/

Nous avons renommé les anciennes données.
Ensuite (en tant que root):

[root@rigel ~]# postgresql-setup initdb

Puis dans la fenêtre où l'on est postgres:

-bash-4.2$ cp /var/lib/pgsql/data_9.2/pg_hba.conf /var/lib/pgsql/data/
-bash-4.2$ pg_upgrade -b /usr/lib64/pgsql/postgresql-9.2/bin/ -B /usr/bin/ -d data_9.2/ -D data

Tout semble se dérouler à merveille.
Et soudain l'erreur fatale:

lc_collate cluster values do not match:  old "fr_FR.UTF-8", new "fr_FR.utf8"

Il faut recommencer après avoir remplacé utf8 par UTF-8 dans /etc/locale.conf

[root@rigel ~]# sed -i 's/utf8/UTF-8/' /etc/locale.conf
[root@rigel ~]# rm -rf /var/lib/pgsql/data
[root@rigel ~]# postgresql-setup initdb

Cette fois, l'étape suivant ne pose aucun problème.
Ne pas oublier de recopier pg_ident.conf (si celui-ci est utilisé):

-bash-4.2$ cp /var/lib/pgsql/data_9.2/pg_ident.conf /var/lib/pgsql/data/

et aussi d'adapter éventuellement le fichier postgresql.conf suivant par exemple ce qui est expliqué ici.
Reste à démarrer le service:

[root@rigel]# systemctl start postgresql.service

mardi 10 décembre 2013

Le piège de la mise à jour cachée

Pignouf Anatole (matricule 1007) a obtenu au sein de l'entreprise une belle promotion: il passe du service "Relations publiques" au service "Recherche" avec à la clef une augmentation de salaire dont le calcul s'effectuera dorénavant sur base de l'échelle 11r.
Il s'occupera du projet "Lave-linge à pédales", projet porteur puisque synonyme d'économie d'énergie.
Reste à mettre à jour les données informatique via un formulaire libreoffice connecté à la base de données PostgreSQL utilisée par l'entreprise:


Dans la table qui sera mise à jour, "Service" et "Projet" apparaissent sous forme de code, mais l'utilisation de listes déroulantes fait que ces codes sont transparents pour l'utilisateur.
Ce qui est affiché à l'écran sous forme d'un seul formulaire est en fait composé d'un formulaire maître "Sélection" et de deux sous-formulaires: "Employés" et "Gestion".


La table "sélection" source de données de "Sélection" contient un matricule qui est sans arrêt mis à jour et qui sert à appeler le dossier désiré. La façon dont tout cela fonctionne est expliqué ici.
Après avoir procédé, la personne chargée d'actualiser les données informatiques en ce qui concerne "Service" et "Projet", clique sur la petite disquette (qui a pris des couleurs) de la barre d'outils:


  ce qui donne:


Il constate alors que depuis un autre poste de travail les données "Ancienneté" et "Échelle de traitement" ont été modifiées (ce qui montre d'ailleurs que chaque enregistrement s'accompagne d'une relecture des données).
Parfait: de toute façon ce n'était pas son job de le faire!
Supposons maintenant que au lieu de cliquer sur l'icône "disquette", l'encodeur appuie sur "Enter" autant de fois qu'il le faut (3 fois) pour amener le focus sur la zone de saisie "matricule". Dès l'instant où le focus quitte le sous-formulaire "Gestion", les données s'enregistrent et nous arrivons à ceci:


L'ancienneté affichée est bien 9, par contre en ce qui concerne l'échelle de traitement on a 12.
Et voilà, le piège s'est refermé! Le piège de la mise à jour cachée!
L'échelle de traitement a été dans un premier temps portée à la valeur '11r' depuis un autre poste, mais ensuite  elle est revenue à la valeur '12'.
Mais pour notre encodeur l'échelle de traitement a toujours été à '12' et il n'a touché à rien. il s'agit donc bien d'un piège et d'une mise à jour cachée.
D'autre part, la personne qui a porté l'échelle de traitement à la valeur '11r' croit avoir fait son travail et elle n'y reviendra plus.
Pour éviter le piège, on peut choisir "Tabulation = non" au niveau des propriétés du contrôle:


ce qui n'empêchera pas que l'on puisse mettre à jour la donnée, à moins de choisir "En lecture seule = OUI".

NB: le piège a été testé avec une grandeur de type character ce qui est le cas de l'échelle de traitement.

samedi 7 décembre 2013

Transfert de colonne

La table "employés" telle que considérée jusqu'à présent (par exemple ici) présente une anomalie, une atteinte à la normalité: la colonne "service" (qui donne sous forme d'un code le service auquel appartient l'employé) est la seule qui contienne des données se rapportant à l'entreprise.

Transfert vers une nouvelle table

On peut envisager de créer une nouvelle table destinée à contenir les données qui sont à la fois relatives à l'employé et à l'entreprise :

CREATE TABLE gestion (
    matricule INTEGER PRIMARY KEY REFERENCES employés,
    service CHAR(2) REFERENCES services,
    projet CHAR(4) REFERENCES projets,
    barème CHAR(4),
    ancienneté SMALLINT
);

Étant donné les relations  d'intégrité référentielle qui figurent dans l'instruction ci-dessus, la table "projets" destinée à contenir en clair le nom des projets doit bien sûr avoir été préalablement créée:

CREATE TABLE projets (
    projet_id CHAR(4) PRIMARY KEY,
    dénomination CHAR VARYING(25)
);

Avant de supprimer la colonne "service" de la table employés, il faut d'abord en transférer les données vers la nouvelle table gestion.
Nous pouvons procéder avec cette instruction SQL:

insert into gestion (matricule, service)
 select matricule, service
 from employés
 order by matricule
 ;

ou dans un terminal psql (nous utilisons PostgreSQL) avec la méta-commande \copy:

\copy employés (matricule, service) to matr-serv
\copy gestion (matricule, service) from matr-serv

Se connectant  en tant que super-utilisateur postgresql, nous pouvons utiliser la commande COPY:


Notons que le fichier de destination sera créé sur le serveur (même si sur la machine client existe un /var/lib/postgresql).
Ce fichier aura comme propriétaire postgres: il faut donc indiquer un chemin conduisant là où postgres a le droit d'écrire. 
Si le volume de données à traiter est très important, cette solution est à privilégier puisqu'elle évite qu'un nombre de données important ne transite par la connexion client/serveur. 

Transfert vers une table existante

Nous avons transféré le contenu de la colonne "service"  depuis la table "employés" vers une table nouvellement créée.
Nous pouvons aussi considérer que la table cible (gestion) existe déjà.
Ajoutons-y la colonne service:

ALTER TABLE gestion
ADD COLUMN service CHAR(2) REFERENCES services
;

Les relations d'intégrité référentielle existantes impliquent que tous les matricules de la table "gestion" correspondent à des matricules de la table "employés", mais l'inverse n'est pas nécessairement vrai.
Il convient tout d'abord d'insérer dans "gestion" les matricules qui n'y existent pas:

insert into gestion (matricule, service)
 select matricule, service
 from employés
 where not exists
 (select *
  from gestion
  where gestion.matricule = employés.matricule)
 order by matricule
 ;

Les rangées nouvellement insérées dans la table gestion possèdent la bonne valeur de "service" (celle de la table employés). Pour toutes les autres rangées, "service" a la valeur NULL.
Il faut donc effectuer une mise à jour, avec par exemple l'instruction SQL:

UPDATE gestion 
SET service = (SELECT service
FROM employés
WHERE employés.matricule = gestion.matricule)
 ;

Mais alors toutes les rangées (dont les nouvelles) seraient concernées par la mise à jour.
Aussi nous préférons agir plus finement avec cette instruction:

UPDATE gestion
SET service = employés.service
FROM employés
WHERE employés.matricule = gestion.matricule
AND   employés.service <> gestion.service

;

(Remarque: l'utilisation de la clause FROM dans une instruction UPDATE n'est pas autorisée dans le langage SQL standard)

Procédons (après avoir mis l'instruction SQL dans le fichier update-gestion.sql):


Une seule rangée mise à jour! Il y a un problème: nous sommes tombés dans le piège du null.
La seule mise à jour effectuée est due certainement à ce que "service" a changé dans la table "employés" pour une des rangées insérées.
Recommençons après avoir modifié l'instruction pour éviter le piège:



Tout est maintenant en ordre: il reste à supprimer la colonne "service" de la table "employés":

ALTER TABLE employés
DROP service
;

Adaptation des formulaires libreoffice

Évidemment dès cet instant le nom du service ne s'affiche plus dans les formulaires que nous avons considérés  ici et :


C'est normal puisque la zone de liste (listbox) qui fournit en clair le nom du service dépend du sous-formulaire "Employés" dont la source de données est la table "employés":


Nous allons régler le problème en cliquant droit sur l'icône du formulaire "Employés", en vue d'y rattacher un nouveau formulaire



que nous appellerons "Gestion"


La source de données en sera la table "gestion" et le lien avec "Employés" sera basé sur "matricule"


Il reste à effectuer un glisser-déposer des icônes qui conviennent vers le formulaire "Gestion":


Et voilà:


samedi 23 novembre 2013

Connexion IPv6 à un serveur PostgreSQL

A la fin de ce billet (maintenant en partie obsolète), nous avons expliqué comment réaliser une connexion IPv4 entre un serveur PostgreSQL et une machine client située sur un réseau local.
Nous allons cette fois expliquer comment procéder pour une connexion IPv6.
Tout d'abord il s'agit d'adapter le fichier pg_hba.conf (situé sur le serveur).
Voici un extrait d'un tel fichier modifié en conséquence:

# TYPE  DATABASE        USER            ADDRESS                 METHOD
# "local" is for Unix domain socket connections only
local   all             all                                     peer  map=map01
# IPv4 local connections:
host    all             all             127.0.0.1/32            ident map=map01
# IPv4 lan connections
host    all             all             192.168.1.0/24          md5
# IPv6 local connections:
host    all             all             ::1/128                 md5
# IPv6  lan connections
host    all             all             fe80::/10               md5

(en bleu ce qui concerne les connexions réseau IPv4, en rouge les connexions IPv6)

Évidemment avec un tel fichier pg_hba.conf, pg_ident.conf doit contenir ce qui convient, par exemple:

# MAPNAME       SYSTEM-USERNAME         PG-USERNAME
map01           toto                    toto
map01           toto                    titine

pour que toto puisse se connecter lui-même ou en tant que titine.

Mais ceci nous éloigne du sujet, d'autant plus que les connexions qui nous intéressent ne sont pas concernées.

Revenons aux connexions réseau: les modifications à effectuer sur le fichier postgresql.conf sont les mêmes que celles présentées auparavant:

# - Connection Settings -
listen_addresses = '*' # what IP address(es) to listen on;
# comma-separated list of addresses;
# defaults to 'localhost', '*' = all
# (change requires restart)
port = 5432 # (change requires restart)

Si nous toutefois nous voulons accepter exclusivement les connexions IPv6, il suffit par exemple de remplacer

listen_addresses = '*'
par:
listen_addresses = '::'

Si un pare-feu est actif, il reste à y effectuer quelques petits aménagements.
Nous pouvons procéder avec gufw via l'onglet avancé du panneau 'Ajouter des règles":



Pour IPv4, nous ajoutons cette règle:


et pour IPv6 celle-ci:


Nous aurions pu agir en ligne de commande avec les commandes:

# ufw allow in from 192.168.1.0/24 to any port postgresql

# ufw allow in from fe80::/10 to any port postgresql

# ufw reload

Si le serveur est un serveur Fedora (utilisant firewalld), les commandes équivalentes sont:


firewall-cmd --permanent --add-rich-rule 'rule family="ipv4" source address="192.168.1.0/24" service name="postgres" accept'

# firewall-cmd --permanent --add-rich-rule 'rule family="ipv6" source address="fe80::/10" service name="postgres" accept'

# firewall-cmd --reload

En IPv4, nous avons configuré la connexion réseau de notre serveur pour que celui-ci dispose d'une adresse IP fixe (située dans une plage qui n'est pas accessible au serveur DHCP):


Nous pouvons connecter un terminal psql avec la commande
psql bd01 -h 192.168.1.2
(connexion de l'utilisateur courant à la base de données bd01).
Si dans le fichier /etc/hosts du poste client nous avons ajouté l'entrée:
192.168.1.2   rigel
(rigel étant le nom d'hôte de la machine serveur)
on peut alors utiliser la commande
psql bd01 -h rigel

(Rappelons que le mot de passe demandé est un mot de passe PostgreSQL et non un mot de passe système)

Pour se connecter en IPv6, il faut utiliser une autre adresse.
Cherchant des infos sur la connexion, nous obtenons ceci:

Mais l'adresse IPv6 qui apparaît ici n'est pas celle à utiliser: elle est destinée aux communications vers l'extérieur et de plus elle est temporaire.
La ligne de commande nous donne plus d'infos:

toto@rigel:~$ ifconfig
eth0      Link encap:Ethernet  HWaddr f4:6d:04:b6:41:4c 
          inet adr:192.168.1.2  Bcast:192.168.1.255  Masque:255.255.255.0
          adr inet6: 2a02:a03f:2082:2500:785e:2571:1adf:b2b6/64 Scope:Global
          adr inet6: 2a02:a03f:2082:2500:f66d:4ff:feb6:414c/64 Scope:Global
          adr inet6: fe80::f66d:4ff:feb6:414c/64 Scope:Lien

                    .................................

Nous découvrons l'existence d'une deuxième adresse IPv6 (adr inet6) destinée aux communications vers l'extérieur mais dont l'utilisation n'est pas privilégiée car elle est construite sur base de l'adresse matérielle.
La 3ième adresse IPv6 (également construite sur base de l'adresse matérielle) est celle qui nous intéresse.
Cependant la commande
 psql bd01 -h fe80::f66d:4ff:feb6:414c
produit le message d'erreur "psql: could not connect to server: Argument invalide":


L'astuce est qu'il faut faire suivre l'adresse de % puis de l'interface à utiliser (par exemple wlan0)pour atteindre la cible:


 Il faut procéder de même pour établir une connexion avec libreoffice:



Nous aimerions maintenant utiliser le nom d'hôte comme en IPv4.
Pour ce faire ajoutons dans le fichier /etc/hosts  du poste client la ligne 

fe80::f66d:4ff:feb6:414c    rigel6

Procédons:

 
Le message reçu est le même que pour la commande

 psql bd01 -h fe80::f66d:4ff:feb6:414c

Mais cette fois ajouter %wlan0 ne nous est d'aucun secours. On reçoit le message:

psql: could not translate host name "rigel6%wlan0" to address: Nom ou service inconnu

Nous pouvons contourner le problème en plaçant dans /usr/local/bin un script (que nous appelons ah et rendu exécutable avec la commande chmod +x) contenant:

#!/bin/sh

awk -v "var=$1" '$2 == var {print $1 "%wlan0"}' /etc/hosts

$1, paramètre transmis au script (rigel6) est sauvegardé dans var.
Ensuite $1 et $2 contiennent les champs de chaque ligne lue par awk.
L'action (print) est exécutée lorsque $2 vaut rigel6.

Vérifions que ça fonctionne:


Bingo!
Cependant ce moyen est inopérant sur l'écran de connexion libreoffice.
Il faut donc saisir pour se connecter à bd01

dbname:bd01 host=fe80::f66d:4ff:feb6:414c%wlan0

mais cela ne doit être effectué qu'une seule fois: lors de la création du fichier odb
 

dimanche 27 octobre 2013

Le piège du chemin absolu

Dans la série 'Le piège', voici 'Le piège du chemin absolu' (le premier épisode  de la série se trouve ici).
Au départ nous avons installé une distribution (disons Manjaro) avec une partition séparée pour les dossiers personnels (montée en /home) et un utilisateur appelé toto.
Ensuite, à côté de cette distribution, nous en avons installé une autre (disons Xubuntu) utilisant la même partition séparée pour les données. Nous avons appelé l'utilisateur totox et non pas toto afin d'avoir un autre dossier personnel, ceci pour éviter les conflits entre fichiers de configuration de versions différentes. Cependant nous voulons dans Xubuntu travailler avec les mêmes données que dans Manjaro. Comme totox dans Xubuntu a le même id (1000) que toto dans Manjaro, aucun problème de droits ne pourra s'y opposer. Donc travaillant dans Xubuntu où nous sommes totox, nous supprimons le répertoire Documents de notre dossier personnel (/home/totox) et effectuons un glisser-déposer du répertoire Documents de /home/toto vers /home/totox


et dans le menu contextuel qui surgit, nous sélectionnons 'Lier ici':


Voilà c'est fait:


Lorsque nous cliquons sur le lien 'Documents', nous accédons directement aux documents de toto:



Comme nous sommes insatiable, nous installons encore une autre distribution (par exemple Fedora) avec cette fois-ci une partition pour les données qui n'est pas celle utilisée pour Manjaro et Xubuntu.
Rien ne nous empêche donc d'appeler notre utilisateur toto.
Et finalement nous installons Kubuntu: utilisateur totok et même partition montée dans /home que Fedora.
Tout est en place pour que le piège fonctionne!
Dans Kubuntu, nous voulons accéder au dossier personnel de totox (utilisateur de Xubuntu).
Nous devons pour ce faire monter la partition contenant ce dossier. Afin de la retrouver plus facilement, nous avons donné à cette partition (commande e2label) le label home-b.
Pour la monter il suffit de cliquer sur ce qui convient dans la barre latérale du navigateur de fichiers:


La partition est alors montée dans /media/home-b:


nous cliquons sur 'Documents' et nous tombons dans le piège! Les données ne sont pas celles attendues:


Essayons de comprendre ce qu'il en est en ouvrant un (émulateur de) terminal au niveau du dossier 'totox' et en y lançant la commande 'ls -l':


Nous constatons que le chemin contenu dans le lien 'Documents' est un chemin absolu: il part toujours de la racine du système quel que soit l'endroit où se trouve le lien. En fait les données que nous avons affichées sont les données du toto de Fedora (et non celles du toto de Manjaro).
il faudrait remplacer ce chemin absolu par un chemin relatif. Pour cela il suffit d'exécuter la commande

ln -sf ../toto/Documents .

(Le point final fait partie de la commande: il symbolise le répertoire courant)


Le chemin part maintenant du répertoire parent du dossier totox (donc de /media/home-b) et il aboutit au bon endroit:




Si nous sommes dans Xubuntu, le répertoire parent, départ du chemin, est maintenant /home et de nouveau nous atterrissons là où nous voulons aller.

samedi 6 juillet 2013

Formulaires: rafraîchir sans bouton

Considérons dans libreoffice base un formulaire donnant accès aux données de la table "employés" d'une base de données PostgreSQL à laquelle libreoffice est connecté.
Nous avons détaillé dans ce billet comment constituer un tel formulaire.
Choisissons d'éditer ce formulaire et ouvrons le navigateur de formulaire.
Nous obtenons ceci:


La table "employés" est la source de données du sous-formulaire "Employés".
Cliquer droit dans le navigateur de formulaires sur l'icône "Sélection" :


nous permet d'afficher le panneau des propriétés du formulaire principal "Sélection". Dans notre cas, la source de données de ce formulaire principal "Sélection" est une instruction SQL


Cette instruction fait référence à une table "sélection" dont voici la description:

   Column    |         Type          | Modifiers 
-------------+-----------------------+-----------
 utilisateur | character varying(20) | not null
 matricule   | integer               |

Le sous-formulaire "Employés" est lié à "Sélection" via le champ "matricule" (clef primaire de la table "employés") comme le montre le panneau des propriétés du sous-formulaire "Employés" (accessible bien sûr en cliquant droit sur l'icône "Employés")


Le formulaire contient un bouton 'OK' qui appartient au sous-formulaire "Employés". Quand on clique dessus après avoir encodé un matricule, le focus passe de "Sélection" à "Employés" ce qui provoque l'écriture dans la table "sélection" du matricule encodé. Ensuite l'action attribuée au bouton (à savoir "Rafraîchir le formulaire") est exécutée.
Tout ceci a été expliqué très en détail précédemment et ça fonctionne très bien.
Maintenant nous aimerions nous passer de ce bouton.
"Rafraîchir le formulaire" consiste en fait à recharger (relire) les tables. Afin de pouvoir procéder sans bouton, nous allons assigner la macro

'*************************************
Sub ReloadTableOne
'*************************************
    Dim Frm as object
    Frm=Thiscomponent.DrawPage.Forms.getByIndex(0)
    Frm.reload()
End Sub

à l’événement "Après l'action d’enregistrement"  figurant dans l'onglet "Événement" des propriétés du formulaire "Sélection"
Auparavant nous devons rendre cette macro accessible. Passons par Outils->Macros-> Gérer les macros


et choisissons d'éditer Mes macros->Standard->Modules 1:


Il nous suffit ensuite d'effectuer un copier-coller du texte de la macro:



De cette manière la macro est disponible pour l'utilisateur courant, quelle que soit le fichier .odb ouvert (elle se trouve dans ~/.config/libreoffice/4/user/basic/Standard/Module1.xba).

On peut aussi l'incorporer au document (bd01.odb dans notre exemple): dans ce cas le code ne sera disponible que pour ce document.
Si bd01.odb->Standard->Module1 n'existe pas encore, il faut cliquer sur nouveau pour le créer:



Si le module existe déjà, cliquer sur "Nouveau" nous permet d'ajouter la macro à ce module.
Pour que la macro puisse être utilisée, il faut via Outils->Options->Sécurité->Sécurité des macros


ajouter l'emplacement du fichier aux sources de confiances.

Si tout est en ordre au niveau des macros, un appui sur ENTER après encodage d'un matricule provoquera un rafraîchissement à condition que le focus soit propulsé hors du formulaire principal "Sélection". Pour cela, il faut veiller à faire passer la séquence d'activation du contrôle "fmtmatricule" de 0 à 1.

Qu'en est-il maintenant si à la place du champ formaté permettant de saisir le matricule, le formulaire contient une boite combinée (combobox):


Dans cette boite combinée se trouve une liste ordonnées de matricules issue de la table "employés". La zone de saisie de la boite combinée est liée à la colonne matricule de la table "sélection". Bien sûr si après avoir cliqué sur un élément de la liste, nous appuyons sur ENTER, tout se passe comme dans le cas précédent. L'idéal serait évidemment que l'on ne doive pas appuyer sur ENTER. Il faudrait que le clic propulse le focus quelque part dans le sous-formulaire "employés".
On peut y arriver en assignant une macro (que nous appelons "SetFocusToEmployés") à l'événement "Statut de l'élément modifié" du panneau des propriétés de la boite combinée (onglet "Événement"):


Ce panneau des propriétés est comme d'habitude accessible en cliquant droit sur ce qui convient (ici l'icône combobox1) dans le navigateur de formulaire:


Voici le texte de cette macro:

'*************************************
Sub SetFocusToEmployes
'*************************************
    Dim Champ AS object
    Dim Controleur AS object
    Champ=Thiscomponent.DrawPage.Forms.GetByName("Sélection").getByName("Employés").getByName("txtnom")
    Controleur = Thiscomponent.getCurrentController()
    Controleur.getControl(Champ).setFocus()
End Sub

qui doit être rendue accessible tout comme la macro précédente

Le bouton "OK" est devenu inutile et le formulaire s'emploie maintenant de manière beaucoup plus confortable puisqu'un seul clic sur un matricule permet d'afficher les données concernées.