FMX – Boîte de choix

Auto-complétion et alternatives

Serge Girard

Au cours de mes développements d’applications, j’ai souvent eu à proposer des boîtes de choix à l’utilisateur, choix généralement fournis à partir d’une autre table de données. Remplir une boîte de choix (TComboBox) par l’intermédiaire des LiveBindings est chose aisée ; plus ardu est d’aider l’utilisateur à faire son choix lorsque les propositions sont nombreuses, par exemple afin de sélectionner un client pour obtenir son code. Le défi de cet article est de proposer et étayer plusieurs solutions en fonction des besoins.

Commentez

- Titre : FMX – Boîte de choix

- Auteur : Serge Girard

- Parution : 6 avril 2019

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2019 Serge Girard. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Voir également :

- LiveBindings et POO

- Personnaliser un TListView

- Mettre de la couleur dans un TListView

Sommaire

- I. Illustration

- II. Auto-complétion

- II-A. Première solution : dériver le composant

- II-B. Deuxième solution : créer un nouveau composant

- III. Alternative

- III-A. Principe

- III-B. Utilisation de cadres

- III-C. Astuces

- IV. Pour conclure

- IV-A. Références utilisées

- IV-B. Remerciements

I - Illustration

L’application que je présente dans ce chapitre n’est là que comme base de départ de la réflexion. J’utilise un fichier des clients disponible dans le répertoire exemple de Delphi (<répertoire d’installation>\Samples\Data\clients.cds). En voici le design :

J’ai délibérément choisi un programme multiplateforme et donc utilise à fond les possibilités de LiveBindings pour rédiger un programme avec le moins de lignes de code possible (pour cette illustration, pas une seule).

