Attendre le retour d'une action

laudemalaudema Membre
04:54 modifié dans API AppKit #1
Bonjour,

Pour essayer de faire un peu mieux les choses je gère désormais une liste de personnes via un PersonnesManager. C'est un singleton qui a un tableau de personnes et peut ouvrir une fenêtre où il est possible de double-cliquer sur une ligne d'une NSTableView afin de sélectionner une des personnes de la liste.
Ce que je voudrais faire c'est ajouter une seule méthode à  mon PersonnesManager, quelque chose comme
<br />Personne *p = [[PersonnesManager sharedManager] selectOneFromList];<br />

Que je pourrais apeller de n'importe quel endroit où j'ai besoin d'une personne dans la liste.

Pour l'instant ma méthode selectOneFromList ne fait pas grand chose d'autre que d'appeler ListeController qui est un NSWindowController chargé d'afficher la liste.
<br />- (Personne *)selectOneFromList{<br />&nbsp; &nbsp; if (! listeController){//listeController est une variable d&#039;instance<br />&nbsp; &nbsp; &nbsp; &nbsp; listeController = [[ListeController alloc] initWithWindowNibName:@&quot;ListePersonnes&quot;];<br />&nbsp; &nbsp; &nbsp; &nbsp; listeController.personnes = managerPersonnes; //Tableau des personnes<br />&nbsp; &nbsp; &nbsp; &nbsp; [listeController.window makeKeyAndOrderFront:self];<br />&nbsp; &nbsp; }<br />}<br />

C'est une autre méthode, dans ListeController, qui est chargée de récupérer le double-clic, méthode configuré via les bindings pour envoyer le tableau selectedObjects du NSArrayController qui fournit les données à  la NSTableView
<br />- (void)doubleClickInTableView:(NSArray*)selected{<br />&nbsp; &nbsp; if (! ([selected count]==1)) {<br />&nbsp; &nbsp; &nbsp; &nbsp; printf(&quot;il devrait y avoir une et une seule personne sélectionnée&#092;n&quot;);<br />&nbsp; &nbsp; }<br />&nbsp; &nbsp; else{<br />&nbsp; &nbsp; &nbsp; &nbsp; printf(&quot;Personne sélectionnée: %s&#092;n&quot;,[((Personne*)[selected lastObject]).fullName UTF8String]);<br />&nbsp; &nbsp; }<br />}<br />

Et ça marche très bien avec un effort minimal jusque là  :)
Sauf que, évidemment, la méthode qui a lancé l'affichage de la liste est finie depuis longtemps.
Ce que je peux faire maintenant c'est ajouter une variable d'instance à  PersonnesManager pour garder la trace de qui a appelé (puisque le principe est de pouvoir appeler de plusieurs endroits où je pourrais avoir besoin de choisir une personne dans la liste).
Ensuite je n'aurais plus qu'à  informer l'appelant pour l'informer du changement (ou utiliser les bindings quelque part).

Mais ça me semble plutôt lourd. Surtout que le besoin est ponctuel : quand il a besoin de choisir dans une liste l'utilisateur l'appelle et dès que c'est fini passe à  autre chose.
C'est ce qui m'a fait penser à  performSelector:onThread:withObject:waitUntilDone:
Mais je ne vois pas trop quelle méthode écrire, probablement dans listeController que j'appellerais depuis PersonnesManager une fois que la fenêtre est affichée
<br />[listeController performSelector:@selector(methodeAEcrire) onThread:mainThread waitUntilDone:YES];<br />//Et ensuite ?<br />

Quelqu'un pourrait il m'aider à  aller un peu plus loin ?
La majorité de la documentation que j'ai pu lire concerne l'usage d'un thread supplémentaire afin de ne pas bloquer l'application ce qui n'est pas mon but dans le cas présent.

Ou vaut il mieux utiliser une Sheet et gérer tout ça avec ?

Merci de vos avis



Réponses

  • AliGatorAliGator Membre, Modérateur
    04:54 modifié #2
    Crée toi un delegate. Ou un couple target/action au choix (ce qui te permettrait de choisir en plus la méthode appelée au retour plutôt que de voir le nom de cette méthode figé par le protocole). Ou mon préféré (mais à  condition que tu cibles un OSX compatible), utilise les blocks.

    [[PersonnesManager sharedManager] selectOneFromListWithCompletion:^(Personne* p) {<br />&nbsp; NSLog(@&quot;Personne sélectionnée : %@&quot;,p);<br />}];
    
    Et dans l'implémentation de selectOneFromListWithCompletion tu mémorises le block passé en paramètre dans une variable d'instance le temps d'afficher ta liste et que l'utilisateur sélectionne une personne, et sur ton code de double-clic une fois la personne choisie dans la liste, tu appelles ce block que tu avais mis de côté.

    Si tu ne veux pas utiliser les blocks mais préfère utiliser les delegates, c'est pareil. Tu passes l'objet qui doit servir de delegate en paramètre de ta méthode, tu le mémorises dans ton PersonnesManager, et au double-clic tu appelles une méthode (que tu auras définie via un @protocol que tu auras créé pour l'occasion et auquel ton delegate devra se conformer) sur ce delegate pour lui signaler la personne choisie (en gros tu appelles [tt][delegate didSelectPerson:p];[/tt] dans ton PersonneManager... et l'appelant n'a plus qu'à  implémenter [tt]-(void)didSelectPerson:(Personne*)p { ... }[/tt])

    Si tu préfères pouvoir choisir la méthode appelé en retour, c'est encore le même principe (du coup ça s'apparente au mécanisme de target/action) où tu mémorises le target et l'action (@selector) et les appelle plus tard (le fameux [tt]performSelector[/tt] dont tu parlais plus haut.



    Attention à  bien séparer les parties du MVC et à  mémoriser ton block, ou ton delegate, ou ton couple target/action à  appeler dans ton ListeController (et pas dans ton PersonnesManager qui est un singleton). Pense qu'il peut tout à  fait être envisageable que tu appelles ta méthode selectOneFromList alors qu'il a déjà  été appelé une fois à  côté (surtout si ton ListeController n'est pas modal), du coup A appelle la méthode, affiche la liste, et B appelle aussi la méthode avant que l'utilisateur n'ait choisi une personne le premier coup, il ne faudrait pas que A ne reçoive jamais rien et attende, pour continuer son code, une réponse qui n'arrivera jamais si elle est envoyée à  B à  la place (si tu as remplacé la valeur de ta variable d'instance qui stocke le block/delegate/target+action par les valeurs passées par B et donc que celles de A sont écrasées)

    Bref tu peux avoir un singleton côté modèle, pour garder une liste de personnes accessible depuis toute ton application, par contre pour l'UI ne fait pas de singleton et pense que tu peux avoir plusieurs ListeController (remarque tu as déjà  une protection avec ton [tt]if (!listeController)[/tt] mais est-ce la bonne façon de faire ?

    Moi j'aurais plutôt surchargé un constructeur de commodité pour ListeController qui fait les 3 lignes que tu fais, à  savoir [tt]+(id)showListWithPersonnes:(NSArray*)list;[/tt] qui fait le alloc+init+autorelease puis affecte la propriété personnes, puis appelle makeKeyAndOrderFront. Et appeler [tt][ListeController showListWithPersonnes:[PersonnesManager sharedManager].personnes][/tt] plutôt comme ça le code appelant a la main sur le ListeController (et peut fixer son delegate, et tout) et tu as bien des ListeController indépendants et une belle séparation Modèle (ton PersonnesManager) et Vue/Controller (ListeController + sa Window).
  • laudemalaudema Membre
    04:54 modifié #3
    dans 1322670339:

    Au double-clic tu appelles une méthode (que tu auras définie via un @protocol que tu auras créé pour l'occasion et auquel ton delegate devra se conformer) sur ce delegate pour lui signaler la personne choisie (en gros tu appelles [tt][delegate didSelectPerson:p];[/tt] dans ton PersonneManager... et l'appelant n'a plus qu'à  implémenter [tt]-(void)didSelectPerson:(Personne*)p { ... }[/tt])

    Merci de m'avoir rappelé cette solution @protocol que je connais (quand je numérise avec un scanner en utilisant une IKScannerDeviceView) et qui ira vite à  faire finalement :)

    dans 1322670339:

    Moi j'aurais plutôt surchargé un constructeur de commodité pour ListeController qui fait les 3 lignes que tu fais, à  savoir [tt]+(id)showListWithPersonnes:(NSArray*)list;[/tt] qui fait le alloc+init+autorelease puis affecte la propriété personnes, puis appelle makeKeyAndOrderFront.

    Je ne sais plus pourquoi je préfère ne pas surcharger ces méthodes comme initWithWindowNibName, je crois que c'est parce qu'on a pas encore de référence à  la fenêtre (elle est à  nil tant qu'elle n'a pas été chargée ?). J'avais passé quelques heures là  dessus à  chercher, du coup j'ai pris la (mauvaise) habitude de faire mes affectations en dehors, une fois sorti des "initWith...:"

    Sinon l'appel à  la méthode ne devrait pas être lancé dans plusieurs endroits, c'est juste un besoin ponctuel le temps d'affecter une personne à  un objet (disons facture ou commande) ensuite l'utilisateur passe à  autre chose. D'un autre côté il ne faut pas injurier l'avenir et donc se dire que ça peut arriver peut être un jour. Dans ce cas la méthode du protocole avec un délégué dans listController devrait marcher mais il faudra que je change personnesManager pour qu'il puisse lancer plusieurs listeController qui se fermeront après usage (pour l'instant je fais [listController.window orderOut] et la garde sous le coude).
Connectez-vous ou Inscrivez-vous pour répondre.