Classe VBA pour gérer les paramètres d'une application

  • Initiateur de la discussion Initiateur de la discussion oguruma
  • Date de début Date de début

Boostez vos compétences Excel avec notre communauté !

Rejoignez Excel Downloads, le rendez-vous des passionnés où l'entraide fait la force. Apprenez, échangez, progressez – et tout ça gratuitement ! 👉 Inscrivez-vous maintenant !

oguruma

XLDnaute Impliqué
Il est fréquent qu'en VBA que l'on ait besoin de paramètres se trouvant dans une ou plusieurs feuilles de calculs.
Si de nombreux paramètres sont nécessaires on peut très vite arriver à des situations compliquées et le code VBA pourrait en souffrir avec une série de Bugs.
Donc autant se l'éviter.

La solution 1 consisterait à rassembler tous les paramètres dans une feuille spécifique et pour chaque paramètre y accéder avec l'instruction RANGE. Cette solution est acceptable quand il y a très peu de paramètres. Une dizaine environ... ne pas aller au-delà.

Pour de gros développements avec un nombre de paramètres conséquents la solution 1 n'est pas franchement recommandée... sauf si on code à l'arrrachhhe "one shot"...

Solution 2 et c'est celle que je vais détailler en livrant deux classes.

On stocke en fait les paramètres dans une collection. On alimente la collection en lisant le tableau structuré contenant ces paramètres.
Donc il est nécessaire comme vous le verrez de mettre en place une procédure de chargement de ceux-ci dans la collection.
Pour y accéder on met à disposition une méthode GetParam("NOM_DU_PARAMETRE") --> 1ère colonne du tableau structuré..

Pour les moins chevronnés ils découvriront comment on met en oeuvre la POO en VBA.... ce n'est qu'un extrait. On peut aller plus loin mais ce n'est pas le but de ce post.

Aussi, documentez au maximum votre code. Combien j'ai lu de source... mal écrit et surtout.... quand on doit partir en spéléo dans les commentaires introuvables 🙂 Grrrr


Méthodologie