(cliquez ici pour aggrandir l'image)

À l’exécution, j’obtiens ceci :

(cliquez ici pour aggrandir l'image)

La grille n’est là que pour montrer les données chargées. En réalité, seule la boîte de choix sera utilisée.

L’objectif, à savoir sélectionner le numéro du client, est atteint, mais à moins que l’utilisateur ne connaisse le petit nom de chacun en fonction de son numéro, cette boîte de choix n’est pas très adaptée. Est-ce améliorable ? Heureusement oui, en utilisant la propriété FillDisplayCustomFormat de la liaison et en la modifiant ainsi :

%s+' '+Dataset.LAST_NAME.Text+' '+Dataset.FIRST_NAME.Text

Pour ceux qui ne sont pas à l’aise avec l’utilisation des LiveBindings, quelques explications. Cette fonctionnalité, apparue en même temps que la possibilité de la compilation multiplateforme, contient, entre autres choses, un interpréteur d’expressions. La propriété FillDisplayCustomFormat n’est qu’une possibilité de passer une expression au cours du remplissage de la liste.

Bien sûr, par codification (par exemple en ajoutant une colonne calculée à l’ensemble de données et en codant l’événement OnCalcFields), en intervenant plus en amont dans la source de données (par l’obtention des données par SQL) ou encore en remplissant la liste par code, on peut très bien obtenir le même résultat.

(cliquez ici pour aggrandir l'image)

Cette manipulation rend certes la boîte de choix plus explicite, mais force à élargir le composant afin de voir le texte entier. À ce propos, je signale que jouer sur la propriété ItemWidth permet d’obtenir une liste plus large que la boîte de choix.

Bien sûr, la « formule » de l’expression peut être changée. Notez surtout le Dataset.<nom_de_colonne>.text qui permet d’accéder aux valeurs.

II - Auto-complétion

La première chose pratique à proposer à l’utilisateur, c’est bien la fonctionnalité d’auto-complétion, surtout si la liste est importante. Le principe en est relativement simple : si l’utilisateur frappe des touches en un laps de temps donné, la boîte se positionne sur l’élément correspondant au plus près au groupe de touches frappées et évite ainsi à l’utilisateur de faire une recherche dans toute la liste en utilisant le défilement.

II-A - Première solution : dériver le composant

La dérivation du composant est simple dans le principe et implique peu de code.

Si vous avez déjà déposé un TComboBox sur votre forme, vous n’aurez même pas besoin d’indiquer l’utilisation de l’unité FMX.ListBox dans la liste des unités nécessaires (uses).

Deux variables privées sont à ajouter au TCombobox de base, une pour tester les délais (chronometre) et une autre pour mémoriser les touches (keys). En dernier lieu, la procédure OnKeyDown sera surclassée pour implémenter la fonctionnalité souhaitée.

type

TCombobox = class(FMX.ListBox.TComboBox)
private
Chronometre:TDatetime;
Keys:string;
protected
procedure KeyDown(var Key: Word; var KeyChar: System.WideChar; Shift: TShiftState);override;
end;

Vous noterez la formulation de la dérivation TCombobox = class(FMX.ListBox.TComboBox)

Je ne crée pas un nouveau composant, mais force un nouveau comportement du TComboBox standard à l’intérieur de la forme.

Cette opération (hacking) me permet d’accéder à toutes les propriétés non publiées, comme Count ou ItemIndex, ou normalement inaccessibles comme Items.

{ TCombobox }

procedure TCombobox.KeyDown(var Key: Word; var KeyChar: System.WideChar;
Shift: TShiftState);
var I: Integer;
begin
if key=vkReturn then exit;
if CharInSet(keychar,[chr(48)..chr(57),chr(65)..chr(90),chr(97)..chr(122)])
then begin
// préférences personnelles de délai 500 ms
if MilliSecondsBetween(Chronometre,Now)<500
then keys:=keys+keychar
else keys:=keychar; // sinon nouvelle séquence

//reset du chronomètre
Chronometre:=Now;
//Recherche de l'élément
for I := 0 to Self.count-1 do
//    if StartsText(Keys,Items[i]) then begin
if ContainsText(Items[i],Keys) then begin
Self.itemindex:=i;
break;  //premier élément trouvé
end;
end;
inherited;
end;

L’utilisation des fonctions MilliSecondsBetween, StartsTextWith ou ContainsText implique l’ajout des unités System.DateUtils pour la première et System.StrUtils pour les autres.

L’avantage de cette démarche est sa simplicité, mais elle n’est pas sans inconvénient :

- du fait de la dérivation du composant de base, tous les composants TCombobox sur la forme auront le même comportement, ce qui n’est peut-être pas souhaitable ;

- la modification se fait pour l’unité alors qu’une application n’en contient rarement qu’une seule ;

- le délai de frappe n’est pas modifiable ;

- les touches sont filtrées par la fonction CharInSet (chiffres, lettres majuscules et minuscules), ce qui vous a peut-être un peu choqué en ne faisant pas la part belle à l’internationalisation du procédé. Une solution alternative serait de faire l’inverse et de filtrer les touches de clavier « spéciales », mais cela suppose une bonne connaissance des divers claviers.

II-B - Deuxième solution : créer un nouveau composant

Dès que l’on parle de créer un nouveau composant, le doute (je n’ose écrire la peur) s’installe. Un débutant se dira que c’est compliqué. Un programmeur confirmé y verra d’autres inconvénients comme la « perte de temps », « un casse-tête n’en valant pas la peine », etc.

Si je ne peux rien répondre au programmeur confirmé, je peux au moins rassurer le débutant : non, la création d’un composant n’est pas si compliquée qu’elle en a l’air et la première section a déjà bien débroussaillé le chemin. En gros, il s’agit de reprendre dans une unité le code indiqué au-dessus, de saupoudrer d’un peu de propriétés et d’ajouter l’enregistrement du composant dans l’EDI !

Comment faire sans stress ? Sans aucune hésitation, en passant par l’assistant de création de composant (dans le menu : l’option « Composant » puis « Nouveau composant… »).

N’hésitez pas à consulter la documentation à ce propos.

(cliquez ici pour aggrandir l'image)

(cliquez ici pour aggrandir l'image)

(cliquez ici pour aggrandir l'image)

En trois étapes, j’obtiens une première unité squelette.

Avant d’aller plus avant, j’aimerais expliquer pourquoi j’ai préféré passer par la création d’une simple source plutôt que de demander la création d’un nouveau package. Comme toute création de composant est à faire précautionneusement, je préfère déboguer mon code et le meilleur moyen pour cela est bien de créer le composant à l’exécution avant de l’installer. Je reporte donc à plus tard la création du paquet !

unit AutoComplete;

interface

uses
System.SysUtils, System.Classes, FMX.Types, FMX.Controls, FMX.ListBox;

type
TAutoCompleteComboBox = class(TComboBox)
private
{ Déclarations privées }
protected
{ Déclarations protégées }
public
{ Déclarations publiques }
published
{ Déclarations publiées }
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents('Tutoriels', [TAutoCompleteComboBox]);
end;

end.

Après réflexion, je vais d’ores et déjà ajouter quelques propriétés. La réponse rapide que j’ai déjà apportée mettait en lumière les variables privées Chronomometre et Keys. Le délai de frappe modifiable serait évidemment un plus. Enfin, j’envisage également l’ajout du mode de recherche (début de chaîne ou contenu dans) sensible à la casse ou non.

Après ce recensement des fonctionnalités désirées, j’insère les lignes de code suivantes dans la partie privée :

private

FChronometre: TDateTime;
FKeys: string;

En préalable à la déclaration des nouvelles propriétés que je vais ajouter, je déclare un nouveau type pour définir les modes de recherche :

type

TSearchMode = (smStarts,smContains);

Enfin, dans la partie publiée, j’ajoute les lignes suivantes :

published

property Delai: Integer read GetDelai write SetDelai default 500;
property ModeRecherche : TSearchMode read FSearchMode write SetMode default TSearchMode.smStarts;
property CasseSensible : Boolean read FCaseSensitive write SetCaseSensitive default false;

L’utilisation de la combinaison de touche Maj+Ctrl+C va me faciliter la tâche, en écrivant, pour moi, les lignes de code manquantes dans la classe (ajout des noms de propriétés, ajout des fonctions et procédures) et dans la partie implementation.

Après Alt+C

unit AutoComplete;

interface

uses
System.SysUtils, System.Classes, //  System.UITypes,
FMX.Types, FMX.Controls, FMX.ListBox;

type
TSearchMode = (smStarts,smContains);

TAutoCompleteComboBox = class(TComboBox)
private
FChronometre: TDateTime;
FKeys: string;
FDelai: Integer;
FSearchMode : TSearchMode;
FCaseSensitive: Boolean;
procedure SetMode(const Value: TSearchMode);
procedure SetCaseSensitive(const Value: Boolean);
protected
procedure SetDelai(AValue: Integer);
function GetDelai: Integer;
public
published
property Delai: Integer read GetDelai write SetDelai default 500;
property ModeRecherche : TSearchMode read FSearchMode write SetMode default TSearchMode.smStarts;
property CasseSensible : Boolean read FCaseSensitive write SetCaseSensitive default false;
end;

procedure Register;

implementation

procedure TAutoCompleteComboBox.SetCaseSensitive(const Value: Boolean);
begin
FCaseSensitive := Value;
end;

procedure TAutoCompleteComboBox.SetDelai(AValue: Integer);
begin
FDelai := AValue
end;

procedure TAutoCompleteComboBox.SetMode(const Value: TSearchMode);
begin
FSearchMode:=Value;
end;

function TAutoCompleteComboBox.GetDelai: Integer;
begin
SetDelai(FDelai); // vérifier délai raisonnable  0 et 4 s
Result := FDelai;
end;

procedure Register;
begin
RegisterComponents('Tutoriels', [TAutoCompleteComboBox]);
end;

end.

Je vais maintenant ajouter le moteur de l’auto-complétion, tel que déjà entre-aperçu dans la partie II.A. Une seule différence : prendre en compte mes différents modes de recherche.

Dans la partie protégée, j’ajoute la déclaration de la procédure KeyDown qui écrasera celle héritée :

protected

procedure KeyDown(var Key: Word; var KeyChar: System.WideChar; Shift: TShiftState); override;

Toutefois, pour alléger le code, je vais séparer la partie recherche dans une chaîne de la procédure et mettre celle-ci dans une fonction qui renverra l’index de l’élément de liste trouvé :

public

function Recherche(AText: string; AShowDropDown: Boolean = True): Integer;

Pourquoi cette déclaration se fait-elle dans la partie publique ? Parce que, même si je n’en vois pas encore l’utilité, il me sera possible d’utiliser cette fonction à l’exécution !

J’utilise de nouveau la combinaison de touches Maj+Ctrl+C qui ajoute les lignes suivantes dans la partie implementation.

function TAutoCompleteComboBox.Recherche(AText: string;
AShowDropDown: Boolean = True): Integer;
begin

end;

procedure TAutoCompleteComboBox.KeyDown(var Key: Word; var KeyChar:System.WideChar;  Shift: TShiftState);
begin
inherited;
end;

À partir de là, c’est plus une question de codage qu’autre chose ! Voici donc ma vision du processus.

procedure KeyDown

procedure TAutoCompleteComboBox.KeyDown(var Key: Word; var KeyChar:System.WideChar;  Shift: TShiftState);
begin
// test des caractères saisis
if CharInSet(keychar,[chr(48)..chr(57),chr(65)..chr(90),chr(97)..chr(122)]) then
begin
// vérification du délai
if MilliSecondsBetween(TimeOf(Now), FChronometre)<=FDelai
then FKeys := FKeys + KeyChar
else FKeys := KeyChar;
// recherche du texte et positionnement dans la liste
itemIndex:= Recherche(FKeys);
// réinitialisation du chronomètre
if ItemIndex<>-1 then
begin
FChronometre := TimeOf(Now);
end;
end
else inherited;
end;

Fonction Recherche

function TAutoCompleteComboBox.Recherche(AText: string;
AShowDropDown: Boolean = True): Integer;
var found : Boolean;
begin
// au besoin, force l’affichage de la liste
if (not Self.DroppedDown) and (AShowDropDown) then Self.DropDown;
found :=False;
// recherche dans la liste en fonction des options
for result := 0 to Self.Items.Count - 1 do
begin
if FSearchMode=TSearchMode.smStarts then
begin
if FCaseSensitive
then found:=StartsStr(FKeys, Self.Items[Result])
else found:=StartsText(FKeys, Self.Items[Result]);
end
else begin
if FCaseSensitive
then found:=ContainsStr(Self.Items[Result],FKeys)
else found:=ContainsText(Self.Items[Result],FKeys);
end;
if found then Break;
end;
if not found then Result := -1;
end;

Une fois l’unité réalisée et sauvegardée, je passe aux tests. Il est temps que je dévoile mon petit programme de test que vous pourrez retrouver entéléchargement ici.

Design

(cliquez ici pour aggrandir l'image)

Bien sûr, au design, les deux boîtes que je vais tester ne vont pas apparaître.

Exécution

(cliquez ici pour aggrandir l'image)

Quelques explications de code s’imposent, surtout en ce qui concerne le test avec un remplissage fait par l’intermédiaire des LiveBindings.

FormCreate

procedure TForm11.FormCreate(Sender: TObject);
var AAutoComplete,BAutoComplete : TAutoCompleteComboBox;
begin
// création Composant AutoComplete Test
// remplissage par code
AAutoComplete:=TAutoCompleteComboBox.Create(Self);
with AAutoComplete do
begin
Parent:=Self;
Position.X:=24;
Position.Y:=160;
Width:=241;
CasseSensible:=False;
Delai:=500;
ModeRecherche:=TSearchMode.smContains;
Clients.Active:=True;
Clients.First;
while not Clients.EOF do
begin
Items.Add(format('%.0f %s %s',[ClientsSS_NUMBER.asFloat,
ClientsLAST_NAME.AsString,
ClientsFIRST_NAME.asString]));
Clients.Next;
end;
Clients.Active:=False;
end;
// simulation LiveBindings Test
BAutoComplete:=TAutoCompleteComboBox.Create(Self);
with BAutoComplete do
begin
Parent:=Self;
Position.X:=24;
Position.Y:=224;
Width:=241;
CasseSensible:=False;
Delai:=500;
ModeRecherche:=TSearchMode.smContains;
end;

with TLinkFillControlToField.Create(Self) do // pas de synchronisation
// with TLinkListControltoField.Create(Self) do // avec synchronisation
begin
Control := BAutoComplete;
Track := True;
FillDataSource := BindSourceDB1;
FillDisplayFieldName := 'SS_NUMBER';
FillDisplayCustomFormat := '%s+'#39' '#39'+Dataset.LAST_NAME.Text+'#39' '#39'+Dataset.FIRST_NAME.text';
AutoFill := True;
end;
Clients.Active:=True;
end;

En effet, tester la création d’un composant utilisant les LiveBindings interdit toute possibilité d’utiliser le concepteur visuel de liaison ou même l’ajout d’une liaison au TBindingsList au cours du design. J’utilise alors la technique exposée dans le tutoriel LiveBindings et POO pour créer le lien au cours de l’exécution.

Dernier point qui a son importance : vous remarquerez qu’après le remplissage par code du premier composant via une boucle classique, l’ensemble de données est refermé. Il est très important de le faire et de le rouvrir après la création par programmation du lien entre la seconde boîte de recherche et les données.

Animation en format "webm" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://serge-girard.developpez.com/tutoriels/Delphi/Combobox/fichiers/video_combobox.webm

Comme les tests sont concluants, il ne me reste plus qu’à créer un nouveau paquet ou ajouter le source de mon composant dans un paquet existant avant de l’installer. Pour cela, je fais à nouveau appel à l’assistant de création de composant. Cette fois-ci, j’utilise les options du menu : Composant/Installer un composant…

(cliquez ici pour aggrandir l'image)

(cliquez ici pour aggrandir l'image)

Suggestion d’amélioration du composant : travailler sur le filtrage de touches de la procédure KeyDown.

Source du composant téléchargeableici.

Attention, avant de le tester, vérifiez les chemins des fichiers.

menu contextuel

(cliquez ici pour aggrandir l'image)

III - Alternative

Toujours frileux ? Je vous propose une alternative qui ouvre de nombreuses perspectives tout en restant dans l’objectif premier : l’aide au choix.

III-A - Principe

L’origine de ce tutoriel est, en fait, un billet de mon blog où je déplorais les limites du TComboBox. Comme d’habitude, pressé par le temps, il me fallait trouver une solution rapidement. En décomposant les fonctionnalités de la boîte de choix, j’y ai trouvé deux choses : une zone de saisie (quoiqu’en lecture seule) et une liste. D’où l’idée d’associer un TEdit et un TListView, ce dernier ayant déjà fait l’objet de plusieurs communications de ma part, aussi bien dans mon blog que via divers tutoriels que vous pourrez retrouver sur mon site personnel

Le design est donc simple et, bien sûr, la liste ne sera visible qu’à la demande.

(cliquez ici pour aggrandir l'image)

Et le résultat est au rendez-vous avec quelques lignes de code seulement ! Le remplissage de la liste (plus de 4000 éléments) se fait via Livebindings.

// montrer la liste
procedure TForm1.SearchEditButton1Click(Sender: TObject);
begin
ListeClients.Visible:=True;
end;

// cacher la liste en cas de sortie (clic dans une autre zone de la forme)
procedure TForm1.ListeClientsExit(Sender: TObject);
begin
ListeClients.Visible:=False;
end;

// gérer la sélection de l'élément
procedure TForm1.ListeClientsItemClick(const Sender: TObject;
const AItem: TListViewItem);
begin
/// récupération de la valeur
/// première solution, traitement du texte affiché
// SearchClient.Text:=LeftStr(AItem.Text,4);
/// deuxième solution, la valeur souhaitée est reportée, seule,
/// dans une zone de l'item de liste (cachée ou non)
// SearchClient.Text:=AItem.Detail;
/// troisième solution, rendue possible par la synchronisation
SearchClient.Text:=Datas.FDClientsNUM_CLIENT.AsString;
ListeClients.Visible:=False;
end;

// abandon de la recherche par l’utilisation de la touche Escape
procedure TForm1.ListeClientsKeyDown(Sender: TObject; var Key: Word;
var KeyChar: Char; Shift: TShiftState);
begin
if Key=vkEscape then
begin
ListeClients.Visible:=False;
TEdit(ListeClients.Parent).SetFocus;
end;
end;
procedure TForm1.ListeClientsExit(Sender: TObject);
begin
ListeClients.Visible:=False;
end;

Toutefois, impossible d’intercepter l’utilisation la touche Escape à l’intérieur de la boîte de recherche ! Pour pouvoir le faire, il faudra, en premier lieu, indiquer l’utilisation de l’unité FMX.SearchBox, puis associer l’événement ListeClientsKeyDown à la boîte de recherche

(voir ce billet).

uses ... FMX.SearchBox;

procedure TForm1.FormCreate(Sender: TObject);
begin
...
// association de l'évènement
if ListeClients.controls[1].ClassType = TSearchBox
then  TSearchBox(ListeClients.controls[1]).OnKeyDown:=ListeClientsKeyDown;
end;

(cliquez ici pour aggrandir l'image)

(cliquez ici pour aggrandir l'image)

Vous excuserez la censure, l’image fournie ici étant réalisée à partir de données d’entreprise !

L’utilisation de cette technique a un avantage certain puisque la liste peut être largement personnalisable, par exemple en y ajoutant des groupes.

Ci-dessous, une boîte de choix sur l’ensemble de données customer.cds fourni par Embarcadero dans le répertoire exemple (..\samples\data) que vous retrouverez aussi dans le second exemple.

(cliquez ici pour aggrandir l'image)

III-B - Utilisation de cadres

Mettre ces deux composants ainsi qu’une partie du code dans un cadre FireMonkey pourrait également être une solution intéressante et permettrait de créer un pseudo-composant. Je vous invite à visionner à ce propos le webinaire animé par Patrick Prémartin : Créer des composants visuels sans faire de composant.

J’ai donc tenté l’expérience. Tout d’abord, voici mon cadre :

(cliquez ici pour aggrandir l'image)

Posé sur une forme, voilà un premier résultat encourageant :

(cliquez ici pour aggrandir l'image)

Malgré cela, j’émettrai une première critique : les TBindingsList, TBindSource et TClientDataset commencent à foisonner ! S’il y en a peu, cela peut rester gérable ; en tout cas, le concepteur visuel de liaisons reste utilisable.

Ma crainte était surtout la suivante : la liste serait-elle « contrainte » à la taille du cadre ou s’afficherait-elle en entier ? Elle s’affiche en entier, telle que dessinée, ce qui est la bonne nouvelle. Au moment du design, je peux donc réduire, après avoir fait les ajustements de la liste, le cadre à la taille de la zone de saisie.

(cliquez ici pour aggrandir l'image)

(cliquez ici pour aggrandir l'image)

Suite à ce premier test, l’éditeur de liaisons démontrait que modifier la source de données était possible, mais il me fallait écrire un code qui n’utilisait aucun nom de colonne et aucune synchronisation. Comme c’était moins pratique, j’ai dû faire une première concession au niveau du remplissage du TEdit associé à la liste. J’ai choisi de changer l’apparence de l’élément de liste (ListItem en ListItemRightDetail), d’indiquer au niveau des liens que la valeur à récupérer se trouve dans l’objet detail, objet que je peux même rendre invisible.

Pourquoi l’objet detail ? Parce qu’il est très facile d’y accéder !

Il est très important de changer le type d’élément de liste. Presque tous les types contiennent l’élément detail, il faut juste faire attention à ce que ce soit un de ceux-là !

Il suffit alors de changer le code lors de la sélection de l’élément de liste :

// Concession
procedure TComboBoxFrame.ListeItemClick(const Sender: TObject;
const AItem: TListViewItem);
begin
Edit1.Text:=AItem.Detail;  // valeur sélectionnée
Liste1.Visible:=False ;
Edit1.SetFocus ;
end;

Résultat :

(cliquez ici pour aggrandir l'image)

Cette solution est compliquée puisqu’elle nécessite plus de manipulations au moment du design, mais pas insurmontable. Par ailleurs, je peux désormais changer au moment du design les sources de données et les liens et obtenir deux listes différentes à partir d’un même cadre. De là ma réflexion suivante : autant retirer toute notion de données et de liaisons.

(cliquez ici pour aggrandir l'image)

Toutes ces « améliorations » ont un coût. Ce qui, au départ, n’était qu’une utilisation d’un cadre avec ses données devient plus une copie de deux composants avec, je le concède, quelques méthodes particulières. Il y a de plus en plus d’opérations à mener après le placement du cadre :

- établir les liaisons (LiveBindings) ;

- ajuster la position de la liste ;

- rendre la liste invisible une fois le design terminé ;

- réduire le cadre à la taille de la zone de saisie ;

- etc.

Au sein d’un même projet, l’utilisation de ce dernier cadre reste donc très pratique.

La dernière étape décrite au cours du webinaire consistait à faire de ce cadre un vrai composant, mais elle s’est révélée un échec dans ce cas. Bien que la technique exposée par Patrick Prémartin soit possible, ce qui cloche c’est que tout ou presque est non modifiable, dont toute la partie concernant les LiveBindings. Je n’ai pas poussé plus avant après ce constat.

Je déduirai de cette expérience que l’alternative est applicable avec des cadres, voire des templates de cadre, mais pas avec un composant créé à partir d’un cadre.

Vous retrouverez mes essais dans ce projet.

Attention, pour le tester, vérifiez les chemins des fichiers de données et ajoutez le cadre dans votre liste de composants.

III-C - Astuces

N’hésitez pas à modifier les propriétés des éléments de liste.

(cliquez ici pour aggrandir l'image)

Ou même à basculer en mode conception pour rendre votre liste conforme à vos souhaits.

Liste en mode conception

(cliquez ici pour aggrandir l'image)

IV - Pour conclure

Je vous ai exposé trois méthodes pour améliorer l’utilisation de boîtes de choix :

- dériver la classe principale ;

- créer un nouveau composant ;

- utiliser un TEdit associé à un TListView et exploiter toutes les possibilités de cette dernière. Et, en option, utiliser cette alternative dans un cadre.

J’espère aussi avoir un peu vulgarisé la création de composant par héritage !

IV-A - Références utilisées

Je ne pourrais citer toutes les questions posées dans les forums, ce qu’un moteur de recherche avec quelques mots clés pourra facilement vous ressortir. Je tiens cependant à signaler ces deux articles de Brovin Yaroslav : « Nouvelle approche de développement de contrôles FireMonkey 'Contrôle - Modèle – Présentation' » Partie 1, Partie 2 TEdit avec auto complétion, qui pourraient ouvrir d’autres perspectives intéressantes.

IV-B - Remerciements

Au terme de cet article, je tiens à remercier les différents intervenants. Ceux qui, au cours d’une discussion dans le forum m’ont permis de saisir les subtilités de la dérivation utilisée chapitre II.A et mes quelques testeurs. Patrick Prémartin sans qui je n’aurais jamais songé aux cadres du chapitre III.B et, par ricochet, l’organisateur de ce webinaire Maxime Capellot.

Et, bien évidemment, un immense merci à l’équipe rédactionnelle tant aux relecteurs techniques gvasseur58 qu’au correcteur f-leb de mon orthographe et grammaire quelques fois hésitantes !

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article