Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

WinDev Discussion :

[POO] Bonnes pratiques


Sujet :

WinDev

  1. #1
    Membre à l'essai
    [POO] Bonnes pratiques
    Bonjour,

    Je vous sollicite sur une question de bonnes pratiques concernant la POO.

    J'utilise le mapping objet avec databinding sur les champs. J'ai donc pour chacun de mes fichiers de l'analyse une classe mappée. (Je n'utilise pas de couches présentations, donc pas de MVP)


    Pour l'exemple, imaginons que j'ai une table dans l'analyse "Offre" contenant une rubrique PrixAchat, une rubrique PrixVente et une rubrique Marge. J'ai donc ma classe générée :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    MOffre est une classe <MAPPING=Offre> 
    <MAPPING>
    m_nIDOffre est un entier sur 8 octets <MAPPING=IDOffre,clé unique>
    m_moPrixAchat est un monétaire <MAPPING=PrixAchat>
    m_moPrixVente est un monétaire <MAPPING=PrixVente>
    m_rMarge est un réel <MAPPING=Marge>
    </MAPPING>


    J'ai ensuite une fenêtre avec 3 champs. Dans l'initialisation de la fenêtre, j'initialise mon objet. gpclOffre est un MOffre dynamique <- allouer un MOffre(Id)
    - Champ SAI_PrixAchat (databindé sur gpclOffre.m_moPrixAchat)
    - Champ SAI_PrixVente (databindé sur gpclOffre.m_moPrixVente)
    - Champ SAI_Marge (databindé sur gpclOffre.m_rMarge et à calculer en temps réel lors de la modification des champs prix d'achat ou du prix de vente)


    Ma question porte sur la méthode à utiliser pour calculer et mettre à jour le champ Marge à chaque modification des champs SAI_PrixAchat ou SAI_PrixVente. Comment doit-je m'y prendre ?

    Méthode 1) Dans l'événement "A chaque modification" du champ SAI_PrixAchat ajouter le code : "gpclOffre.m_moPrixAchat = MoiMême" et dans le champ SAI_PrixVente ajouter le code "gpclOffre.m_moPrixVente=MoiMême" afin d'avoir mes membres à jour dans mon objet ? Et après ces codes appeler une méthode "CalculerMarge" de ma classe mappée MOffre :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    PROCEDURE CalculerMarge()
    :m_rMarge = :m_moPrixVente - :m_moPrixAchat


    Et ensuite faire un SourceVersEcran() afin de rafraichir ma vue ?


    Méthode 2) Ou ne pas tenir compte des membres dans ma procédure et utiliser :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    PROCEDURE CalculerMarge(PrixAchat est un numérique, PrixVente est un numérique)
    renvoyer PrixVente - PrixAchat


    Et dans les codes "A chaque modification" de mes champs SAI_PrixAchat et SAI_PrixVente faire SAI_Marge = gpclOffre.CalculerMarge(SAI_PrixAchat, SAI_PrixVente) ?

    Ici, je perd l'intérêt du databinding, et ma méthode CalculerMarge ne tient pas compte des membres de ma classe mappée, donc je ne suis pas sur que cela soit la meilleur solution.
    Je pourrais éventuellement créé la méthode ci-dessus dans une classe globale et ajouter ensuite une autre méthode (CalculerMargeOffre) dans ma classe mappé MOffre qui va appeler la méthode globale :m_rMarge = CGlobal.CalculerMarge(:m_moPrixVente,:m_moPrixAchat), mais cela revient au même que la méthode 1 vu qu'il faut que mes membres soient à jour dans mon objet.


    Méthode 3) Créer la méthode ci-dessus dans une classe globale CGlobal en tant que méthode globale et appeler directement cette méthode à chaque modification des champs SAI_PrixAchat et SAI_PrixVente ? SAI_Marge = CGlobal.CalculerMarge(SAI_PrixAchat,SAI_PrixVente) et donc ne pas avoir de méthode dans ma classe MOffre ?



    Qu'en pensez-vous ? Comment feriez-vous ?


    Autres questions que je me pose en terme de bonnes pratiques et qui sont liées à ma question ci-dessus :
    Q1) Affecter directement un membre PUBLIQUE d'un objet avec les données saisies dans un champ est-il autorisé, propre ? (Ex gpclOffre.m_moPrixAchat = SAI_PrixAchat). Ou il faudrait que j'ajoute des SETTER sur chaque membre de ma classe MOffre et dans mon code faire "gpclOffre.SETprixAchat = SAI_PrixAchat" à la place de "gpclOffre.m_moPrixAchat = SAI_PrixAchat" ? Sachant que mes membres sont PUBLIQUES donc directement accessibles dans mes fenêtres, il y a t-il une différence entre affecter directement les membres publiques ou utiliser des setter pour affecter ?

    Q2) Faut-il mettre à jour en temps réel les membres des classes databindés, donc avoir dans l'événement "A chaque modification" de chaque champ de saisie "MonObjet.MonMembreDatabindé = MoiMême" OU alors mettre à jour qu'à la fin en utilisant EcranVersSource() ?


    Merci d'avance,

    Cordialement

  2. #2
    Membre habitué
    Bonjour,

    Citation Envoyé par Djsven Voir le message
    Bonjour,

    Je vous sollicite sur une question de bonnes pratiques concernant la POO.

    J'utilise le mapping objet avec databinding sur les champs. J'ai donc pour chacun de mes fichiers de l'analyse une classe mappée. (Je n'utilise pas de couches présentations, donc pas de MVP)


    Pour l'exemple, imaginons que j'ai une table dans l'analyse "Offre" contenant une rubrique PrixAchat, une rubrique PrixVente et une rubrique Marge. J'ai donc ma classe générée :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    MOffre est une classe <MAPPING=Offre> 
    <MAPPING>
    m_nIDOffre est un entier sur 8 octets <MAPPING=IDOffre,clé unique>
    m_moPrixAchat est un monétaire <MAPPING=PrixAchat>
    m_moPrixVente est un monétaire <MAPPING=PrixVente>
    m_rMarge est un réel <MAPPING=Marge>
    </MAPPING>

    1 - Tu ne devrais pas utiliser le type réel pour ta marge car ce type ne manipule pas toujours des valeurs précises quel que soit le langage - cf pas mal de messages sur ce forum et sur la toile
    2 - Ce n'est pas une bonne pratique de stocker en base une valeur calculée comme la marge, puisque tu peux la calculer à tout moment à partir des valeurs du prix d'achat et du prix de vente.


    J'ai ensuite une fenêtre avec 3 champs. Dans l'initialisation de la fenêtre, j'initialise mon objet. gpclOffre est un MOffre dynamique <- allouer un MOffre(Id)
    - Champ SAI_PrixAchat (databindé sur gpclOffre.m_moPrixAchat)
    - Champ SAI_PrixVente (databindé sur gpclOffre.m_moPrixVente)
    - Champ SAI_Marge (databindé sur gpclOffre.m_rMarge et à calculer en temps réel lors de la modification des champs prix d'achat ou du prix de vente)
    Donc là, tu charges les valeurs du prix d'achat et du prix de vente (allouer un MOffre(Id) puis tu fais un sourceVersEcran() ?




    Ma question porte sur la méthode à utiliser pour calculer et mettre à jour le champ Marge à chaque modification des champs SAI_PrixAchat ou SAI_PrixVente. Comment doit-je m'y prendre ?

    Méthode 1) Dans l'événement "A chaque modification" du champ SAI_PrixAchat ajouter le code : "gpclOffre.m_moPrixAchat = MoiMême" et dans le champ SAI_PrixVente ajouter le code "gpclOffre.m_moPrixVente=MoiMême" afin d'avoir mes membres à jour dans mon objet ? Et après ces codes appeler une méthode "CalculerMarge" de ma classe mappée MOffre :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    PROCEDURE CalculerMarge()
    :m_rMarge = :m_moPrixVente - :m_moPrixAchat


    Et ensuite faire un SourceVersEcran() afin de rafraichir ma vue ?


    Méthode 2) Ou ne pas tenir compte des membres dans ma procédure et utiliser :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    PROCEDURE CalculerMarge(PrixAchat est un numérique, PrixVente est un numérique)
    renvoyer PrixVente - PrixAchat


    Et dans les codes "A chaque modification" de mes champs SAI_PrixAchat et SAI_PrixVente faire SAI_Marge = gpclOffre.CalculerMarge(SAI_PrixAchat, SAI_PrixVente) ?

    Ici, je perd l'intérêt du databinding, et ma méthode CalculerMarge ne tient pas compte des membres de ma classe mappée, donc je ne suis pas sur que cela soit la meilleur solution.
    Je pourrais éventuellement créé la méthode ci-dessus dans une classe globale et ajouter ensuite une autre méthode (CalculerMargeOffre) dans ma classe mappé MOffre qui va appeler la méthode globale :m_rMarge = CGlobal.CalculerMarge(:m_moPrixVente,:m_moPrixAchat), mais cela revient au même que la méthode 1 vu qu'il faut que mes membres soient à jour dans mon objet.


    Méthode 3) Créer la méthode ci-dessus dans une classe globale CGlobal en tant que méthode globale et appeler directement cette méthode à chaque modification des champs SAI_PrixAchat et SAI_PrixVente ? SAI_Marge = CGlobal.CalculerMarge(SAI_PrixAchat,SAI_PrixVente) et donc ne pas avoir de méthode dans ma classe MOffre ?



    Qu'en pensez-vous ? Comment feriez-vous ?
    Moi, je mettrais le code suivant dans l'initialisation de SAI_Marge et aucun databinding puisque je ne mettrais pas cette valeur en bdd

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
    moimeme = SAI_PrixAchat + SAI_PrixVente


    et le code suivant dans l'évènement à chaque modification des champs SAI_PrixAchat + SAI_PrixVente

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
    executetraitement(SAI_Marge, trtinit)


    D'ailleurs, je choisirais plutôt un champ libellé pour la marge.




    Autre question que je me pose en terme de bonnes pratiques et qui est liée à ma question ci-dessus : Affecter directement un membre PUBLIQUE d'un objet avec les données saisies dans un champ est-il autorisé, propre ? (Ex gpclOffre.m_moPrixAchat = SAI_PrixAchat). Ou il faudrait que j'ajoute des SETTER sur chaque membre de ma classe MOffre et dans mon code faire "gpclOffre.SETprixAchat = SAI_PrixAchat" à la place de "gpclOffre.m_moPrixAchat = SAI_PrixAchat" ? Sachant que mes membres sont PUBLIQUES donc directement accessibles dans mes fenêtres, il y a t-il une différence entre affecter directement les membres publiques ou utiliser des setter pour affecter ?



    Merci d'avance,

    Cordialement
    Je mets systématiquement les membres de mes classes mappées en privé et je génère les propriétés associées pour ne choisir que l'accès en lecture si nécessaire. Par exemple, une clef primaire ne devrait être accessible qu'en lecture puisqu'elle est logiquement gérer par la base.

    hth, padbrain

    [Edit] Tu as modifié ton message pendant que je te répondais.

    Pour ta question Q2, un ecranverssource() juste avant l'enregistrement est suffisant AMHA

  3. #3
    Membre chevronné
    Bonjour,
    Dans la mesure où tu utilises une classe, pourquoi ne pas créer un membre m_Marge, CalculerMarge sera alors son SETTER. Tu pourras alors "databinder" m_Marge
    Lors du changement de valeur de l'un ou l'autre membre, il suffira d'appeler CalculerMarge et de faire un SourceVersEcran.
    Il y a peut être plus simple, mais ça tourne

  4. #4
    Membre à l'essai
    Salut Padbrain,

    1 - Tu ne devrais pas utiliser le type réel pour ta marge car ce type ne manipule pas toujours des valeurs précises quel que soit le langage - cf pas mal de messages sur ce forum et sur la toile
    Ça marche merci pour l'info

    2 - Ce n'est pas une bonne pratique de stocker en base une valeur calculée comme la marge, puisque tu peux la calculer à tout moment à partir des valeurs du prix d'achat et du prix de vente.
    En faite j'ai surtout pris cet exemple pour illustrer une question globale que je me pose :

    Faut-il mettre à jour en temps réel les membres des classes databindés, donc avoir dans l'événement "A chaque modification" de chaque champ de saisie "MonObjet.MonMembreDatabindé = MoiMême" ?

    Car, il est clair que dans ce cas, enregistrer en BDD la marge n'est pas forcément utile, mais plus globalement, si par exemple j'ai une méthode dans une classe mappée qui va manipuler les membres de cette classe afin de renvoyer quelque chose. Si au moment de l'appel (à la modification d'un champ de saisie dans ma vue par ex) mes membres de ma classe ne sont pas à jour avec les champs de saisies de ma vue, la résultat sera donc forcément faussé. C'est pour cela que j'ai pris l'exemple d'une méthode de calcul de marge à chaque modification du prix d'achat et du prix de vente.


    Après même si je n'avais pas de rubrique Marge dans ma bdd, je pourrais cependant avoir une propriété "Marge" dans ma classe MOffre (propriété que je pourrais databindé sur mon champ Marge de ma fenêtre et qui serait mis à jour automatiquement avec SourceVersEcran() à chaque modif du prix d'achat/vente)
    "RENVOYER :m_moPrixVente - :m_moPrixAchat", mais pour cela il faudrait forcement que mes membres :m_moPrixVente et :m_moPrixAchat soient en phase avec la vue, d'où de nouveau la question en gras ci-dessus.



    Sinon oui en effet ta méthode est plus simple et fonctionne parfaitement


    Donc là, tu charges les valeurs du prix d'achat et du prix de vente (allouer un MOffre(Id) puis tu fais un sourceVersEcran() ?
    Oui c'est ce que je fais



    Je mets systématiquement les membres de mes classes mappées en privé et je génère les propriétés associées. Un identifiant ne devrait être accessible qu'en lecture puisqu'il est logiquement gérer par la base.
    Donc pour faire le databinding sur tes champs, tu databind tes champs aux propriétés des classes au lieu des membres directement ?

  5. #5
    Membre à l'essai
    Citation Envoyé par Voroltinquo Voir le message
    Bonjour,
    Dans la mesure où tu utilises une classe, pourquoi ne pas créer un membre m_Marge, CalculerMarge sera alors son SETTER. Tu pourras alors "databinder" m_Marge
    Lors du changement de valeur de l'un ou l'autre membre, il suffira d'appeler CalculerMarge et de faire un SourceVersEcran.
    Salut Voroltinquo,

    Par contre dans ce cas, ça suppose donc que dans chaque champ de saisie, à chaque modification, je mette à jour en temps réel les membres PrixVente et PrixAchat de ma classe, car sinon quand j’appellerais le SETTER CalculerMarge pour affecter le membre m_Marge, si ensuite je fais un sourceVersEcran() et que les valeurs des membres PrixAchat et PrixVente ne sont pas en phase avec les champs de saisie de ma fenêtre, je récupérerais les anciennes valeurs dans ces champs à l'appel de SourceVersEcran(), et c'est justement pour ça que je me demandais si c'était une bonne pratique de mettre dans chaque champ de saisie, à chaque modification, "MonObjet.MonMembreDatabindé = MoiMême" ? (ou MonObjet.MonSetterDatabindé = MoiMême ?)



    Par ailleurs, par rapport à ton exemple, vu qu'on peut databinder directement les propriétés aux champs, dans ce cas je peux simplement créer une propriété "Marge" qui renvoie :m_moPrixVente - :m_moPrixAchat sans avoir besoin de créer un membre m_Marge non? (Même si le principe reste le même)


    Merci

  6. #6
    Membre chevronné

    Par contre dans ce cas, ça suppose donc que dans chaque champ de saisie, à chaque modification, je mette à jour en temps réel les membres PrixVente et PrixAchat de ma classe,
    C'est cela avant l'appel de Calculermarge (éventuellement dans la méthode (à tester), afin de rendre l'opération la plus "transparente" possible si l'on veut rester dans l'esprit objet)


    Par ailleurs, par rapport à ton exemple, vu qu'on peut databinder directement les propriétés aux champs, dans ce cas je peux simplement créer une propriété "Marge" qui renvoie :m_moPrixVente - :m_moPrixAchat sans avoir besoin de créer un membre m_Marge non? (Même si le principe reste le même)
    La propriété doit être associée à un membre. Elle a 2 parties, une partie GETTER et une partie SETTER (il est possible de ne choisir qu'une des 2 possibilité grâce à des cases à cocher "lecture" et "écriture"
    Il y a peut être plus simple, mais ça tourne

  7. #7
    Membre habitué
    Citation Envoyé par Djsven Voir le message
    Salut Padbrain,

    Donc pour faire le databinding sur tes champs, tu databind tes champs aux propriétés des classes au lieu des membres directement ?
    Pas tout à fait. J'utilise le pattern MVP systématiquement.

    Comme je le disais dans un de tes précédents sujets, mes classes mappées ne s'occupent que de l'accès au données de la table (fichier) associée et n'ont pas de logique métier. La logique métier est déplacée dans des classes façades qui peuvent manipuler plusieurs classes mappées et s'occupent de la cohérence des données via des méthodes, pour la plupart, privées et proposent une API à destination des classes de présentation.

    Les classes de présentation possèdent des variables de types structure ou tableaux de structure correspondant aux données à manipuler par la vue. J'implémente aussi deux méthodes privées _moiMemeVersEcran() et _ecranVersMoiMeme() qui me permettent de mettre à jour les données de mes classes façades avec les données manipulées par la vue et le contraire.

    J'appelle ces deux méthodes en fonction de la direction du flux de données.

    Par exemple une méthode charger() d'une classe présentation sera de la forme :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
     
    Procédure charger(pId est un entier) : booléen
     
    soit bRetour = _variableDeTypeFacade.charger(pId)
    SI bRetour ALORS
    _moiMemeVersEcran()
    FIN
    renvoyer bRetour


    et une méthode enregistrer :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
     
    Procédure enregistrer() : booléen
     
    _ecranVersMoimeme()
     
    // Ici, initialisation ou mise à jour de variables non databindées car calculées ou correspondant à des clefs étrangères ou ...
    RENVOYER _variableDeTypeFacade.enregistrer()


    Donc pour répondre à ta question en gras, si ma vue appelle une méthode de ma classe présentation et que cette méthode nécessite de manipuler les variables de la vue mises à jour, j'appelle _ecranVersMoiMeme() dans cette méthode avant de manipuler mes données.
    ça évite de faire x fois le traitement à chaque fois qu'une saisie est effectuée dans un champ.

  8. #8
    Membre à l'essai
    Citation Envoyé par Voroltinquo Voir le message
    La propriété doit être associée à un membre. Elle a 2 parties, une partie GETTER et une partie SETTER (il est possible de ne choisir qu'une des 2 possibilité grâce à des cases à cocher "lecture" et "écriture"
    Pourtant on peut créé une propriété sans forcément l'associer à un membre quand on fait "Nouvelle propriété" sur une classe et qu'on choisit "Aucun". J'avais vu ça sur une vidéo de pcsoft à un moment, que je viens de retrouver : https://youtu.be/PLuQ8UoiUeg?t=441
    D'ailleurs en y regardant, c'est également un calcule de marge dans cette vidéo et eux font une propriété, sans membre associé, qui renvoie le calcule de la marge en fonction des membres Vente et achat. D'après ce qui est dit sur cette vidéo, eux définissent les propriétés comme un mix entre un membre et une méthode qu'il est possible de databinder directement sur les champs.


    Citation Envoyé par Padbrain
    J'implémente aussi deux méthodes privées _moiMemeVersEcran() et _ecranVersMoiMeme() qui me permettent de mettre à jour les données de mes classes façades avec les données manipulées par la vue et le contraire.

    Donc pour répondre à ta question en gras, si ma vue appelle une méthode de ma classe présentation et que cette méthode nécessite de manipuler les variables de la vue mises à jour, j'appelle _ecranVersMoiMeme() dans cette méthode avant de manipuler mes données.
    En gros tes méthodes _moiMemeVersEcran() et _ecranVersMoiMeme() équivalent aux fonctions SourceVersEcran() et EcranVersSource() si on n'utilisent pas le modèle MVP ? Au lieu d'avoir à chaque modification d'un champ l'association avec le membre de sa classe pour mettre à jour le membre en fonction des données saisies dans le champ, toi tu appelles ta méthode quand t'as besoin d'avoir les données à jour, et cette dernière va, comme pour EcranVersSource(), affecter tous les champs de saisie avec leur membre respectif ?

    Donc pour mon cas, vu que je n'utilise pas le modèle mvp, si j'appelle une méthode dans ma fenêtre qui a besoin des données à jour par rapport à la vue, il me suffit donc de faire un EcranVersSource() avant l'appelle de cette méthode ? Un peu comme je ferais lors de la validation de la fenêtre pour enregistrer mes données en bdd (EcranVersSource() puis gpclOffre.bHEnregistrer(clErreur)) ?



    Sinon j'avais bien lu ta méthode sur mon autre poste concernant ta façon de faire, MVP + couche façade en plus, sans travestir les classes mappés, mais étant donné que je n'ai pas beaucoup d'expérience en POO, et que rajouter plusieurs couches prends également du temps que je n'ai plus, je me suis limité à générer mes classes de la même façon que mon analyse (avec les relations), avec les traitements métiers dans ces classes. Et dans ma fenêtre j'utilise des procédures locales afin de ne pas avoir de codes dans les boutons. Par exemple dans le boutons valider de chaque fiche, j'appelle une procédure locale de type <UI> (lP_ValiderFiche() <UI>) qui va simplement vérifier que mes champs obligatoires sont renseignés et ensuite appeler les méthodes de mon objet pour enregistrer.
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    PROCEDURE lP_ValiderFiche() <UI> //Procédure locale de validation de la fiche Offre
    SI SAI_PrixAchat = 0 alors 
    info("Le prix d'achat doit être renseigné")
    RepriseSaisie(SAI_PrixAchat)
    FIN
     
    EcranVersSource()
     
    clErreur est un cErreur
    SI PAS gpclOffre.bEnregistrer(clErreur)
    Erreur(ErreurInfo())
    RETOUR
    FIN
     
    Info("L'offre a été enregistrée")

  9. #9
    Membre habitué
    Oui, tu peux faire comme ça.

  10. #10
    Membre actif
    Méthode 1: elle mets derrière les champs une logique métier qui devrait faire partie de la classe. Donc non, pas pour moi.

    Méthode 3: c'est de l'objet, pour ce genre de besoin il ne devrait pas y avoir besoin de méthode globale, donc non, pas pour moi non plus.

    Méthode 2: tu y es presque. Mais quitte à mettre derrière chaque évènement de modif du prix d'achat et du prix de vente une méthode qui va mettre à jour la marge, autant que le champ de la marge soit directement bindé sur cette méthode: tu fais une property dans la classe. Et c'est fait.

    Q1)Question fort pertinente: il est vrai que la théorie devrait imposer un setter partout histoire de blinder chaque champ de contrôles. En pratique, on a pas franchement le temps de faire ça il faut bien l'admettre. D'autant que lorsque tu mettras à jour l'analyse, les setters ainsi crées ne se mettront pas à jour et tu devras donc réviser le code de la classe (et de toutes ses dépendances).

    Q2)Oui tu peux, mais attention: chaque évènement de modif va déclencher une mise à jour de l'objet. Si tu débutes, faut bien prendre en considération que gérer ce genre d'archi peut te conduire à débugger des effets de bords que tu ne verras pas venir plus tard. Je suis plutôt de l'avis de padbrain sur ce point, avant enregistrement ça peut suffire.

    Et je le rejoins aussi sur un point: la marge pouvant être déduite des prix de vente et d'achat, pas la peine de la stocker. Par contre, si tu veux la marge de milliers de produits d'un coup, il est évident que ne pas la stocker reviendrait à devoir parcourir la table pour les calculer, donc soit la mettre en rubrique calculée dans le fichier (m'en suis jamais servi, voir si les perfs seraient bonnes dans le cas d'une grosse table), soit faire une vue dans la base même si pour un seul champ c'est un peu l'artillerie lourde.

    En revanche je ne rejoins pas padbrain sur un élément : ExecuteTraitement sur un champ. Cette fonction aurait dû être interdite déjà à la base, mais SURTOUT sur un champ graphique, elle n'a pas de sens et provoque des effets de bords monstrueux et complique énormément le débug et la factorisation des codes. Quand un ExecuteTraitement est quelque part, c'est qu'on aurait pu mettre un appel à une "vraie" fonction à la place. ExecuteTraitement est pour moi un outil de facilité et de contournement bien traître.

    Pour le reste et vu ton dernier message, tu en arrives à une solution globale qui me semble appropriée pour le temps que tu as ou qu'il te resterait encore. Cela permet à tout moment d'aller faire un refurb du code si tu veux factoriser encore plus, tout en ne consommant pas trop de temps en dev et en étant assez flex pour faire des évols. L'archi de padbrain me semble plus respectueuse de l'art du métier mais tu pourrais modifier la tienne pour arriver à son résultat, simplement son archi doit prendre plus de temps en conception. Si tu n'as pas ce temps, mieux vaut faire un cran de moins mais le faire bien, que tenter le cran du dessus fait à moitié, qui sera un cauchemar à changer.

  11. #11
    Membre habitué
    Citation Envoyé par kunnskap Voir le message
    ...
    En revanche je ne rejoins pas padbrain sur un élément : ExecuteTraitement sur un champ. Cette fonction aurait dû être interdite déjà à la base, mais SURTOUT sur un champ graphique, elle n'a pas de sens et provoque des effets de bords monstrueux et complique énormément le débug et la factorisation des codes. Quand un ExecuteTraitement est quelque part, c'est qu'on aurait pu mettre un appel à une "vraie" fonction à la place. ExecuteTraitement est pour moi un outil de facilité et de contournement bien traître...

    Oui kunnskap, tu as sans doute raison. Tout comme le binding entre un fichier de données et la vue, l'indirection ou encore l'attribut <associé> restent discutables. Et sans doute encore pas mal de pratiques possibles dans les produits PCSoft que certains utilisent sans modération et d'autres non.

    Cependant, je m'autorise à utiliser ExecuteTraitement() dans mes vues.

    J'ai un petit exercice à soumettre. J'ai trois boutons BTN_Jour, BTN_Semaine, BTN_Mois qui me permettent de sélectionner la période à afficher dans mon agenda AGD_MesRdv.

    Pas compliqué, je dépose dans le code du clic de mes trois boutons le code correspondant à son action :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    AGD_MesRdv.ChangeMode(agzJour)
    AGD_MesRdv.ChangeMode(agzSemaine)
    AGD_MesRdv.ChangeMode(agzMois)


    Mais je veux aussi que, visuellement, l'utilisateur voit quel est le bouton actif, ie. si l'affichage de l'agenda est sur semaine, alors BTN_Semaine sera en vert et les deux autres boutons seront blancs. Si le bouton BTN_Mois est cliqué il passe au vert tandis que BTN_Semaine et BTN_Jour sont blancs.

    Jérôme AERTS dirait : "Alors comment je fais ?"

  12. #12
    Membre actif
    "Le binding entre un fichier de données et la vue": ola oui, on est bien d'accord...
    "L'indirection": qui a donné tant d'erreur à l'exec, même si j'avais trouvé une "solution": créer une fonction inutilisée qui appelle tous les champs et les affecte à eux mêmes, juste pour ne pas avoir de warning à chaque ligne et pour que le compilateur signale ces champs dans le code dès que quelqu'un s'amuse à les changer. Il irait donc dans la fonction où un énorme commentaire disait que ces champs étaient utilisés dans des indirections
    "L'attribut associé": je ne m'en suis jamais servi, mais à lire la doc, c'est une espèce de contournement de l'héritage en objet? Dis m'en plus sur le sujet?

    Pour ton exercice je présume que lors de la création du champ Agenda tu le laisses créer les boutons dont tu parles.
    C'est ce que j'ai fait, mais j'ai changé ces boutons pour qu'ils changent l'attribut mode de ma classe cAgenda via une property au lieu de taper directement le champ Agenda
    Le setter appelle ensuite au choix une callback passée au constructeur pour la maj de l'ihm (qui rappelle donc la procédure locale de la fenêtre pour la mettre à jour, c'est la procédure locale qui contient le code qui sait ce qu'il doit faire pour maj la fenêtre en demandant la property du mode à l'objet oAgenda), ou alors plus simplement, passer le nom de la fenêtre. Evidemment en prévoyant des catchs au cas où l'objet est instancié ailleurs que dans cette fenêtre, histoire de ne pas crasher à l'exec. Ou sinon, on peut aussi changer la propriété de l'objet derrière les boutons et appeler directement le traitement de maj ihm depuis la fenêtre dans le code du bouton aussi avec DemandeMiseAJourUI()
    Et on a un bel agenda avec des boutons dynamique.

    Mais je ne sais pas si c'est ce que Jérôme Aerts ferait...

  13. #13
    Membre habitué
    Citation Envoyé par kunnskap Voir le message
    ...
    Pour ton exercice je présume que lors de la création du champ Agenda tu le laisses créer les boutons dont tu parles.
    C'est ce que j'ai fait, mais j'ai changé ces boutons pour qu'ils changent l'attribut mode de ma classe cAgenda via une property au lieu de taper directement le champ Agenda
    Le setter appelle ensuite au choix une callback passée au constructeur pour la maj de l'ihm (qui rappelle donc la procédure locale de la fenêtre pour la mettre à jour, c'est la procédure locale qui contient le code qui sait ce qu'il doit faire pour maj la fenêtre en demandant la property du mode à l'objet oAgenda), ou alors plus simplement, passer le nom de la fenêtre. Evidemment en prévoyant des catchs au cas où l'objet est instancié ailleurs que dans cette fenêtre, histoire de ne pas crasher à l'exec. Ou sinon, on peut aussi changer la propriété de l'objet derrière les boutons et appeler directement le traitement de maj ihm depuis la fenêtre dans le code du bouton aussi avec DemandeMiseAJourUI()
    Et on a un bel agenda avec des boutons dynamique.

    Mais je ne sais pas si c'est ce que Jérôme Aerts ferait...
    Merci kunnskap pour ta réponse,

    J'ai fait autrement. A l'époque ou j'ai commencer à développer avec windev (quelques mois) je suis tombé sur le blog de de Jonathan LAURENT et notamment sur cet article : Mettre en place un événement sur un groupe de champs que j'ai utilisé pour cette feature.

    Les procédures de l'article en procédures globales et un menu composé de boutons pour lesquels je souhaite gérer l'état actif du menu en cours.
    Tous les boutons se retrouvent dans un groupe GR_Menu et je gère l'état du menu à l'aide d'une procédure interne dans ma FI_Menu :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    	associerTraitementAuGroupe(trtClic, rendreItemMenuActif, GR_Menu)
     
     
    	PROCÉDURE INTERNE rendreItemMenuActif()
    		POUR TOUT bouton DE groupeListerChampsContenus(GR_Menu)
    			SI bouton..Nom = MoiMême..Nom ALORS
    				bouton..CouleurFond = CouleurDésirée
    				bouton..Couleur = AutreCouleurDésirée
    			SINON
    				bouton..CouleurFond = CouleurDéfaut
    				bouton..Couleur = CouleurDéfaut
    			FIN
    		FIN
    	FIN


    Ceci fonctionne très bien mais j'ai depuis fait évolué mon code, en développant les autres fonctionnalités de l'appli, et je vais devoir reprendre l'agenda pour l'adapter à l'architecture à laquelle je suis arrivé.
    Au début, je n'utilisait pas du tout le pattern MVP et il y a du code dans tous les sens avec parfois des effets de bords. Le pattern MVP permet de structurer plus proprement les choses et certainement que ma classe présentation se chargera de mémoriser la valeur du mode d'affichage et un traitement dans la demande de mise à jour IHM permettra de mettre l'état de mon menu à jour.

    PS : Nous devrions arrêter de polluer la discussion du fil de Djsven. Désolé Djsven.

  14. #14
    Membre actif
    Si Djsven repasses par là il aura du coup des petits trucs en plus
    Je ne connaissais pas ..Traitement, solution intéressante.

  15. #15
    Membre à l'essai
    Salut,

    Merci à vous tous pour vos réponses, pour la marge, j'ai finalement opté pour la solution de créer une propriété "Marge" qui renvoie m_moPrixVente-m_moPrixAchat databindé sur mon champ Marge sur ma fenêtre. Lors de la modification du prix de vente, achat, je fais un EcranVersSource() puis une DemandeMiseAjourUI afin de mettre à jour la marge automatiquement.

    J'ai une petite question concernant "DemandeMiseAJourUI", est-il préférable de l'appeler côté vue (procédure local etc), ou bien de l'appeler directement dans les méthodes de classes et ajouter côté vue l'attribut <présentation> à l'objet déclaré dans l'initialisation de la fenêtre afin de faire le lien entre l'objet et l'événement Demande de mise à jour de l'affichage de la vue ? (Je rappelle que je n'utilise pas le modèle MVP)

    Car là actuellement, lors de la modification du prix de vente et d'achat, je fais EcranVersSource() puis DemandeMiseAjourUI() côté vue, mais je pourrais également créer deux setter dans ma classe MOffre (SetPrixAchat, SetPrixVente) qui possède tous les deux la fonction "DemandeMiseAJourUI", et donc lorsque j'appelle ces setter (à chaque modification des champs sai_prixachat, sai_prixvente), cela rafraichi également la vue si j'attribue à mon objet MOffre l'attribut <présentation>.



    J'ai ensuite une autre demande qui n'a rien à voir avec les précédentes mais qui est toujours en rapport avec les "Bonnes pratiques". Je vous explique le contexte :
    Pour un contact, je peux lui affecté un service et une division. La division est lié au service. Donc un service possède plusieurs divisions, mais une division est affectée à un seul et unique service.

    Actuellement, j'ai une classe MService qui possède un objet contenant un tableau d'objet MDivision "m_pclTabDivision est un MTableauDivision". J'ai une méthode dans ma classe MService permettant de remplir l'objet en appelant la méthode de chargement de ma classe MTableauDivision avec comme paramètre mon ID service.
    J'ai dans ma classe MContact les membres mappés ServiceID, DivisionID et un objet m_pclService est un MService dynamique.

    Ce qui donne en gros pour résumé les classes suivantes :
    Classe MDivision :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    MDivision est une Classe <MAPPING=Division>
    	hérite de MBase
    	<MAPPING>
    	m_nDIVid	 est un entier sur 8 octets				<MAPPING=DIVid, clé unique>
    	m_sDIVnom	est une chaîne ANSI						<MAPPING=DIVnom>
    	m_nSRVid	est un entier sur 8 octets 		<MAPPING=SRVid> //ServiceID
     	<FIN>
    FIN


    Classe MTableauDivision :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    MTableauDivision est une Classe
    	hérite de MBaseTableau
    	m_tabDivision est un tableau de MDivision dynamique
    FIN

    => Avec une méthode bCharger(nIdService) pour charger ce tableau "m_tabDivision" avec comme paramètre l'id du service.

    Classe MService :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    MService est une Classe <MAPPING=Service>
    	hérite de MBase
    	<MAPPING>
    	m_nSRVid	est un entier sur 8 octets				<MAPPING=SRVid,clé unique>
    	m_sSRVnom	est une chaîne ANSI						<MAPPING=SRVnom>
    	<FIN>
     
    	m_pclTabDivision est un MTableauDivision dynamique
    FIN

    => Avec une méthode bChargerTableauDivision() qui va charger l'objet m_pclTabDivision en appelant la méthode bCharger(nIdService) de chargement présent dans la classe MTableauDivision et en passant en paramètre m_nSRVid


    Classe MContact :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    MContact est une classe <MAPPING=Contact>
            herite de MBase
           <MAPPING>
            //...
            m_nSRVid	est un entier sur 8 octets 	<MAPPING=SRVid> //ID service
            m_nDIVid  est un entier sur 8 octets 	<MAPPING=DIVid> //ID division
            FIN
     
            m_pclService est un MService dynamique //Objet MService qui possède un sous objet MTableauDivision
    FIN



    Maintenant, côté vue, dans ma fenêtre fiche contact, j'ai un combo Service (Combo_Service) avec la liste de tous les services (Databindé sur TabService de mon objet MTableauService, valeur mémorisée m_nSRVid) , et un combo Division (Combo_Division) qui possède les divisions en rapport avec le service sélectionné dans le Combo_Service (Databindé sur m_tabDivision de mon objet MTableauDivision, valeur mémorisée m_nDIVid).


    Ma question est la suivante : Quelle est la meilleur méthode, parmi les suivantes, pour remplir le combo division après avoir sélectionné un service dans le combo des services ?

    Je pense que je dois utiliser l'objet MService qui possède le sous objet MTableauDivision, charger MTableauDivision mais ce que je ne sais pas, c'est quel objet MService je dois utiliser pour databindé . Celui présent dans ma classe MContact, ou bien un objet que j'aurais déclaré en global dans ma fenêtre Fiche Contact "gpclServiceCourant est un MService" ? Ou alors ne pas utiliser l'objet MService et utiliser directement un objet MTableauDivision (Voir 3) ?

    Donc soit méthode 1 ) S'il faut que j'utilise l'objet m_pclService (MService) de ma classe MContact, avoir une méthode dans MContact qui va charger m_pclService + le sous objet MTableauDivision en appelant leurs méthodes de chargement, et faire le databinding de mon combo_Division sur gpclContact.m_pclService.m_pclTabDivision.m_tabDivision . J'aurais également un setter (SetService) dans MContact qui va affecter l'ID service sélectionné dans le combo Service au membre m_nSRVid et appelé la méthode précédemment créer pour charger l'objet m_pclService (MService) de ma classe MContact.

    Ou soit méthode 2 ) S'il faut que j'utilise l'objet déclaré en global "gpclServiceCourant est un MService" qui n'a rien à voir avec celui dans MContact. Et à la sélection d'un service dans le combo Service, faire gpclServiceCourant <- allouer un MService(combo_service..Valeurmémorisée) puis gpclServiceCourant.bChargerTableauDivision(). Dans ce cas databindé mon combo sur gpclServiceCourant.m_pclTabDivision.m_tabDivision

    OU alors méthode 3 ) Faire différemment, ne pas créer de sous objet MTableauDivision dans la classe MService. Et déclarer directement en global dans ma fenêtre un objet MTableauDivision (gpclTabDivision est un MTableauDivision). Et à la sélection d'un service dans le combo Service, faire gpclTabDivision <- allouer un MTableauDivision() puis gpclTabDivision.bCharger(combo_service..Valeurmémorisée). Dans ce cas databindé mon combo_Division sur gpclTabDivision.m_tabDivision

    Autre ?

    Les 3 fonctionneraient, mais quelle est la meilleur méthode à utiliser pour charger et faire le databinding sur ".m_tabDivision". A partir de l'objet MService présent dans MContact (méthode 1), de l'objet MService déclaré dans la fenêtre (méthode 2), ou de l'objet MTableauDivision sans utiliser MService (Methode 3) ?



    J'espère avoir été le plus claire possible,

    D'avance merci à celui qui pourra me conseiller,


    Bonne journée


    PS : Je met un peu de temps à répondre afin de pouvoir tester et voir s'il y d'éventuelles nouvelles questions d'ordre "Bonnes pratiques" à poser, de manière précise.
    Et ne vous inquiétez pas, vous ne polluez pas la discussion, au contraire c'est intéressant de voir certains traitements dont on n'aurait pas forcément imaginé

  16. #16
    Membre actif
    DemandeMiseAJourUI : si tu l'appelles dans la classe en lui passant un nom de fenêtre en dur, ça va pas, car la classe n'est pas réutilisable si tu la copies ailleurs (c'est un peu le genre de question à se poser dans ce cas pour savoir si on fait bien: si je la sors et que je la mets ailleurs, est-ce que l'IHM va me casser les pieds?)
    Si tu l'appelles dans la classe avec un nom de fenêtre paramétrable, c'est mieux, mais quite à faire ça, autant implémenter ce que j'ai écris plus haut: donner à la classe une méthode qui servira à mettre à jour l'IHM et que la classe rappellera (une variable de type Procédure) en le catchant correctement car l'utilisateur de la classe peut décider de ne passer aucune procédure si il n'en a pas besoin (le refresh IHM par callback n'est pas toujours indispensable ça dépend du traitement)

    Pour la combo, je ne comprends pas trop ce que ta fenêtre a déjà comme globales mais je vais tenter d'apporter un éclairage qui peut être te mettra sur la voie. Rends toi dans les 7 onglets (clic droit Description sur le champ Combo):
    -l'onglet Contenu, c'est comment la combo sera alimentée pour afficher TOUTES les valeurs possibles. Ca doit donc correspondre à une instance de la classe (globale à la fenêtre pourquoi pas) qui contient un tableau de toutes les valeurs possibles
    -l'onglet Liaison, c'est la valeur unitaire à laquelle la combo sera liée

    Ainsi, quand la fiche se charge:
    -la combo Service possède en Contenu une instance de classe dont le tableau contient tous les services (il faut bien que la combo possède ces infos quelque part, sinon si tu veux changer le service, elle ne pourra pas t'afficher la liste de toutes les possibilités de Service puisqu'elle ne les a pas récupérés)

    -la combo Service possède en liaison la rubrique de ton Contact adéquate qui contient l'ID que la combo mémorise dans l'onglet Contenu, et qui correspond au service effectif du contact

    Un contact n'a pas tous les services dans son instance d'objet, il a uniquement l'ID du service qu'il possède. A lui seul il ne peut donc pas suffire pour une combo.

  17. #17
    Membre à l'essai
    Citation Envoyé par kunnskap Voir le message
    DemandeMiseAJourUI : si tu l'appelles dans la classe en lui passant un nom de fenêtre en dur, ça va pas, car la classe n'est pas réutilisable si tu la copies ailleurs (c'est un peu le genre de question à se poser dans ce cas pour savoir si on fait bien: si je la sors et que je la mets ailleurs, est-ce que l'IHM va me casser les pieds?)
    Si tu l'appelles dans la classe avec un nom de fenêtre paramétrable, c'est mieux, mais quite à faire ça, autant implémenter ce que j'ai écris plus haut: donner à la classe une méthode qui servira à mettre à jour l'IHM et que la classe rappellera (une variable de type Procédure) en le catchant correctement car l'utilisateur de la classe peut décider de ne passer aucune procédure si il n'en a pas besoin (le refresh IHM par callback n'est pas toujours indispensable ça dépend du traitement)
    Il n'est pas nécessaire de préciser le nom de la fenêtre dans DemandeMiseAJourUI appelé dans une classe. Si on précise l'attribut <présentation> dans la déclaration de l'objet en global de la fenêtre, les DemandeMiseAJourUI() dans la classe font directement le lien avec la fenêtre grâce à l'attribut <Présentation>.

    En gros, si j'ai ma fenêtre FEN_Contact, si dans la déclaration global, je fais gpclContact est un MContact dynamique <présentation>. Les DemandeMiseAJourUI() de ma classe MContact appelleront bien l'événement Demande de mise a jour de l'affichage de ma fenêtre FEN_Contact.

    C'est pour cela que je demande si je dois utiliser cet attribut et faire mes DemandeMiseAJourUI() dans mes méthodes de classes ou bien directement dans la vue, procédure locale etc. Genre dans une procédure locale faire SourceVersEcran(), + Appelle d'une méthode de ma classe MContact, et ensuite faire DemandeMiseAJourUI() à la suite de ma procédure locale ?


    Pour la combo, je ne comprends pas trop ce que ta fenêtre a déjà comme globales mais je vais tenter d'apporter un éclairage qui peut être te mettra sur la voie. Rends toi dans les 7 onglets (clic droit Description sur le champ Combo):
    -l'onglet Contenu, c'est comment la combo sera alimentée pour afficher TOUTES les valeurs possibles. Ca doit donc correspondre à une instance de la classe (globale à la fenêtre pourquoi pas) qui contient un tableau de toutes les valeurs possibles
    -l'onglet Liaison, c'est la valeur unitaire à laquelle la combo sera liée

    Ainsi, quand la fiche se charge:
    -la combo Service possède en Contenu une instance de classe dont le tableau contient tous les services (il faut bien que la combo possède ces infos quelque part, sinon si tu veux changer le service, elle ne pourra pas t'afficher la liste de toutes les possibilités de Service puisqu'elle ne les a pas récupérés)

    -la combo Service possède en liaison la rubrique de ton Contact adéquate qui contient l'ID que la combo mémorise dans l'onglet Contenu, et qui correspond au service effectif du contact

    Un contact n'a pas tous les services dans son instance d'objet, il a uniquement l'ID du service qu'il possède. A lui seul il ne peut donc pas suffire pour une combo.
    Ma fenêtre à comme globale :
    gpclContact est un MContact dynamique
    gpclTabService est un MTableauService dynamique (classe qui possède un tableau de MService), databindé sur mon combo Service. Cela fonctionne, je récupère bien les services.

    Mais ma question porte sur le combo division, qui lui est lié au service sélectionné dans le combo. Donc quand je selectionne un service dans mon combo service, je dois mettre à jour la liste des divisions dans mon combo division.

    C'est pour cela que je demande si dans ma classe MService, je dois ajouter un objet "m_pclTabDivision est un MTableauDivision" (Liaison un à plusieurs, un service peut être affecté à plusieurs divisions, une division est affecté à un seul service), déclarer en global un objet gpclServiceCourant est un MService, puis à chaque sélection d'un service dans mon combo, faire gpclServiceCourant.ChargerTableauDivision(Code service sélectionné) pour charger la liste des divisons correspondantes au service sélectionné et databindé mon combo division sur gpclServiceCourant.m_pclTabDivision.m_tabDivision

    Ou bien

    Ne pas ajouter d'objet m_pclTabDivision est un MTableauDivision dans ma classe MService, et déclarer en global de ma fenêtre "gpclTabDivison est un MTableauDivision", et à chaque sélection d'un service dans mon combo service, faire gpclTabDivison.charge(Code service sélectionné), et le databind sur gpclTabDivison .m_tabdivision

    Ou bien

    Vu qu'un contact possède un service, j'ai également un sous objet "m_pclService est un MService" dans ma classe MContact, qui lui possède un objet "m_pclTabDivision est un MTableauDivision", donc faire le databind de mon combo division sur gpclContact.m_pclService.m_pclTabDivision.m_tabDivision. En gros par rapport à ce que tu as dit dans ta dernière phrase, je ne parle pas de tous les services dans l'instance d'objet MContact, mais plutôt que cet objet contact possède une instance de MService qui par contre lui possèderait un tableau de toute les divisions. Et donc faire le databind sur ce tableau à partir de l'objet MContact, du sous objet MService et du sous sous objet MTableauDivision


    Dans les 3 cas je peux bien charger le tableau des divisions et donc remplir le combo Division en rapport avec le service sélectionné dans le combo service, mais quelle est la méthode à utiliser ?

  18. #18
    Membre actif
    Je ne me sers pas de l'attribut présentation donc je ne me prononce pas sur ça. Je comprends que si DemandeMiseAJourUI() est dans le code de la classe, la fenêtre raffraichie sera celle ou l'objet est déclaré avec cet attribut, pourquoi pas.

    Si à l'IHM tu dois pouvoir changer de division ça va, mais si tu dois pouvoir changer de service aussi, alors la 3ème solution ne va pas car l'objet MContact n'a pas tous les services. Pour moi l'objet Contact qui possède le service et la division doit figurer dans la partie Liaison des combos

    Mais pour le contenu:

    gpclTabService est un MTableauService dynamique possède les Services et est donc le contenu de la combo Service
    oServiceCourant est un objet qui va dynamiquement récupérer les divisions quand la sélection de la combo Service est faite. Cet objet possède un tableau des divisions, c'est le contenu de la combo Division qu'il conviendra donc de rafraichir à chaque sélection d'un service.

    Donc, solution 1 si j'ai tout suivi

  19. #19
    Membre habitué
    Citation Envoyé par Djsven Voir le message
    Salut,

    Merci à vous tous pour vos réponses, pour la marge, j'ai finalement opté pour la solution de créer une propriété "Marge" qui renvoie m_moPrixVente-m_moPrixAchat databindé sur mon champ Marge sur ma fenêtre. Lors de la modification du prix de vente, achat, je fais un EcranVersSource() puis une DemandeMiseAjourUI afin de mettre à jour la marge automatiquement.

    J'ai une petite question concernant "DemandeMiseAJourUI", est-il préférable de l'appeler côté vue (procédure local etc), ou bien de l'appeler directement dans les méthodes de classes et ajouter côté vue l'attribut <présentation> à l'objet déclaré dans l'initialisation de la fenêtre afin de faire le lien entre l'objet et l'événement Demande de mise à jour de l'affichage de la vue ? (Je rappelle que je n'utilise pas le modèle MVP)

    Car là actuellement, lors de la modification du prix de vente et d'achat, je fais EcranVersSource() puis DemandeMiseAjourUI() côté vue, mais je pourrais également créer deux setter dans ma classe MOffre (SetPrixAchat, SetPrixVente) qui possède tous les deux la fonction "DemandeMiseAJourUI", et donc lorsque j'appelle ces setter (à chaque modification des champs sai_prixachat, sai_prixvente), cela rafraichi également la vue si j'attribue à mon objet MOffre l'attribut <présentation>.
    ...
    Je pense que, dans le cas qui t'intérresse, tu utilises "DemandeMiseAjourUI()" en lieu et place de "SourceVersEcran()". "DemandeMiseAjourUI()" va exécuter l'évènement associé de la fenêtre. Il y a dans le code de cet évènement tout ce qui doit être mis à jour dans ta fenêtre, même s'il ne s'agit pas que de mettre à jour la valeur de la marge.
    Puisque ton champ marge est bindé à la propriété "marge", un sourceVersEcran me semble suffisant.

    Concernant l'endroit où tu dois utiliser cette fonction, je dirais que tu peux l'utiliser dans ta vue ou dans une classe <présentation>. Tu pourras trancher, ou pas, avec l'expérience où tu préfères l'utiliser.

    Tu peux bien sur déclarer une classe de la couche modèle comme <présentation> dans ta vue même si ce n'est pas son rôle, mais tu dévies alors de certains concepts de développement objet, notamment celui du SRP (SRP : Single Responsibility Principle).

    Ceci fonctionnera mais tu auras une application plus difficilement évolutive et maintenable...

    hth,
    Padbrain

  20. #20
    Membre habitué
    Citation Envoyé par Djsven Voir le message
    ...
    Ma question est la suivante : Quelle est la meilleur méthode, parmi les suivantes, pour remplir le combo division après avoir sélectionné un service dans le combo des services ?
    ...
    Moi ce que j'en pense c'est que ton incertitude à savoir quelle méthode employer, quelle classe utiliser, où déclarer tel tableau (ie. : quelle classe doit contenir un tableau de telle autre classe) ou encore quel objet dois-je databinder à tel champ, démontre que ton architecture ne sépare pas correctement les responsabilités et tu vas te retrouver avec des risques d'effet de bord car tes classes prennent trop de responsabilités.

    Moi je ferais une classe <présentation> qui aurait un membre public :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    PUBLIC
         tListeDivisions est un tableau associatif d'entiers


    et une méthode, publique aussi, qui me permettrait de mettre à jour cette liste après avoir sélectionné un service :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    PROCEDURE setListeDivision(pIdService est un entier)
     
    sdSource est une source de donnée = REQ_SelectDivisionsParIdService
    sdSource.pIdService = pIdService
     
    SI PAS HExecuteRequete(sdSource) ALORS
         erreur(HErreurInfo(H...))
    SINON
         POUR TOUT sdSource
              tListeDivisions[sdSource.libelleDivision] = sdSource.idDivision
         FIN
    FIN
     
    //     Et une autre procédure qui renvoie la liste
    PROCEDURE getListeDivisions()
    RENVOYER tListeDivisions


    que j'appellerai sur sélection COMBO_Service :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    goPresentation.setListeDivision(MoiMême)
    DemandeMiseAJourIHM()


    et dans l'évènement de mise à jour de la fenêtre, j'appellerais le traitement d'init de ma COMBO_Division (Même si kunnskap est en désaccord )

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    COMBO_Division.ExécuteTraitement(TrtInit)


    Et enfin, dans le code init de COMBO_Division

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    POUR TOUT nIndice, sLibelle de goPresentation.getListeDivisions()
         MoiMême.Ajoute(sLibelle + gLien(nIndice))
    FIN


    En prenant soin de cocher "Renvoyer la valeur de gLien" dans la fenêtre 7 onglets.

    Voici, si cela peut t'inspirer

    Nota : Toujours pareil, le code est écrit à la volée donc potentiellement à corriger/adapter
    Avec :
    - goPresentation qui est l'objet du type de la classe présentation associée à ta vue et,
    - REQ_SelectDivisionsParIdService qui est la requête de sélection des divisions en fonction de l'id service

    Code SQL :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SELECT
         divisions.id  idDivision,
         divisions.libelle libelleDivision
    FROM
         divisions
    WHERE
         divisions.services_id = {pIdService}



    hth,
    Padbrain