- réunir vos paramètres dans un seul onglet (couleur rouge en général)
- les paramètres seront enregistrés dans un tableau structuré (inutile de rappeler les avantages des TS c'est largement documenté sur la toile)
- minimum 2 colonnes : identifiant (mot clef du paramètre), valeur. Eventuellement une colonne commentaires qui explique sa présence et pourquoi
- en général me concernant : Params = nom de l'onglet, nom du TS = TS_PARAMS

Et c'est là qu'intervient la classe permettant de gérer les paramètres. Le code source est ci-dessous largement documenté.
J'en expose aussi son utilisation via APPLICATION.CALLER qui permet de mettre en place une procédure d'appel universel.
Cela comme démonté à l'avantage de faire l'initialisation de la table une seule à l'appel de la procédure MAIN() qui pilote APPLICATION.CALLER

Je vous laisse lire le code.... il parle de lui-même.

Comment les utiliser

Option Explicit

Sub TEST_LOAD_PARAMS()

'************************************************************************************
'* La feuille Excel doit s'appeler Params
'* Le nom du tableau structuré TB_PARAMS
'* OTS_PARAMS : Objet de tableau global dans le module Mod_Global
'* Cette classe Static nous évite la déclaration de l'objet tableau
'************************************************************************************

Set OTS_PARAMS = New TB_PARAMS_STATIC
MsgBox "COLLECTION :: Nom de la table paramètres : " & OTS_PARAMS.GetTSParamName
MsgBox "COLLECTION :: Nbr de paramètres : " & OTS_PARAMS.GetNbParams
MsgBox "COLLECTION :: Valeur du paramètre TEST = " & OTS_PARAMS.GetParam("TEST")
MsgBox "COLLECTION :: Valeur du paramètre PARAM_INCONNU = " & OTS_PARAMS.GetParam("PARAM_INCONNU")

'************************************************************************************
'* Le nom de la feuille est libre
'* Le nom du tableau structuré est libre
'* La méthode oDynParam.Init doit être appelée à l'issue de l'instanciation
'* Exemple : oDynParam.Init("Params", "TB_PARAMS")
'* En passant par cette classe on peut gérer plusieurs tableaux de paramètres
'* Si l'objet exemple oDynParam est appelé dans plusieurs modules
'* il est recommandé de le déclaré public dans un module réservé
'************************************************************************************


'*=*=*=* *=*=*=*
'*=*=*=* Déclaration uniquement pour la procédure *=*=*=*
'*=*=*=* *=*=*=*
Dim oDynParam As TB_PARAMS_DYNAMIC
Set oDynParam = New TB_PARAMS_DYNAMIC
Call oDynParam.Init("Params", "TB_PARAMS")
MsgBox "COLLECTION :: oDynParam :: Valeur du paramètre TEST = " & oDynParam.GetParam("TEST")

'************************************************************************************
'* Exemple avec un objet Public GDynParam
'************************************************************************************
Set GDynParam = New TB_PARAMS_DYNAMIC
Call GDynParam.Init("Params", "TB_PARAMS")
MsgBox "COLLECTION :: GDynParam :: Valeur du paramètre TEST = " & GDynParam.GetParam("TEST")
End Sub


Comment les utiliser avec APPLICATION.CALLER via une procédure commune MAIN()

Option Explicit


Dim oDynParam As TB_PARAMS_DYNAMIC

Public Sub MAIN(Optional sDUMY As String)
Dim vAppel As Variant
Dim sType As String

'*******************************************************************************
'* Cas de la classe static - public - tous modules
'*******************************************************************************
Set OTS_PARAMS = New TB_PARAMS_STATIC

'*******************************************************************************
'* Cas de la classe dynamique - public - tous modules
'*******************************************************************************
Set GDynParam = New TB_PARAMS_DYNAMIC
Call GDynParam.Init("Params", "TB_PARAMS")

'*******************************************************************************
'* Cas de la classe dynamique - du module
'*******************************************************************************
Set oDynParam = New TB_PARAMS_DYNAMIC
Call oDynParam.Init("Params", "TB_PARAMS")

'*******************************************************************************
'* On remarque que les initialisations se font une seule fois
'* et qu'il possible d'appeler les paramètres quelque soit la procédure
'* en passant par l'astuce de MAIN avec APPLICATION.CALLER
'* on peut gérer l'appel de procédures ou fonctions communes
'*******************************************************************************

sType = TypeName(Application.Caller)
Select Case sType
Case "Range"
vAppel = Application.Caller.Address
Case "String"
vAppel = Application.Caller
Case "Error"
vAppel = "Error"
Case Else
vAppel = "unknown"
End Select
If sType <> "String" Then Exit Sub

Select Case vAppel
'=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
' Bouton de la source
'=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Case "BTN_1"
BTN_1
Case "BTN_2"
BTN_2
Case "BTN_3"
BTN_3
Case Else
MsgBox "Pas de fonction disponible", vbExclamation
End Select

End Sub

Private Sub BTN_1()
MsgBox "OTS_PARAM Valeur du paramètre BTN1 = " & OTS_PARAMS.GetParam("BTN1")
MsgBox "GDynParam Valeur du paramètre BTN1 = " & GDynParam.GetParam("BTN1")
MsgBox "oDynParam Valeur du paramètre BTN1 = " & oDynParam.GetParam("BTN1")
End Sub

Private Sub BTN_2()
MsgBox "OTS_PARAM Valeur du paramètre BTN2 = " & OTS_PARAMS.GetParam("BTN2")
MsgBox "GDynParam Valeur du paramètre BTN2 = " & GDynParam.GetParam("BTN2")
MsgBox "oDynParam Valeur du paramètre BTN2 = " & oDynParam.GetParam("BTN2")
End Sub

Private Sub BTN_3()
MsgBox "OTS_PARAM Valeur du paramètre BTN3 = " & OTS_PARAMS.GetParam("BTN3")
MsgBox "GDynParam Valeur du paramètre BTN3 = " & GDynParam.GetParam("BTN3")
MsgBox "oDynParam Valeur du paramètre BTN3 = " & oDynParam.GetParam("BTN3")

End Sub

TB_PARAMS_STATIC


Option Explicit

'************************************************************************************
'* CLASSE DE PARAMETRES STATIC
'* La feuille Excel doit s'appeler Params
'* Le nom du tableau structuré TB_PARAMS
'* Le chargement des paramètres se fait à l'issue de l'initialisation de l'objet
'************************************************************************************



'************************************************************************************
'* A modifier si nécessaire selon le contexte de l'application
'************************************************************************************
Const WK_PARAMS As String = "Params"
Const TB_PARAMS As String = "TB_PARAMS"
'************************************************************************************

Private pTS_Params As ListObject
Private pWb_MACRO As Workbook
Private pNbParams As Integer
Private pName As String
Private pParameters As Collection

Private Sub Class_Initialize()

Set pWb_MACRO = ThisWorkbook
Set pParameters = New Collection
Set pTS_Params = pWb_MACRO.Worksheets(WK_PARAMS).ListObjects(TB_PARAMS)
pName = pTS_Params.Name

'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
'* On vérifie le nombre de lignes paramètres
'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
If pTS_Params.DataBodyRange Is Nothing Then
pNbParams = 0
Else
pNbParams = pTS_Params.DataBodyRange.Rows.Count
End If

'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
'* Chargement des paramètre selon les valeurs statiques
'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
Call Load_Parameters


End Sub

Sub AddParam(hKey As String, hValue As Variant)
pParameters.Add Key:=hKey, Item:=hValue
End Sub

Function GetParam(hKey As String) As Variant
On Error Resume Next
GetParam = pParameters(hKey)
If Err Then GetParam = "<N/A>"
End Function

Function GetNbParams() As Integer
GetNbParams = pNbParams
End Function

Function GetTSParamName() As String
GetTSParamName = pTS_Params.Name
End Function

Private Sub Load_Parameters()
'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
'* Chargement du tableau dans la collection
'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
Dim iParam As Integer
Dim sClef As String
Dim sValeur As String

On Error GoTo HANDLE_ERROR
For iParam = 1 To pNbParams
sClef = Trim(pTS_Params.DataBodyRange.Cells(iParam, 1))
sValeur = pTS_Params.DataBodyRange.Cells(iParam, 2)
If sClef <> "" Then
pParameters.Add Key:=sClef, Item:=sValeur
End If
Next

FIN:
Exit Sub

HANDLE_ERROR:
MsgBox "Erreur pendant le chargement des paramètres - Param N° : " & iParam & vbLf & _
"Err # " & Err.Number & vbLf & _
Err.Description, vbCritical, "PARAMETRES"
Resume FIN
End Sub


TB_PARAMS_DYNAMIC

Option Explicit

'************************************************************************************
'* CLASSE DE PARAMETRES DYNAMIC
'* La feuille Excel est libre
'* Le nom du tableau structuré est libre
'* Une procédure d'initialisation se charge de prendre en compte la feuille et le TS
'************************************************************************************



'************************************************************************************
'* A modifier si nécessaire selon le contexte de l'application
'* Ces paramètres spécifiques seront alimentés par une procédure d'initialisation
'************************************************************************************
Private pWK_PARAMS As String
Private pTB_PARAMS As String
'************************************************************************************

Private pTS_Params As ListObject
Private pWb_MACRO As Workbook
Private pNbParams As Integer
Private pName As String
Private pParameters As Collection

Private Sub Class_Initialize()
Set pParameters = New Collection
Set pWb_MACRO = ThisWorkbook
End Sub

Sub Init(hWK As String, hTB As String)

Set pTS_Params = pWb_MACRO.Worksheets(hWK).ListObjects(hTB)
pName = pTS_Params.Name

'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
'* On vérifie le nombre de lignes paramètres
'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
If pTS_Params.DataBodyRange Is Nothing Then
pNbParams = 0
Else
pNbParams = pTS_Params.DataBodyRange.Rows.Count
End If

'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
'* Chargement des paramètre selon les valeurs statiques
'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
Call Load_Parameters
End Sub
Sub AddParam(hKey As String, hValue As Variant)
pParameters.Add Key:=hKey, Item:=hValue
End Sub

Function GetParam(hKey As String) As Variant
On Error Resume Next
GetParam = pParameters(hKey)
If Err Then GetParam = "<N/A>"
End Function

Function GetNbParams() As Integer
GetNbParams = pNbParams
End Function

Function GetTSParamName() As String
GetTSParamName = pTS_Params.Name
End Function

Private Sub Load_Parameters()
'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
'* Chargement du tableau dans la collection
'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
Dim iParam As Integer
Dim sClef As String
Dim sValeur As String

On Error GoTo HANDLE_ERROR
For iParam = 1 To pNbParams
sClef = Trim(pTS_Params.DataBodyRange.Cells(iParam, 1))
sValeur = pTS_Params.DataBodyRange.Cells(iParam, 2)
If sClef <> "" Then
pParameters.Add Key:=sClef, Item:=sValeur
End If
Next

FIN:
Exit Sub

HANDLE_ERROR:
MsgBox "Erreur pendant le chargement des paramètres - Param N° : " & iParam & vbLf & _
"Err # " & Err.Number & vbLf & _
Err.Description, vbCritical, "PARAMETRES"
Resume FIN
End Sub
 
Dernière édition:
bsr, en effet une idée, ta proposition me laisse imaginer que tu as déjà été confronté à cette problématique.
L'avantage du TS c'est que ça reste à la portée de l'utilisateur si un changement de paramètre est nécessaire, ce qui peut être compliqué dans la solution que tu proposes ou alors j'ai fais une fausse interprétation de ta proposition. En termes d'échanges si tu as un exemple en "grosso merdo" je suis preneur 😉
Dans ta proposition si toujours ben compris les paramètres seraient protégés de toutes interventions extérieures si je suis dans le vrai ?
 
Bonjour @oguruma
juste en passant
depuis longtemps j'ai proscrit l'enregistrement d'un paramètre dans une variable et par conséquent une classe ou dans un simple module
en effet le simple fait qu'il y est un débogage quelque part (pas forcément dans le module classe) vide les variables
conclusion perte des paramètres

a ce jour et depuis bien for longtemps la meilleure solution pour des paramètres nombreux et variés

la première la solution la plus sure c'est le fichier ".ini" ou ".xml" dynamique on écrit ou modifie en dur dans un fichier
la lecture ou l’écriture d'un fichier "ini", "xml "voir même "txt")se fait en quelques nanosecondes

la 2d solution c'est une feuille avec un tableau(structuré ou pas comme vous voulez)
perso je préfère le TS car c'est plus facile a coder l’accès

un module ou une classe est une fausse bonne idée même si l'exercice de style est intéressant
 
Bonjour @oguruma
juste en passant
depuis longtemps j'ai proscrit l'enregistrement d'un paramètre dans une variable et par conséquent une classe ou dans un simple module
en effet le simple fait qu'il y est un débogage quelque part (pas forcément dans le module classe) vide les variables
conclusion perte des paramètres

a ce jour et depuis bien for longtemps la meilleure solution pour des paramètres nombreux et variés

la première la solution la plus sure c'est le fichier ".ini" ou ".xml" dynamique on écrit ou modifie en dur dans un fichier
la lecture ou l’écriture d'un fichier "ini", "xml "voir même "txt")se fait en quelques nanosecondes

la 2d solution c'est une feuille avec un tableau(structuré ou pas comme vous voulez)
perso je préfère le TS car c'est plus facile a coder l’accès

un module ou une classe est une fausse bonne idée même si l'exercice de style est intéressant
Bonjour Patrick,
"depuis longtemps j'ai proscrit l'enregistrement d'un paramètre dans une variable"
=> à chacun sa méthode !
Je n'ai jamais été friand du tout en dur et quelque soit le langage, l'environnement, l'os.
Sache que par exemple dans l'environnement MAINFRAME IBM OS/390 (La Grosse Babasse)
On ne code jamais ou très rarement des valeurs en dur
eg. en COBOL
77 PARAM PIC 9(2) VALUE 10.
ou
77 PARAM PIC 9(2).
MOVE 10 TO PARAM.

Pour cela on passe par un JCL dans lequel on déclare des cartes paramètres que l'on va relire avec une instruction ACCEPT... FROM
(je te la fais simple)... ou on passe aussi ce paramètre dans la carte EXEC qui va lancer le module compilé.

Ou encore on passe aussi par une table, dite "table SPI" ou encore SPITAB ou encore une table spécifique sous DB2 (un peu équiv d'oracle dans le principe) avec des JCL de paramétrages dédiés.
C'est une table qui est créée avec deux colonnes, paramètre et valeur...
Bon j'arrête là mais je peux te faire un cours spécial sur la programmation COBOL+construction de JCL sur Mainframe 🙂

Je te parle de COBOL mais pour tout autre langage utilisé dans ces environnements comme en REXX où on passe par des EXECIO.


au risque d'être partiellement en désaccord avec toi
j'utilise cette méthode depuis plus de 10 ans et elle a toujours bien fonctionné.
Oui en effet j'utilise un TS comme table de paramètres.
Il est composé de deux colonnes : MOT_CLE du paramètre, VALEUR du paramètre, voire une 3ème colonne COMMENTAIRES
L'ordre des mots clefs n'a aucune importance.
Les paramètres sont chargés dans une collection ce qui permet de les retrouver rapidement et aussi de vérifier qu'il n'y a pas de doublons. Cela peut arriver.
La classe est peut-être luxueuse, certes, on pourrait passer par une fonction dédiée. Comme je dis et redis chacun sa méthode.
L'avantage d'une classe c'est qu'elle est portable.

Je n'adhère pas à cela => un module ou une classe est une fausse bonne idée

Oui aussi on peut passer par des fichiers externes .ini ou autres que l'on dépose dans le répertoire du classeur mais cela fait deux livraisons le classeur+le fichier paramètres voir un module d'installation pour les utilisateurs pas trop dégourdis 🙂

le fait d'intégrer cela dans un classeur tu as un tout en un !
la feuille qui héberge le TS peut-être aussi cachée véry-hidden pour plus de sécurité aussi on passe par du VBA et ça limite les accès pour les non avertis au VBA



ENFIN ! je me demande quel est le but de ce post ! chacun son point de vue
ma méthode a porté ses fruits plus d'une fois surtout quand il faut changer une valeur c'est plus simple de faire modifier le contenu d'une cellule dans le TS que de modifier celle-ci dans le source ou de demande à l'utilisateur lambda d'utiliser un éditeur de texte et le faire naviguer dans le fichier .ini ou dans un .xml... certains sont doués certains ont 4 mains gauches !
 
Dernière édition:
re
@oguruma oui un tableau d'accords pas de soucis (lecture /écriture)
parti de la une classe n'est pas nécessaire
donc depuis 10 ans tu a eu une chance phénoménale
car le moindre pet de travers quelque part dans le VBA (n'importe ou) remet les variable a l'initiale
le but :
juste pour te rappeler et je sais au combien que toi tu le sais
il n'y a rien de mieux qu'un fichier ini ou txt ou le format que tu veux en fait
je le redis même si l'exercice de style est intéressant (les modules object (classe))
à moins d'être réinitialisé a chaque besoin d'un paramètre au quel cas c'est beaucoup de ramdam
autant utiliser le tableau comme source et object
VB:
Sub ecriture()
    With Range("paramètres")
        .Cells(1, 2) = True
        .Cells(2, 2) = False
        .Cells(3, 2) = False
    End With
End Sub

Sub lecture()
    With Range("paramètres")
        MsgBox "mon paramètres N°2 [" & .Cells(2, 1) & "] est à " & .Cells(2, 2)
    End With
End ' "End tout Court" ici on vide vba de toute variable 
'ça pourrait être une erreur provoquant le réinit avec[B] click su débogage ou fin en cas d'erreur quelque pârt dans le code d'un des module[/B]
'relance la sub  et tu constatera que  rien n'est perdu 
'fait la même chose avec une classe ben bye!bye!!
End Sub
1752924368063.png


ce n'est pas une question de méthode mais de bon sens
tout simplement par ce qu'un paramètres pour un quelconque besoin ne doit pas être volatile
le tableau paramètres lui est persistant tandis qu'une classe est tributaire du bon fonctionnement du reste du code de tout les autres modules

dois-je te faire un exemple ou une classe serait caduque dans un certain contexte après une erreur ?

mais les exercices de style avec les modules classes sont terriblement manquants sur xld
beaucoup encore ne savent pas gérer une classe d'object

L'avantage d'une classe c'est qu'elle est portable.
intéresse toi aux name et name de names voir aux customProperty aussi
qui ont l'avantage de pouvoir persister même après fermeture du fichier si enregistré
la pour le coup la portabilité prends tout son sens

patrick
😉
 
re
@oguruma oui un tableau d'accords pas de soucis (lecture /écriture)
parti de la une classe n'est pas nécessaire
donc depuis 10 ans tu a eu une chance phénoménale
car le moindre pet de travers quelque part dans le VBA (n'importe ou) remet les variable a l'initiale
le but :
juste pour te rappeler et je sais au combien que toi tu le sais
il n'y a rien de mieux qu'un fichier ini ou txt ou le format que tu veux en fait
je le redis même si l'exercice de style est intéressant (les modules object (classe))
à moins d'être réinitialisé a chaque besoin d'un paramètre au quel cas c'est beaucoup de ramdam
autant utiliser le tableau comme source et object
VB:
Sub ecriture()
    With Range("paramètres")
        .Cells(1, 2) = True
        .Cells(2, 2) = False
        .Cells(3, 2) = False
    End With
End Sub

Sub lecture()
    With Range("paramètres")
        MsgBox "mon paramètres N°2 [" & .Cells(2, 1) & "] est à " & .Cells(2, 2)
    End With
End ' "End tout Court" ici on vide vba de toute variable
'ça pourrait être une erreur provoquant le réinit avec[B] click su débogage ou fin en cas d'erreur quelque pârt dans le code d'un des module[/B]
'relance la sub  et tu constatera que  rien n'est perdu
'fait la même chose avec une classe ben bye!bye!!
End Sub
Regarde la pièce jointe 1220536

ce n'est pas une question de méthode mais de bon sens
tout simplement par ce qu'un paramètres pour un quelconque besoin ne doit pas être volatile
le tableau paramètres lui est persistant tandis qu'une classe est tributaire du bon fonctionnement du reste du code de tout les autres modules

dois-je te faire un exemple ou une classe serait caduque dans un certain contexte après une erreur ?

mais les exercices de style avec les modules classes sont terriblement manquants sur xld
beaucoup encore ne savent pas gérer une classe d'object


intéresse toi aux name et name de names voir aux customProperty aussi
qui ont l'avantage de pouvoir persister même après fermeture du fichier si enregistré
la pour le coup la portabilité prends tout son sens

patrick
😉
les classes si ils connaissent... copier/coller... je ne rien compris je cris au secours sur xld... c'est ça la démarche... ça m'orrrripile à donfff
je vais voir dans un autre dév qui sait 🙂
sinon bien ecoutes 🙂 je vais rester sur 10 années de chance, de ce côté elle a toujours bien fonctionné quand tu expliques à l'utilisateur l'objet de cette feuille et tu lui dis "pas touche" sauf si grand besoin... et voir le guide utilisateur que je t'ai livré 🙂 lol, allez bon we, merci 🙂
 
- Navigue sans publicité
- Accède à Cléa, notre assistante IA experte Excel... et pas que...
- Profite de fonctionnalités exclusives
Ton soutien permet à Excel Downloads de rester 100% gratuit et de continuer à rassembler les passionnés d'Excel.
Je deviens Supporter XLD

Discussions similaires

Retour