[résolu] notification de classe ou d'instance

mybofymybofy Membre
septembre 2013 modifié dans API AppKit #1

Bonjour


 


À un WindowController j'ajoute un Notification Observer de nom @CloseWindow pour fermer la fenêtre associée depuis un objet quelconque, sans lien direct avec le WindowController.


 


Je crée plusieurs instances du WindowController, qui affichent chacune la fenêtre associée.


 


Si je poste la notification  @CloseWindow, toutes les fenêtrent se ferment.


Si je comprends bien la notification  @CloseWindow est envoyée à  toutes les instances du WindowController.  


 


Est-il possible de poster une notification à  une seule des instances du WindowController ?


 


Merci.


 


 


Réponses

  • samirsamir Membre
    septembre 2013 modifié #2

    Bonjour,


     


    Tu peux choisir quelle instance s'abonne a l'écoute de la notification, comme ça y a que cette instance qui vas recevoir la notification "CloseWindow" '. 


  • AliGatorAliGator Membre, Modérateur
    Oui. Cf la doc de NSNotificationCenter (tu as un paramètre object, entre autres)

    Mais bon, le principe même des notifications est de "crier une information sur tous les toits à  qui veut l'entendre" genre "Hého, pour qui ça intéresse, l'utilisateur vient de changer le thème dans ses préférences" ou des trucs comme ça. Et après, chacun s'abonne à  la notification en tant qu'observeur s'il est intéressé pour écouter telle ou telle notif.

    Du coup envoyer la notification à  un seul objet spécifique, ça perd un peu de son intérêt. Pourquoi ne pas simplement exposer une méthode publique de ton WindowController (à  moins qu'elle n'existe pas déjà , d'ailleurs, je connais pas trop NSWindowController par coeur) pour permettre de fermer la fenêtre, et appeler cette méthode quand tu veux fermer ladite fenêtre ?
    Un des autres avantages des notifs c'est que tu peux ainsi découpler l'appelant et l'appelé puisque celui qui crie le message "Je veux fermer les fenêtres" s'en fiche de savoir qui écoute, et celui qui écoute s'en fiche de savoir qui a crié. Mais dans ton cas, puisque tu dis que tu as déjà  l'objet WindowController que tu veux cibler, pourquoi lui crier dans les oreilles à  lui et à  lui seul plutôt que de simplement appeler une méthode -close dessus ?
  • mybofymybofy Membre
    septembre 2013 modifié #4


    Bonjour,


     


    Tu peux choisir quelle instance s'abonne a l'écoute de la notification, comme ça y a que cette instance qui vas recevoir la notification "CloseWindow" '. 




    Je sais abonner ma classe mais pas une instance de cette classe ?


    Dans  addObserver:selector:name:object:  object fait référence à  l'objet qui poste la notification, pas à  celui qui observe la notification me semble-il.

  • CéroceCéroce Membre, Modérateur


    Je sais abonner ma classe mais pas une instance de cette classe ?


    Dans  addObserver:selector:name:object:  object fait référence à  l'objet qui poste la notification, pas à  celui qui observe la notification me semble-il.




     


    Et observer ?



  • Et observer ?




     


    Quid observer ?


    C'est un peu court, non ?

  • Tout est dans la documentation et les réponses a tes questions.


    Dans la doc :  addObserver:selector:name:object 


    Ici l'observer c'est l'objet qui doit écouter la notification, c'est de ça qui @Céroce te parle. Donc quand que tu crée ton objet comme suit :


     


    WindowController *obj = [WindowController alloc].....


    la tu abonne ton obj a la notification voulu et ça sera que celui la qui pourra recevoir la notification.


     


    Mais comme @AliGator t'a répondu sur le principe des NotificationsCenter, ce n'est pas vraiment adapté a ton cas. Donc tu peux seulement exposer une méthode dans ton objet closeWindow ... ou bien tu peux créer un Protocol/Delegate si tu ne veux pas avoir de dépendances fortes.

  • Je crois avoir trouvé une solution.



    [[NSNotificationCenter defaultCenter] postNotificationName:@ClosePlantesListeWin object:[[[[self view] superview] window] windowController]];

    - (void)notifClosePlantesListeWin:(NSNotification *) aNotif {
    if ([[aNotif name] isEqualToString:@ClosePlantesListeWin]) {
    if ([aNotif object] == self) {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[self window] close];
    ...


    Maintenant je peux avoir autant d'instances de WindowController que je veux et les fermer une à  une sans problème.


    Est-ce une programmation correcte ?


     


    Grand merci à  tous.


     


    PS : même si les post ne donnent pas directement la solution, le fait de lire et d'écrire les post me permet de trouver des solutions "robustes".

  • AliGatorAliGator Membre, Modérateur

    Quid observer ?
    C'est un peu court, non ?

    Je peux faire plus court que Céroce si tu insistes : RTFM.
  • samirsamir Membre
    septembre 2013 modifié #10



    [[NSNotificationCenter defaultCenter] postNotificationName:@ClosePlantesListeWin object:[[[[self view] superview] window] windowController]];

    - (void)notifClosePlantesListeWin:(NSNotification *) aNotif {
    if ([[aNotif name] isEqualToString:@ClosePlantesListeWin]) {
    if ([aNotif object] == self) {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[self window] close];
    ...


    Est-ce une programmation correcte ?




    Non ;).


     


    1. ClosePlantesListeWin Non Il faut la déclarer comme constantes, parce que la tu l'utilise au moins deux fois et si un jour tu n'affiche plus des plantes plus tot des vaches et que tu veux changer le nom de la notification tu sera obligé de le modifier par tout, par contre si tu la déclare comme constante tu la modifier une seule fois dans tous le programme.


     


    2. [[[[self view] superview] window] windowController]] pas beau a voir. la majorité utilise le dot (.) notation pour les getter/setter. 


     


    3. Et y a aussi le faite que probablement les notification ne sont pas appropriée pour ton cas, comme expliqué encore par @AliGator.


     


    Si j'était toi, je resterais sur une solution qui marche pour l'instant et je regarderais la documentation en meme temps sur les Notification center, pattern délégation,..

  • AliGatorAliGator Membre, Modérateur
    septembre 2013 modifié #11

    Je crois avoir trouvé une solution.
    [...]
    Est-ce une programmation correcte ?

    Clairement non.

    Encore une fois, RTFM

    - Comparaison inutile avec object
    - Emplacement illogique de removeObserver
    - Manque d'utilisation de constantes
    - Mais surtout surtout surtout, c'est toujours super pas logique d'utiliser des Notifications pour ça

    Puisque tu récupères déjà  ton objet avec [[[[self view] superview] window] windowController] :
    • Pourquoi ne pas appeler [[... window] close] sur cet objet directement, quel intérêt de passer par des notifications ?
    • En plus tu as un chemin bien compliqué pour accéder à  la window que tu veux fermer... déjà  window.windowController.window c'est pareil que window, et view.superview.window c'est forcément la même que view.window... Donc au final au lieu de [[[[[self view] superview] window] windowController] window] autant utiliser self.view.window c'est quand même 10x plus court et évite des appels inutiles !
    Bref, tu te prends bien la tête pour pas grand chose, comme déjà  dit plus haut tu ne pars pas sur la bonne solution et les bons outils (notifs) dès le départ... Alors que tu remplaces tout ton boxon des notifs & co par la simple ligne [self.view.window close] et le tour est joué... Mais bon pourquoi faire simple... ^^
  • Je n'ai pas réussi à  faire comprendre mon problème.


     


    Voici ce que je veux obtenir :


    http://nadot.net


     


    Chaque fenêtre correspond à  une fonctionnalité.


    Je veux pouvoir afficher à  l'écran plusieurs fenêtres ”plantesListe" afin de comparer les photos de plusieurs plantes.


    Je veux pouvoir effacer une fenêtre sans effacer les autres.


     


    Par  exemple, la  fenêtre "plantesListe" comporte selon l'étape (subview est un raccourci pour subView de contentView) :


    - étape1 :


        - subview "entête"


        - subview "selectPlante"


        - subview "pied"


    - étape2 :


        - subview "entête"


        - subview "planteSelected"


        - subview "pied"


    - étape3 :


        - subview "entête"


        - subview "planteSelected"


        - subview "listePhotos"


        - subview "pied"


    Les subviews sont toujours les mêmes à  chaque étape.


    Par exemple, "pied" comporte trois boutons visibles ou non selon des événements comme action sur un bouton "pied" à  l'étape précédente ou existence de photos pour une plante donnée.


     


    Les subviews "entête" "listePhotos" "planteSelected" "pied", etc. seront utilisées dans d'autres fenêtres comme "photosListe", etc.


     


    Cela me permet de peaufiner l'apparence de chaque subview, d'avoir des apparences identiques dans les différentes fenêtres, au lieu d'avoir une foultitude de xib, dont il est pratiquement impossible qu'ils soient homogènes.


     


    La comparaison avec objet permet d'effacer une instance et pas toutes en même temps.


    C'est parce que je passe par des subviews de contentView que je dois écrire  [[[[self view] superview] window] windowController].


    Quant à  la notation pointée, je veux bien l'adopter s'il y a une raison autre que de coutume d'écriture.


    Pour removeObserver c'est un reste des nombreux essais.


    Quant aux constantes, quel est l'intérêt dans mon cas ?


     


    Pour passer des infos d'une subview à  l'autre, je suis preneur s'il y a d'autre techniques que les notifications. Sans doute, je "ne pars pas sur la bonne solution", mais quelle est la bonne solution ? Où la doc dit-elle comment choisir la bonne solution ?


     


    Merci.

  • CéroceCéroce Membre, Modérateur

    OK, avant de te donner une solution, il me reste des choses à  comprendre:


    Dans l'étape 1, on choisit la plante.


    Alors à  quoi servent les étapes 2 et 3 ? Je veux dire, on a déjà  sélectionné la plante, alors on peut corriger son nom après ? Pareil pour la 3, on peut afficher la photo dès la plante sélectionnée.


     


    Enfin, voici quand même un début de solution. Il faut utiliser le design pattern MVC. En gros, tu auras un objet Modèle qui conservera les infos sur la plante. Quand on passe d'une étape à  l'autre, il faudrait passer cet objet Plante à  chaque view controller avant qu'il s'affiche, afin qu'il remplisse les champs.


  • Mon modèle c'est l'ensemble des méthodes qui me permettent d'accéder à  une base de données PostgrSQL sur un serveur distant (Cette base de données doit pouvoir servir à  d'autres applications, par exemple un site web).


    À chaque subview est associé un viewController, qui fait l'interface entre la subview et le modèle. Je crois que c'est bien ça le design pattern MVC ?


     


    J'avais commencé par une window et son windowController pour chaque fonctionnalité : ListePlantes, ListePotos, AjoutPlante, AjoutPhoto, AjoutLiaisonPlantePhoto, etc. avec un xib pour chacune.


    C'était très simple, mais il était très difficile d'avoir des contentView homogènes entre elles et très difficile par exemple d'ajouter la famille de la plante dans les multiples contentView, avec des sources d'erreur en nombre indéfinies.


     


    D'où l'idée d'une seule subview et son viewController pour chaque portion d'affichage : sélection d'une plante par son nom, affichage du nom commun et du nom latin de la plante, affichage d'une liste de photos d'une plante, etc.  avec des données extraites de la base de données.


    Ensuite pour chaque fonctionnalité j'ajoute les subview utiles, dans l'ordre adéquat, à  la contentView de la window.


    Si je veux ajouter la famille de la plante, je n'ai à  modifier que la seule subView "planteSelected".


     


    J'aurais dû donner comme exemple deux fonctionnalités :


     


                             ListePlantes                            ListePhotos


    - étape1 :     - subview "entête"                   - subview "entête"


                          - subview "selectPlante"        - subview "listePhoto"


                          - subview "pied"                      - subview "pied"


     


    - étape2 :     - subview "entête"                   - subview "entête"


                          - subview "planteSelected"   - subview "listePhoto"


                          - subview "pied"                      - subview "planteSelected"


                                                                             - subview "pied"


     


    - étape3 :     - subview "entête"


                          - subview "planteSelected"


                          - subview "listePhotos"


                          - subview "pied"


     


    J'ai donc 5 subview : "entête",  "pied", "selectPlante", "planteSelected", "listePhoto" que je combine selon le besoin et que je contrôle avec ... leurs viewController .


     


    Mon gros problème est donc de passer des informations d'une vue à  l'autre, et d'une vue à  la l'instance de la window qui la possède.


    J'ai trouvé une solution avec des "notifications privées" qui font hurler Aligator, mais qui marchent pour moi.


    J'ai essayé [[[[self view] superview] window] windowController] qui marche,  mais


    [[[[[self view] superview] window] windowController] methodeInstance] où methodeInstance est une méthode d'instance du windowController ne marche pas  !


     


    Quelle serait donc une solution propre et conforme au standard de programmation Cocoa ?


  • Pas de réactions ?


     


    Je vais donc continuer à  utiliser ma technique "horrible"  des notifications privées,


    faute d'une solution "propre".


    Et après tout, à  quoi sert le paramètre userInfo ? Ne peut-il servir à  fournir l'instance


    de l'émetteur de la notification ?

  • samirsamir Membre
    septembre 2013 modifié #16


    Et après tout, à  quoi sert le paramètre userInfo ? Ne peut-il servir à  fournir l'instance


    de l'émetteur de la notification ?




    Ben non c'est pas ça le principe des notifications. Si tu passe en paramètre l'objet, dans ce cas tu vas avoir un couplage fort entre l'émetteur et le récepteur. 


     


    Relis la doc sur les notification sinon mes colègues t'ont bien expliqué le pricipe et quand utiliser les notifications.




    Un des autres avantages des notifs c'est que tu peux ainsi découpler l'appelant et l'appelé puisque celui qui crie le message "Je veux fermer les fenêtres" s'en fiche de savoir qui écoute, et celui qui écoute s'en fiche de savoir qui a crié. 



  • AliGatorAliGator Membre, Modérateur

    Pas de réactions ?

    Bah si on t'a déjà  tout expliqué mais tu as l'air têtu...
  • CéroceCéroce Membre, Modérateur

    Pas de réactions ?

    Non, c'est l'apathie la plus complète.
     

    Et après tout, à  quoi sert le paramètre userInfo ?

    Tu mets ce que tu veux dedans. En général, ce sera un NSDictionary.

    Ne peut-il servir à  fournir l'instance
    de l'émetteur de la notification ?

    Non. Il y a -[NSNotification object] pour ça.
  • AliGatorAliGator Membre, Modérateur

    J'ai l'impression que malgré nos insistances il n'a pas RTFM... car tout ça c'est expliqué encore une fois dans les liens cités...


  • J'ai bien compris que l'usage que je fais des notification est absurde.


    Ce n'est pas la peine de me le rappeler à  chaque fois.


    Je suis prêt à  abandonner les notifications "privées".


     


    Ma demande est simple : serait-il possible que quelqu'un me fournisse l'idée d'une solution "propre" ?


    À la demande de Céroce, j'ai tenté d'expliquer ce que je veux faire. Je ne sais pas


    en dire plus.


     


    Et pourquoi


        [[[[[self view] superview] window] windowController] maMethodeInstance]


    ne marche pas ?


  • AliGatorAliGator Membre, Modérateur

    Bah t'as débugué un peu ? Que valent les valeurs intermédiaires ?! J'ai oublié ma boule de cristal moi...




  • Bah si on t'a déjà  tout expliqué mais tu as l'air têtu...




    Pas tout !


    On m'a expliqué la nature des notification et que j'en faisait mauvais usage.


    Mais on ne m'a pas expliqué comment résoudre mon problème !


    Et oui, c'est vrai, j'aime comprendre et je suis têtu...



  • Bah t'as débugué un peu ? Que valent les valeurs intermédiaires ?! J'ai oublié ma boule de cristal moi...




    Réponse du compilateur :


    No known instance method for selector maMethodeInstance


    Alors que maMethodeInstance est bien reconnue autrement !

  • Si ça intéresse encore quelqu'un,


    voir mon application:


        http://github.com/mybofy/Herbier

  • AliGatorAliGator Membre, Modérateur


    Réponse du compilateur :


    No known instance method for selector maMethodeInstance


    Alors que maMethodeInstance est bien reconnue autrement !




    Ahhhh ben voilà , déjà  c'est plus clair ! Et du coup bah oui quand on y pense c'est normal.


    (Si tu nous avais dit tout de suite le message d'erreur que tu avais du compilateur ça aurait facilité les choses...)


     


    Tant que tu n'avais pas dit ça moi je pensais vu tes messages ("... ne marche pas") que ça compilais sans rien dire mais que ça n'appelais pas la méthode (laissant présager que qqch dans le chemin d'appel retournait "nil"), ma boule de cristal ne m'ayant pas permis d'en savoir plus...


     


    Alors que là  le message est clair. Tu appelles maMethodeInstance sur un objet dont le type est le type de retour de la méthode windowController, autrement dit NSWindowController. Or NSWindowController n'a pas de telle méthode.


    (Du coup évidemment la solution est de caster ou bien d'utiliser une variable intermédiaire correctement typée)

  • Rien compris.


     


    Les protocoles, vous connaissez ?


     


    J'ai ma solution et cela va te plaire : il n'y a plus de notifications "privées".


     


    Grand merci à  tous


  • samirsamir Membre
    septembre 2013 modifié #27


    Rien compris.




     


    Analyse bien ta ligne de code et tu comprendra tout seul :).


     [[[[[self view] superview] window] windowController] maMethodeInstance]  (1).


     


    [self view] : Retourne un objet de type NSVView, donc on peux faire comme ça : NSView *currentView = [self view]; (2)  ( doc dans NSViewController : - (NSView *)view.).


     


    Remplaçons (2) dans (1) ça donne :


    [[[[currentView superView] window] windowController] maMethodeInstance] (3).


     


    [currentView superView] :  Retourne la superView ( celle qui contient currentView) qui est de type NSIView. On puex la remplacer par NSView  *mySuperView = [currentView superView];(4)  ( Doc dans NSView : - (NSView *)superview).


     


    Remplaçon (4) dans (3) ça donne :


    [[[mySuperView window] windowController] maMethodeInstance];


     


    Tu fait la meme chose pour le reste ( regarde la doc de NSView et NSWindow).


    .....


     


    a la fin tu vas arriver a : 


     


    [id  maMethodeInstance]; et c'est la que tu a l'erreur, le compilateur te dit que id ne connait pas la méthode maMethodeInstance, donc @Aligator te dit de caster 'id' vers ton controlleur qui connait maMethodeInstance.


    j'espère que c'est clair.


     




    Les protocoles, vous connaissez ?




     


    Yep. Je pense que les gens qui t'ont répondu connaissent bien les protocoles ;)


     




    J'ai ma solution et cela va te plaire : il n'y a plus de notifications "privées".




     


    ben super, ça fait du bien quand on y arrive comme ça et en meme temps on comprends pas mal de choses ( les notifications, quand les utiliser, avantages,...).




  • Analyse bien ta ligne de code et tu comprendra tout seul :).


     [[[[[self view] superview] window] windowController] maMethodeInstance]  (1).


     


    [self view] : Retourne un objet de type NSVView, donc on peux faire comme ça : NSView *currentView = [self view]; (2)  ( doc dans NSViewController : - (NSView *)view.).


     


    Remplaçons (2) dans (1) ça donne :


    [[[[currentView superView] window] windowController] maMethodeInstance] (3).


     


    [currentView superView] :  Retourne la superView ( celle qui contient currentView) qui est de type NSIView. On puex la remplacer par NSView  *mySuperView = [currentView superView];(4)  ( Doc dans NSView : - (NSView *)superview).


     


    Remplaçon (4) dans (3) ça donne :


    [[[mySuperView window] windowController] maMethodeInstance];


     


    Tu fait la meme chose pour le reste ( regarde la doc de NSView et NSWindow).


    .....


     


    a la fin tu vas arriver a : 


     


    [id  maMethodeInstance]; et c'est la que tu a l'erreur, le compilateur te dit que id ne connait pas la méthode maMethodeInstance, donc @Aligator te dit de caster 'id' vers ton controlleur qui connait maMethodeInstance.


    j'espère que c'est clair.


     


     


     




     


     


     


    Je ne vois pas de différence entre


      [[[[[self view] superview] window] windowController] maMethodeInstance]


    et


        [[[mySuperView window] windowController] maMethodeInstance]


    Sauf que pour éviter deux couples de crochets on rajoute deux lignes de code sans utilité réelle.


    Je n'aurais pas pu écrire cette ligne de code sans avoir lu et relu NSView, NSViewController ou NSWindow.


    C'est un peu facile de dire qu'il suffit de lire la doc à  quelqu'un qui l'a lue et relue avant de poster.


     


    Par contre je ne comprends pas


        [id  maMethodeInstance]


    Si j'écris ça quelque part le compilateur va hurler.


    De plus dans self, le controller où se trouve  maMethodeInstance n'est pas visible. Je ne vois pas comment caster n'importe quoi avec quelque chose qui n'est pas visible !


     


    D'où mon utilisation d'un protocole associé au controller qui contient maMethodeInstance, protocole où celle-ci est déclarée et qui rend cette méthode visible dans


           [[[[[self view] superview] window] windowController]  maMethodeInstance]


    ce qui est finalement assez conforme avec ce qui est pratiqué dans les classes standard pour gérer par exemple les clics de la souris.


     


    Où est le problème ?

  • Je ne t'ai pas répondu pour te donner une solution, mais pour analyser ton code et t'expliquer pour quoi ça compile pas et plus important par ce que t'a répondu au message de @Aligator par : "Rien compris", de coup j'ai essayé de t'expliquer ce que t'a fait comme erreur.  


     




    Je n'aurais pas pu écrire cette ligne de code sans avoir lu et relu NSView, NSViewController ou NSWindow.


    C'est un peu facile de dire qu'il suffit de lire la doc à  quelqu'un qui l'a lue et relue avant de poster.




     ben si tu l'avait bien lu, t'aurais compris que la méthode d'instance window de NSWindow renvoi un objet type 'id', donc l'apple de


    [id myInstanceMethode] ne passera pas :)


     


     




    D'où mon utilisation d'un protocole associé au controller qui contient maMethodeInstance, protocole où celle-ci est déclarée et qui rend cette 




    Très bonne solution. 


     


     




    Où est le problème ?




     


    Y a aucun problème puisque t'a bien résolu ton erreur, ou bien tu parles d'autre chose ?


Connectez-vous ou Inscrivez-vous pour répondre.