NSSavepanel et modaldelegate

tabliertablier Membre
23:25 modifié dans API AppKit #1
je souhaite gérer la sortie d'un "savepanel" par "beginSheetForDirectory:..." Le n'arrive pas à  repasser le context:
- (NSArray *)saveItem:(NSString *)leChemin :(NSString *)leMessage :(NSString *)leTitre :(NSString *)leLabel :(NSString *)letype<br />{<br />int			retour ;<br />NSSavePanel		*sauvePanel ;<br />NSString		*filepath, *lefichier ;<br />BOOL			leContexte[5] ;<br /><br />	leContexte[0] = YES ; leContexte[1] = NO ; leContexte[2] = NO ; leContexte[3] = YES ; leContexte[4] = NO ; <br />	<br />	filepath =&nbsp; [leChemin stringByDeletingLastPathComponent] ;<br />	lefichier = [[[leChemin stringByDeletingPathExtension] stringByAppendingPathExtension:letype] lastPathComponent] ;<br />	<br />	if (!k2)<br />	 {<br />		sauvePanel = [NSSavePanel savePanel] ;<br />		[sauvePanel setCanSelectHiddenExtension:YES] ;					<br />		[sauvePanel setCanCreateDirectories: k0] ;<br />		[sauvePanel setDirectory:leChemin] ;<br />		[sauvePanel setMessage:leMessage ];<br />		[sauvePanel setTitle:leTitre ];<br />		[sauvePanel setNameFieldLabel:leLabel] ;<br />		[sauvePanel setRequiredFileType: letype] ;<br />	<br />		[sauvePanel beginSheetForDirectory:filepath&nbsp; file:lefichier modalForWindow:nil modalDelegate:(self) <br />				didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:leContexte ] ;<br /><br />		filepath = [sauvePanel filename];					// Prise du nom<br />			<br />		.................................................................etc<br /><br />&nbsp; &nbsp; return [NSArray arrayWithObjects: .............................,&nbsp; nil]&nbsp; ;<br />}<br /><br />- (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode&nbsp; contextInfo:(void&nbsp; *)contextInfo<br />{<br />BOOL&nbsp; *Kontext, truc[5] ;<br /><br />	Kontext = (BOOL *)contextInfo ;<br />	truc[0] = *Kontext++ ; truc[1] = *Kontext++ ; truc[2] = *Kontext++ ; truc[3] = *Kontext++ ; truc[4] = *Kontext++ ;<br />	<br />	.................................. etc<br />}<br />


Si cela marchait comme c'est expliqué dans la doc, truc devrait contenir YES,NO,NO,YES,NO. Or il contient YES,YES,YES,YES,YES.
Je constate que Kontext contient bien l'adresse de leContexte, c'est à  dire que l'adresse de l'array est bien transmise, mais que le contenu a disparu!! J'ai donc relus la doc sur la gestion de la mémoire et les réservations, je ne trouve pas ou est mon erreur!!
Allons jusqu'au bout: ces mèthodes font parti d'un objet alloué et initialisé sous AppleScript Studio par
if (deleguer = missing value) then
set deleguer to (call method "initialise" of object (call method "alloc" of class "deleguer"))
end if
puis saveItem: est appelé par un call method

Quelqu'un aurait une idée de l'erreur que je fait? (ou de ce que je n'ai pas compris).


«1

Réponses

  • schlumschlum Membre
    avril 2007 modifié #2
    Ton tableau "leContexte" défini dans la méthode "saveItem" n'existe plus à  la fin de celle ci.

    (passe le en variable de classe)
  • BruBru Membre
    23:25 modifié #3
    Il faut surtout expliquer à  Tablier que la méthode beginSheetForDirectory:file:modalForWindow:modalDelegate:didEndSelector:contextInfo: n'est pas bloquante. Cette méthode ne fait qu'afficher le savePanel, puis c'est tout.
    C'est donc, en effet, pourquoi le tableau leContexte[5]) n'existe plus au moment quand il tente de fermer le savePanel.

    Plus qu'une variable de classe, il peut aussi utiliser un objet (non-autorelease bien sûr) comme paramètre à  contextInfo.

    .
  • tabliertablier Membre
    23:25 modifié #4
    Merci de vos réponses.
    Effectivement la méthode n'est pas bloquante (je ne l'avais pas vu!) mais il y a quand même quelque chose que je ne comprend pas!  :-\\
    En C, si l'on appelle une routine B a l'intérieur d'une routine A, la routine A ne peut se terminer (sauf exception) tant que l'exécution de la routine B n'est pas terminée. Et les variables définies au début de A restent valables jusqu'à  son 'return' final: donc durant toute l'exécution de B.
    Vous avez l'air de dire qu'ici en Objective-C ce n'est pas le cas. Pourriez-vous être plus explicites ou me donner un lien sur la partie de la doc qui explique cela.
  • schlumschlum Membre
    23:25 modifié #5
    Ca s'appelle le multi-threading  ;)
    regarde du côté de NSThread
  • AliGatorAliGator Membre, Modérateur
    23:25 modifié #6
    Et il y a aussi que les méthodes sont soit synchrones, soit asynchrones (ceci dit pour que ce soit asynchrone il faut en effet plusieurs threads sinon ça n'a pas trop de sens).

    Dans tous les cas, si une méthode A appelle une méthode B, on revient puis termine la méthode A qu'une fois que la méthode B est terminée... Mais la méthode B n'est pas forcément bloquante pour autant.

    Tu peux par exemple avoir une méthode qui demande l'affichage d'un SavePanel, et te rend la main. Il faut bien attendre que la méthode qui affiche le SavePanel soit terminée, mais elle se termine une fois que le SavePanel a été affiché, sans attendre que l'utlisateur ait cliqué sur OK. Donc elle se termine très vite, et te rend la main dans ta méthode principale très vite.
    Plus tard, quand l'utilisateur a cliqué sur OK, le SavePanel appelle une autre méthode, pour t'indiquer que le panel s'est fermé. Mais c'est donc séparé de ton appel pour afficher le SavePanel.

    Donc dans un sens tu n'as pas tord, ça marche comme en C, il faut attendre que la méthode appelée soit terminée pour reprendre la main, mais il y a des méthodes qui se contentent d'initier l'action (afficher un SavePanel), et c'est tout, donc qui sont plus courtes que ce que tu pourrais penser. D'ailleurs tu peux tout à  fait faire ça en C aussi, du moment que tu utilises des threads aussi.
  • mai 2007 modifié #7
    dans 1177960081:

    Ca s'appelle le multi-threading  ;)
    regarde du côté de NSThread


    Dans le contexte évoqué ici, le multhreading c'est vraiment de l'overkill pour expliquer ce comportement: c'est de la programmation événementielle de base.

    Comme Ali l'a signalé, une méthode non bloquante comme celle-ci se contente d'afficher une fenêtre, et puis on rentre dans un cycle normal de gestion événement/attente d'événement (qui est le cas par défaut, pour avoir du bloquant lorsque des éléments d'interface comme ceux-ci sont impliqués, ça doit vraiment être intentionnel).

    Lorsque l'utilsateur pousse sur OK ou Annuler (bête paradigme action/target qu'on a tout le temps dans Cocoa), le Save Panel se contente d'appeller un sélecteur (qui peut-être stocké comme variable d'instance) sur un objet (qui peut-être lui aussi stocké de cette manière) - enfin, dans le cas présent c'est plutôt via une NSInvocation, mais ça ne change rien au principe.

    Pour tablier: c'est donc le genre de trucs que tu fais toi-même lorsque tu écris un programme. N'oublie pas que Apple est soumis aux mêmes règles que toi, et qu'ils seraient stupides de ne permettre que le cas le plus contraignant (qui est le bloquant ici), alors que ce qui est permis "naturellement" est beaucoup souple: le non-bloquant n'exclut pas le bloquant, mais la réciproque n'est pas vraie.

    Pour Ali: l'asynchrone a également tout son sens dans de la programmation événementielle mono threadée.
  • schlumschlum Membre
    23:25 modifié #8
    dans 1177974493:

    Et il y a aussi que les méthodes sont soit synchrones, soit asynchrones (ceci dit pour que ce soit asynchrone il faut en effet plusieurs threads sinon ça n'a pas trop de sens).


    J'ai failli parler de synchrone / asynchrone, mais je trouve ça embrouillant, car "synchrone" veut dire "Qui a lieu en même temps." et là  c'est l'idée inverse de ce qu'on veut faire passer.
  • schlumschlum Membre
    mai 2007 modifié #9
    dans 1177993505:

    dans 1177960081:

    Ca s'appelle le multi-threading  ;)
    regarde du côté de NSThread


    Dans le contexte évoqué ici, le multhreading c'est vraiment de l'overkill pour expliquer ce comportement: c'est de la programmation événementielle de base.


    Que veut dire "overkill" ?  ???
    Les threads sont à  la base de tout ça, je ne vois pas le problème ?

    Je rappelle quand même que la question était :

    En C, si l'on appelle une routine B a l'intérieur d'une routine A, la routine A ne peut se terminer (sauf exception) tant que l'exécution de la routine B n'est pas terminée. Et les variables définies au début de A restent valables jusqu'à  son 'return' final: donc durant toute l'exécution de B.
    Vous avez l'air de dire qu'ici en Objective-C ce n'est pas le cas. Pourriez-vous être plus explicites ou me donner un lien sur la partie de la doc qui explique cela
  • mai 2007 modifié #10
    On parle effectivement de la même chose: tablier pose une question et tente d'y répondre en faisant l'analogie avec un raisonnement "procédural" tel qu'il le connait en C et donc ne comprend pas "l'anomalie" qu'il observe. Seulement là , on parle d'un appli Cocoa, qui utilise un modèle événementiel: dans ce cas, une appli peut parfaitement rester mono threadée et arriver à  ce comportement (et donc l'envoyer sur la piste du multi-threading et des NSThread est une très mauvaise idée, à  mon sens en tous cas).

    Sinon, overkill = mise en oe“uvre de moyens beaucoup trop importants par rapport à  l'objectif à  atteindre (sous entendu, on peut le faire par les threads, mais inutile d'aller jusque là  pour faire la même chose).

    dans 1178025465:

    J'ai failli parler de synchrone / asynchrone, mais je trouve ça embrouillant, car "synchrone" veut dire "Qui a lieu en même temps." et là  c'est l'idée inverse de ce qu'on veut faire passer.


    Ou bien alors tu transformes légèrement ta définition par une définition "factuelle", et tu considères que le résultat d'une méthode/fonction synchrone est exploitable directement (que ce soit par la valeur renvoyée et/ou une valeur passée en référence), tandis que le résultat d'une fonction asynchrone est "communiqué" par un callback/méthode de délégué, et dans ce cas il devient clair qu'on a affaire à  de l'asychrone.
  • schlumschlum Membre
    mai 2007 modifié #11
    Euh... Une méthode dite "asynchrone" (que je trouve ce mot mal placé  :o) utilise bien un thread secondaire hein  ???
    Il suffit de se placer dans le debugger pour le voir.
    À partir du moment où tu as deux choses qui peuvent se faire en même temps, il y a multi-threading.

    Or sa question était basée là  dessus, donc je l'oriente vers la classe NSThread, ça me parait normal   :-\\

    S'il n'y avait pas multi-threading, la "callback" ne pourrait pas être appelée après que la fonction ne soit terminée...
  • 23:25 modifié #12
    Pas (forcément) dans le cas d'un modèle événementiel.

    Le "callback" peut très bien être appelé suite à  un événement bien précis (en l'occurence, dans le cas présent, suite au mouseUp sur le bouton OK ou Annuler du save panel, mais pour le processeur, il n'y a jamais qu'une seule chose à  la fois qui se passe et donc le multhreading est inutile pour ce genre de cas).
  • schlumschlum Membre
    23:25 modifié #13
    dans 1178028342:

    Pas (forcément) dans le cas d'un modèle événementiel.

    Le "callback" peut très bien être appelé suite à  un événement bien précis (en l'occurence, dans le cas présent, suite au mouseUp sur le bouton OK ou Annuler du save panel, mais pour le processeur, il n'y a jamais qu'une seule chose à  la fois qui se passe et donc le multhreading est inutile pour ce genre de cas).


    Dans ce cas c'est une méthode dite "asynchrone" ; elle est bloquante pour l'application.
    (ça existe... Toutes les fonction "beginModalSession...")

    Ici, si sa fonction "savePanelDidEnd" a été appelée après la fin de sa fonction "saveItem", c'est qu'il y avait forcément un second thread. C'est absolument impossible autrement (ou alors il faut m'expliquer...)
  • mai 2007 modifié #14
    Je simplifie et suppose que la fonction callback ne prend qu'un argument (comme je l'ai dit plus haut, il faudrait dans ce cas utiliser des NSInvocation, mais c'est plus compliqué à  écrire mais ne change rien au principe).

    Lorsque tu appelles [tt]beginSheetForDirectory:file:[...]contextInfo:[/tt], le save panel affiche le panneau, le mets au premier plan et  stocke dans des variables d'instance le modalDelegate et le didEndSelector. Lorsque l'utilisateur pousse sur OK, le code correspondant pour le save panel serait: [tt][_modalDelegate performSelector:_didEndSelector withObject:self];[/tt]

    Pas compliqué non?
  • schlumschlum Membre
    23:25 modifié #15
    Je comprends bien ce que tu essaies de me dire... Mais comment veux-tu qu'en faisant ce que tu dis, le processeur continue en même temps d'exécuter la suite de la fonction "saveItem" (au point qu'elle soit terminée quand le "didEndSelector" est appelé) ?  ;)
  • 23:25 modifié #16
    Si tu gères le savepanel à  la main, le call back sert juste à  dire que l'utilisateur a validé un chemin pour la sauvegarde (ou au contraire a changé d'avis), et donc c'est à  ce moment que tu dois appeller le "saveItem" toi même. Le save panel n'appelle jamais ce genre de fonction tout seul.
  • schlumschlum Membre
    mai 2007 modifié #17
    Baon... Rien ne vaut un petit exemple.

    Une application toute bête qui affiche un NSSavePanel ; tu me diras donc qu'elle n'est pas "multi-treadée"
    Place un break-point sur le NSLog de la première fonction, tu verras que ce n'est pas 1, mais 3 threads que tu as dans cette application.

    http://cschlum2.free.fr/TestThread.zip

    Les threads sont à  la base de tout ce qui est exécuté en même temps, donc ici c'est obligatoire. La fonction se termine en même temps que le NSSavePanel s'affiche !
  • mai 2007 modifié #18
    Ces threads additionnels sont utilisés pour des effets "visuels" tels que le déploiement de la sheet, l'animation du bouton Save et autres fantaisies dans le genre qui pour moi ne font pas partie du problème évoqué ici.

    D'ailleurs, modifie tes logs pour y ajouter l'info suivante: [NSThread isMultiThreaded]?@threaded:@not threaded

    Tu auras dans les 2 cas "not threaded".
  • schlumschlum Membre
    23:25 modifié #19
    An application is considered multithreaded if a thread was ever detached from the main thread using detachNewThreadSelector:toTarget:withObject:. If you detached a thread yourself using POSIX threads directly, this method could still return NO. The detached thread does not have to be running for an application to be considered multithreaded"it may have already exited.


    Ils n'utilisent pas la classe NSThread pour détacher leurs threads tout simplement.
  • mai 2007 modifié #20
    Tu es fatiguant.

    La question de base était comme tu me l'as rappelé:
    dans 1177959543:
    En C, si l'on appelle une routine B a l'intérieur d'une routine A, la routine A ne peut se terminer (sauf exception) tant que l'exécution de la routine B n'est pas terminée. Et les variables définies au début de A restent valables jusqu'à  son 'return' final: donc durant toute l'exécution de B.
    Vous avez l'air de dire qu'ici en Objective-C ce n'est pas le cas. Pourriez-vous être plus explicites ou me donner un lien sur la partie de la doc qui explique cela.


    Je réponds dans le cadre de cette question, et reste dans le cadre cette question.

    Maintenant si l'implémentation Tiger de l'AppKit crée 3 threads parce qu'il y a des animations, ou des observers divers pour que l'affichage du save panel reste à  jour, ou des threads qui chargent les icônes des fichiers, ou... (qui n'est plus qu'un nib qui est simplement chargé, au cas où tu l'aurais oublié). ça ne veut pas dire que les threads en questions sont utilisés dans la gestion des événements et du problème évoqué ici.
  • schlumschlum Membre
    23:25 modifié #21
    Je ne suis pas fatiguant, j'essaie de te faire comprendre que c'est impossible que la fonction ait poursuivi si le "beginSheetForDirectory" n'avait pas créé de thread.

    La question était justement de savoir pourquoi la fonction avait poursuivi après l'appel de "beginSheetForDirectory" et j'y répond, c'est tout.

    Explique moi, comment, sans thread, le processeur peut partir dans "beginSheetForDirectory", afficher la sheet et attendre que l'utilisateur clique sur un bouton et en même temps poursuit dans la fonction qui a appelé "beginSheetForDirectory".

    Pas la peine de dire que je suis fatiguant, on peut discuter calmement "en adultes" comme ça a été fait jusque là ...  ;)
  • schlumschlum Membre
    23:25 modifié #22
    dans 1177974493:

    Et il y a aussi que les méthodes sont soit synchrones, soit asynchrones (ceci dit pour que ce soit asynchrone il faut en effet plusieurs threads sinon ça n'a pas trop de sens).


    Voilà  ce que j'essaie de dire depuis tout à  l'heure...
  • tabliertablier Membre
    23:25 modifié #23
    Je rentre juste et je suis surpris de voir autant de désaccords entre-vous, bien que vous parraissiez en fait du même avis: des threads différents sont utilisés avec beginSheetForDirectory:.  Alors, si je comprend bien ce dont vous avez discuter et en restant pratique:
    -- Le programmeur ne peut pas savoir à  priori si les méthodes appellées (qui sont dans les frameworks) utilisent un (ou plusieurs) thread(s) différent(s) du thread principale de l'application.
    -- La seule maniére d'avoir le renseignement est de mettre des points d'arrêts aux endroits appropriés.
    Est-ce que je me trompe  :( ?

  • mai 2007 modifié #24
    Tu as quitté le cadre spécifique de la question pour venir sur un autre truc, qui est la manière dont Tiger gère ses sheets, ses save panels et compagnie. Alors oui, il crée un thread pour gérer l'animation et pour ne pas que la fonction soit bloquante sur le temps de l'animation, ça je ne nie pas, mais pour moi ça ne fait pas partie du problème.

    Maintenant l'appli là  où je ne te suis absolument pas est que l'appli n'attend pas que l'utilisateur clique sur OK et continue l'exécution de la méthode qui appelé beginSheetForDirectory en même temps. Elle termine l'exécution de la méthode qui a appelé beginSheetForDirectory et rentre dans un nouveau cycle normal de runloop vu que la dite fonction n'est pas bloquante. À part la question de l'animation, tout peut parfaitement se faire dans le même thread. Mais dans le cas où tu as une fonction bloquante, il faut effectivement un second thread (mais ce n'est pas le cas ici).
  • schlumschlum Membre
    23:25 modifié #25
    dans 1178035719:

    Je rentre juste et je suis surpris de voir autant de désaccords entre-vous, bien que vous parraissiez en fait du même avis: des threads différents sont utilisés avec beginSheetForDirectory:.  Alors, si je comprend bien ce dont vous avez discuter et en restant pratique:
    -- Le programmeur ne peut pas savoir à  priori si les méthodes appellées (qui sont dans les frameworks) utilisent un (ou plusieurs) thread(s) différent(s) du thread principale de l'application.
    -- La seule maniére d'avoir le renseignement est de mettre des points d'arrêts aux endroits appropriés.
    Est-ce que je me trompe  :( ?



    Non, les points d'arrêt n'apprennent rien ; sur ce point il a raison, les threads peuvent très bien être utilisés pour des routines d'affichage.
    Donc je ne vois pas comment montrer par l'exemple qu'il y a plusieurs threads, c'est simplement la logique qui le dit dans ce cas.
  • schlumschlum Membre
    23:25 modifié #26
    dans 1178036147:

    Tu as quitté le cadre spécifique de la question pour venir sur un autre truc, qui est la manière dont Tiger gère ses sheets, ses save panels et compagnie. Alors oui, il crée un thread pour gérer l'animation et pour ne pas que la fonction soit bloquante sur le temps de l'animation, ça je ne nie pas, mais pour moi ça ne fait pas partie du problème.


    Oui, je suis d'accord, le problème n'est pas l'animation ; oublions l'exemple foireux qui ne prouve rien donc.

    Le problème (sa partie intégrante même), c'était de savoir pourquoi la fonction continuait avant l'appel de la "callback" et la réponse à  ce problème c'est "Thread".
    Du coup, je ne vois pas pourquoi on s'énerve comme ça...

    Maintenant l'appli là  où je ne te suis absolument pas est que l'appli n'attend pas que l'utilisateur clique sur OK et continue l'exécution de la méthode qui appelé beginSheetForDirectory en même temps. Elle termine l'exécution de la méthode qui a appelé beginSheetForDirectory et rentre dans un nouveau cycle normal de runloop vu que la dite fonction n'est pas bloquante. À part la question de l'animation, tout peut parfaitement se faire dans le même thread.


    Supprimons l'animation ; imaginons qu'au lieu d'afficher un NSPanel l'application parte en boucle infinie en attendant que l'utilisateur tape "y".
    Comment veux-tu avec simplement un fonctionnement de callback il y ait deux choses de faites à  la fois ?
  • MalaMala Membre, Modérateur
    23:25 modifié #27
    Mais c'est qu'ils vont nous pêter une durite à  ce rythme là !  :)

    Alors multi-thread obligatoire ou non?  :brule:
    Aller je prends le parti de Renaud. Une application peut parfaitement être monothread et gérer ce genre de panneau. Dans une application graphique, le main thread n'est jamais qu'un gestionnaire d'événements. Rien ne l'empêche d'afficher une nouvelle fenêtre, un nouveau panneau ou tout ce qu'on veut dans une méthode prévue à  cet effet puis de revenir comme si de rien n'était à  sa boucle d'événements. La validation du panneau ne fera que générer un évènement dans le thread principal.

    dans 1178036473:

    Comment veux-tu avec simplement un fonctionnement de callback il y ait deux choses de faites à  la fois ?

    La méthode affiche le panel et continue. Je ne vois pas où on a forcément besoin d'un second thread.
  • schlumschlum Membre
    23:25 modifié #28
    dans 1178036708:

    dans 1178036473:

    Comment veux-tu avec simplement un fonctionnement de callback il y ait deux choses de faites à  la fois ?

    La méthode affiche le panel et continue. Je ne vois pas où on a forcément besoin d'un second thread.



    Et qui gère l'attente d'une action de l'utilisateur dans ce cas ? Dieu ?  :P

    Le processeur fait forcément deux chose à  la fois à  un moment... Attendre en boucle une réponse, et continuer la fonction précédente.
  • mai 2007 modifié #29
    Voilà , maintenant que j'ai pigé qu'on parlait pas de la même chose, il n'y avait effectivement pas lieu de s'énerver sorry. J'ai édité mon message pendant que tu tapais la réponse pour y ajouter une précision, qui répond justement à  ton interrogation.

    Dans le cas d'une fonction bloquante, oui, il faut un second thread. Dans le cas d'une fonction non bloquante, il ne faut pas de second thread: la méthode qui appelle beginSheetForDirectory continue son exécution normalement et un nouveau cycle de runloop commence. Simplement comme il y a une sheet sur la fenêtre, l'application ne fait pas suivre les événements qui auraient dus arriver à  la fenêtre parent, mais le cas évoqué n'a rien de modal (au sens NSApp du terme), et donc il n'est pas nécessaire de créer des threads en plus.
  • schlumschlum Membre
    23:25 modifié #30
    Hein, j'y comprend plus rien là Â  :p

    C'est quand la fonction est non bloquante qu'il faut un thread justement  :p

    Parce qu'à  ce moment, un nouveau thread s'occupe de la fonction appelée tandis que l'ancien poursuit la fonction qui l'a appelée
  • mai 2007 modifié #31
    Je ne parle pas en général, mais dans ce cas bien précis.

    Tu fais à  mon avis l'erreur de penser que beginSheetWithDirectory fait plus que ce qu'elle fait réellemement: elle ne fait qu'instancier une fenêtre et l'afficher, rien de plus.

    Dans le cas où tu as une fonction bloquante (genre une fenêtre modale justement), il faut créer un nouveau thread pour gérer les événéments de manière à  laisser le thread en cours arrêté.
Connectez-vous ou Inscrivez-vous pour répondre.