Utilisation d'une sheet qui se rafraichit

JijoJijo Membre
08:17 modifié dans API AppKit #1
Voilà  je sais créer une sheet qui s'ouvre et se ferme pendant un traitement cependant je ne sais pas comment actualiser les NSTextfield, NSimage et NSProgressIndicator au cours du temps.

Le sheet n'actualise pas les informations.

Merci

Voici une photo de ce que j'ai

Réponses

  • mpergandmpergand Membre
    08:17 modifié #2
    On va te répondre d'utiliser un NSThread !

    Mais, ce n'est pas la seule solution:
    http://www.objective-cocoa.org/forum/index.php?topic=3223.msg31816;topicseen#new
  • JijoJijo Membre
    08:17 modifié #3
    J'ai vraiment du mal a comprendre les NSThread et ta solution.

    Je voulais savoir si quelqu'un a des exemples simples , basics qui correspond à  mon cas.

    Merci
  • mpergandmpergand Membre
    janvier 2009 modifié #4
    dans 1232101514:

    J'ai vraiment du mal a comprendre les NSThread et ta solution.

    Je voulais savoir si quelqu'un a des exemples simples , basics qui correspond à  mon cas.

    Merci


    Si je comprends bien, tu charges un nombre connu d'images, donc tu peux faire:
    imageCount=12;&nbsp; <br />[self loadImage];<br /><br />............<br /><br />-(void) loadImage<br />{<br />	// charge une image<br />	// mise à  jour de l&#39;interface<br />	[textField setstringValue:@&quot;bla bla&quot;];<br />	// etc<br />	imageCount--;<br />	<br />	if(imageCount!=0)<br />		[self performSelector:@&quot;loadImage) withObject:nil afterDelai:0.1];<br />	<br />}<br />
    


    [Edit: projet ci-joint]
    (c'est bon là  Aligator ?)
  • AliGatorAliGator Membre, Modérateur
    janvier 2009 modifié #5
    dans 1232103132:

    [Edit: ajout exemple]
    Mdr, j'adore : dans ton post, il n'y a justement que l'exemple pour aider Jijo... y'avait quoi avant ton EDIT alors ?  :)

    [EDIT]Loool ouais c'est bon là  :P[/EDIT]
  • JijoJijo Membre
    08:17 modifié #6
    Je sais pas si c'est moi mais ça ne se rafraà®chit pas après chaque boucle.


    Sinon en thread cela est-il compliqué?
  • AliGatorAliGator Membre, Modérateur
    08:17 modifié #7
    Non pour le thread ce n'est pas compliqué en soi : si tu veux exécuter des actions dans un thread séparé, il suffit d'isoler bien dans une méthode donnée qu'on appellera par exemple [tt]-(void)loadImageThread:(id)argument[/tt] le traitement que tu veux faire, puis d'appeler ensuite [tt][NSThread detachNewThreadSelector: @selector(loadImageThread:) toTarget:self withObject:nil][/tt] et cela suffit.

    L'argument passé à  "withObject" sera passé en argument au sélecteur qu'on a choisi (ici j'ai mis nil mais tu pourrais mettre autre chose, qui sera alors passé en argument à  ton loadImageThread et que tu pourras alors utiliser dans ce thread). Evidemment le "target" correspond à  l'objet contenant ta méthode "loadImageThread", ici j'ai mis self mais si tu l'as mis dans une classe à  part il faut passer une instance de cette classe, ça va de soi.

    Après là  où il faut faire attention c'est juste si tu utilises des variables dans ton thread que tu peux utiliser aussi de l'extérieur du thread. C'est à  dire si tu as des variables qui pourront être accédées ou modifiées à  la fois depuis ton thread principal et ton thread loadImageThread. Dans ce cas il faut penser à  protéger ces variables avec des "@synchronized"; (ou ne pas mettre "nonatomic" pour les @property correspondantes situ utilises les propriétés d'Objective-C 2.0) pour éviter les accès concurrentiels à  ces variables qui sinon te feraient planter ton programme.
  • mpergandmpergand Membre
    08:17 modifié #8
    Exemple en thread:
    #import &quot;Controller.h&quot;<br /><br />#define COUNT 50<br />static int imageCount=COUNT;<br /><br />@implementation Controller<br /><br />-(void) updateInterface<br />{<br />	[progress setDoubleValue:((COUNT-imageCount)/(double)COUNT)*100];<br />	[text setIntValue:imageCount];<br />	<br />	if(imageCount==0)<br />	{<br />		[text setStringValue:@&quot;Terminé !&quot;];<br />		[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:2]];<br />		<br />		[NSApp endSheet:panel];<br />		[panel close];<br />	}<br />	<br />	<br />}<br /><br />-(void) loadImage<br />{<br />	<br />	while(imageCount--)<br />		{<br />	&nbsp; &nbsp; &nbsp;  // charger l&#39;image ici<br />		[self performSelectorOnMainThread:@selector(updateInterface) withObject:nil waitUntilDone:YES];<br />		}<br />}<br /><br />-(void) awakeFromNib<br />{	<br />	[window orderFront:self];<br />	[NSApp beginSheet:panel modalForWindow:window modalDelegate:nil didEndSelector:nil contextInfo:nil];<br />	<br />	<br />	[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];<br />	 <br />	<br />}<br /><br /><br />@end
    


    Ici c'est simple, y a pas de variable partagée, mais tu en auras peut-être, et comme le dit Aligator, bien faire attention à  ça !

    Dans l'exemple, il peut y avoir un doute sur imageCount, mais son accès est "atomic" (variable simple) et waitUntilDone:YES devrait éviter tout problème, normalement...
  • JijoJijo Membre
    08:17 modifié #9
    Merci pour ta réponse. J'ai trouvé un exemple en thread pour mon cas. Mais ta réponse m'a aider a comprendre.

    Il me reste à  voir avec instrument ou le moniteur d'activité quand on met annuler si  le thread est détruis. En tout cas en utilisant [NSThread exit]  cela fait planter mon appli. Mais bon sans le détruire cela fonctionne même si cela n'est pas propre d'avoir un thread en activité qui sert a rien.

    En tout cas merci.
  • mpergandmpergand Membre
    janvier 2009 modifié #10
    Et voila, les ennuis commencent  :)
    + (void)exit

    Invoking this method should be avoided as it does not give your thread a chance to clean up any resources it allocated during its execution.


    Ce n'est pas comme ça que l'on fait pour terminer un thread !

    Mon conseil, comme tu sembles ne pas vraiment maà®triser le sujet, est d'éviter absolument les threads !!!
    De plus dans ton cas, ça ne sert à  rien, sauf à  avoir les pires problèmes, plantages aléatoires et j'en passe ...


    Reprends le 1er exemple, sans thread, que j'ai fait dans le projet "PanneauDeChargement".

    Après quand tu auras un programme fonctionnel, je te ferais une version avec thread, mais il ne faut pas griller les étapes.
  • AliGatorAliGator Membre, Modérateur
    08:17 modifié #11
    Le plus simple (mais là  justement il faut alors une variable partagée) c'est que dans ta boucle que tu mets dans ton thread, tu testes une variable booléenne genre [tt]BOOL cancelled[/tt] à  chaque itération de boucle (si [tt]cancelled == YES[/tt] alors tu arrêtes la boucle au même titre que si tu avais fini de charger toutes tes images quoi).
    Et du coup quand tu cliques sur ton bouton "Cancel" dans ta sheet window, il "suffit" de mettre cancelled à  YES pour que le thread s'annule, du moins le plus tôt qu'il pourra (il va finir le traitement en cours, c'est à  dire le chargement de l'image en cours, quand même, avant. Ce qui n'est pas plus mal pour être sûr de l'intégrité des données et que ton thread ne s'arrête pas en plein milieu après avoir alloué de la mémoire mais pas avoir eu le temps de la relâcher...)

    Par contre si tu utilises cette méthode classique pour annuler ton thread, il faut penser à  protéger l'accès à  cette variable partagée, pour que quand tu la lis dans ton thread et quand tu l'écris à  l'intérieur de ton thread il n'y ait pas de risque d'accès concurrentiel. Bon pour une variable booléenne c'est pas si dramatique car à  priori c'est atomique, mais quand même c'est bien de prendre les bonnes habitudes.


    Après pour faire ça il y a 2 possibilités :
    - soit tu mets ton BOOL cancelled directement dans ta classe qui gère ta sheetWindow et contient aussi ta méthode utilisée pour le thread de chargement des images... du coup les deux ont accès à  la variable "cancelled" directement normalement (faut juste utiliser les setter/getter de la variable, dans lesquelles tu auras mis un @synchronized(self), pour accéder à  la variable de façon thread-safe) ;
    - soit tu te crées une classe genre ThreadParam contenant ta variable cancelled (et éventuellement d'autres que tu voudrais partager entre ton code principal et ton thread), et tu passes une instance de cette classe en argument de ton thread (à  la place du "nil" de "withObject" quand tu crées ton thread). Comme ça à  la fois le code de ton sheet qui a connaissance de ton ThreadParam et le thread lui-même pourront accéder à  ces variables, en plus ça marche du coup aussi même si tu mets le code du thread dans une autre classe (target autre que "self") que celle qui gère ta sheetWindow et crée ton thread...


    Voilà  pour les pistes... En tout cas c'est toujours mieux d'utiliser un test booléen dans ton thread à  chaque boucle d'itération pour savoir s'il faut l'annuler que d'appeler [tt][NSThread exit][/tt]... qui en plus interrompt le thread courant, donc si tu fais appel à  cette méthode de classe depuis ton code qui gère la sheetWindow et donc depuis le main thread (et non pas depuis le thread que tu as créé pour charger tes images), tu vas interrompre le thread principal... d'où plantage  ;)
  • uocramuocram Membre
    08:17 modifié #12
    dans ce que j'ai vu sur les routes du web, il semble prudent d'utiliser une NSAutorealeasePool pour gérer la mémoire du NSThread et tester le BOOL cancelled à  chaque itération de boucle dans le genre :
    <br />-(void) loadImage<br />{<br />	NSAutoreleasePool	*pool = [[NSAutoreleasePool alloc] init];<br />	<br />	while(cancelled != YES &amp;&amp; imageCount--)<br />		{<br />	&nbsp; &nbsp; &nbsp;  // charger l&#39;image ici<br />		[self performSelectorOnMainThread:@selector(updateInterface) withObject:nil waitUntilDone:YES];<br />		}<br /><br />	[pool release];<br />}<br />
    


    Ainsi, pour quitter le thread en cours de route, il suffit de mettre cancelled à  YES ce qui sort de la boucle, stop le thread et nettoie la mémoire (il me semble).


  • AliGatorAliGator Membre, Modérateur
    08:17 modifié #13
    Tout à  fait uocram !
    C'est pour ça que je mentionnais qu'il fallait plutôt utiliser un booléen que l'on teste à  chaque itération, comme tu le fais si bien dans ton code, aussi pour être sûr que l'itération en cours se termine ce qui est mieux pour l'intégrité de la mémoire que de l'interrompre un peu n'importe où...

    Par contre j'avais totalement zappé l'AutoreleasePool, et tu as parfaitement raison il faut absolument l'ajouter !!
    En effet le thread principal a automatiquement son NSAutoreleasePool donc on ne s'en soucie en général pas, mais dans un thread par contre il faut en créer au moins une nous même ! Pour que tout ce qui est "autorelease" fonctionne (que ce soit un autorelease envoyé explicitement à  un objet dans le code du thread, ou sous le capot dans du code Apple dont tu ne vois pas le code) !
  • mpergandmpergand Membre
    janvier 2009 modifié #14
    Par contre j'avais totalement zappé l'AutoreleasePool, et tu as parfaitement raison il faut absolument l'ajouter !!

    Ou utiliser detachDrawingThread:toTarget:withObject: de NSApplication
    C'est pour ça que je mentionnais qu'il fallait plutôt utiliser un booléen que l'on teste à  chaque itération, comme tu le fais si bien dans ton code, aussi pour être sûr que l'itération en cours se termine ce qui est mieux pour l'intégrité de la mémoire que de l'interrompre un peu n'importe où...


    Pas besoin de bool, suffit de rajouter la méthode:
    -(void)cancel<br />{<br />&nbsp; imageCount=0;<br />}
    

     
    ;)
    [edit] et du coup y a une ambiguà¯té sur l'instruction imageCount--, faux mettre des @synchronize !


    Bon, on est en train de discuter de l'utilité d'un thread qui sert, en fait, à  mettre à  jour l'interface, cad à  revenir dans le main thread  :)

    Par contre, ce qui serait plus efficace, c'est de créer un thread par image à  charger !
    Je viens de jetter un oe“il sur les threads en 10.5, les choses on bien changer, en fait maintenant c'est comme en java, on dérive NSThread et tout ce passe dans main.

    Si je suis de bonne humeur, j'essayerais peut-être de faire un exemple, si c'est pas trop casse tête  :P
  • AliGatorAliGator Membre, Modérateur
    08:17 modifié #15
    Ben tu as plein de possibilités avec X.5 en fait :
    - Les threads avec NSThread. Pour les utiliser, on peut par exemple utiliser [tt]+detachNewThreadSelector:toTarget:withObject:[/tt], où tu peux préciser alors la méthode (@selector) à  appeler. Ou sinon on peut aussi sous-classer NSThread et surcharger la méthode "main". Les deux sont possibles, au choix.
    - Les threads posix, mais bon y'a pas grand intérêt pour toi
    - Les NSOperations et NSOperationQueue qui permettent d'empiler des tâches unitaires à  réaliser et de les laisser s'exécuter "quand le CPU aura le temps"...
    - Sinon y'a la solution de la RunLoop proposée plus haut en effet, avec laquelle je ne suis pas familier pour ma part ;)
    - Y'en a p'tet d'autres que je connais pas ?

    Bref, y'a que l'embarras du choix :P
    Un peu de lecture chez Apple.
  • mpergandmpergand Membre
    08:17 modifié #16
    Les NSOperations et NSOperationQueue qui permettent d'empiler des tâches unitaires à  réaliser et de les laisser s'exécuter "quand le CPU aura le temps"...


    As-tu une expérience dans ce domaine ? Est-ce approprié pour le chargement des images ?
  • AliGatorAliGator Membre, Modérateur
    08:17 modifié #17
    Malheureusement non je n'ai pas trop utilisé les NSOperations. En fait j'avais juste fait mumuse avec il fut un temps... mais en fait juste pour découvrir cette classe et voir un peu comment ça marchait, rien de plus. Donc je n'ai pas vraiment creusé et je ne les ai jamais utilisées dans un "vrai" projet.

    Ceci dit, si c'est par exemple pour charger une liste d'images dont on veut afficher la progression de chargement ou traitement, je pense que le NSThread avec une boucle sur les images est plus approprié. les NSOperations je vois plutôt ça pour du post-traitement par exemple, ou pour par exemple télécharger des images plus haute qualité, genre on affiche l'image basse qualité qu'on a en cache et on charge l'image haute qualité quand on a le temps, et quand elle arrivera il sera toujours temps de l'afficher à  la place de la vignette... Enfin c'est my two cents  ;)

    Après l'avantage que je vois des NSOperations c'est qu'on peut définir des dépendances entre les NSOperations, pour qu'une opération ne se lance que si une ou plusieurs autres ont été terminées précédemment... On peut aussi demander à  une opération de s'annuler (ce qui n'aura aucun effet si la tâche est déjà  lancée, mais la déprogrammera si elle n'est pas encore lancée)...  8--)

    Après, pour le cas qui nous intéresse ici, à  savoir juste charger une liste d'images, c'est pas utile d'utiliser des NSOperations, à  mon avis un simple [NSThread detachNewThreadSelector:... toTarget:... withObject:...] suffit amplement (ou le truc avec les NSRunLoops que j'ai jamais testé peut-être)  ;)
  • JijoJijo Membre
    janvier 2009 modifié #18
    Voilà  cela marche correctement. Le hic c'est que dans mon cas il y a des traitements lourds. A savoir que l'upload de l'image peu mettre plusieurs secondes ou plus. Dans mon cas si l'utilisateur annule lors de l'upload d'une photo il doit attendre la fin de l'upload. Je sais pas si  je pourrais faire un thread pour l'upload et comme cela en cas d'annulation le thread serai killer et on aurai plus besion d'attendre. 

    for(i=0; cancelUpload==NO && i<ImageCount; i++)
    {
    [self performSelectorOnMainThread:@selector(updateInterface:) withObject:progressInfo waitUntilDone:YES];

    if(cancelUpload==NO)//1
    {
    // traitements long
    [obj upload:session Eimage:image Efilename:filename];

    if(cancelUpload==NO)//2
    {
    [obj processing:session Eupload:upload Efilename:filename];
    }// fin 2

    }// fin 1

    }// fin for

    // fermeture de la fenêtre sheet

    NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1;

    [NSApp endSheet:progressPanel];
    [progressPanel close];


    En tout cas merci à  vous car j'ai vraiment avancé.
  • mpergandmpergand Membre
    08:17 modifié #19
    dans 1232267325:

    Voilà  cela marche correctement. Le hic c'est que dans mon cas il y a des traitements lourds. A savoir que l'upload de l'image peu mettre plusieurs secondes ou plus. Dans mon cas si l'utilisateur annule lors de l'upload d'une photo il doit attendre la fin de l'upload. Je sais pas si  je pourrais faire un thread pour l'upload et comme cela en cas d'annulation le thread serai killer et on aurai plus besion d'attendre. 



    1) on ne peut pas tuer un thread !
    2) toute opération sur l'interface doit se faire dans le main thread, d'où l'utilité de la méthode performSelectorOnMainThread: !

    Ma proposition: y a rien n'a faire, sauf à  afficher un message: cancel operation, please wait...
  • JijoJijo Membre
    08:17 modifié #20
    Je ne sais pas comment on peut faire cela toutes mes tentatives n'ont donné aucuns résultat.

    J'ai tenté de mettre la méthode performSelectorOnMainThread: dans l'action du bouton annuler mais sans résultat non plus. 

    J'ai essayé aussi de fermer la sheet dés le clique du bouton annuler et d'en afficher une autre mais la seconde apparaà®t vide sans les labels.

    Voilà  si quelqu'un a une solution une piste.

    Merci
  • mpergandmpergand Membre
    08:17 modifié #21
    Tu m'a pas d'autre choix que d'attendre que le thread de chargement ne soit plus actif.

    Donc, dans la méthode cancel, tu affiches un textfield de la sheet avec "Cancel operation, please wait" pour que l'utilisateur voit que la sa demande est prise en compte.

    Maintenant, il te faut savoir quand le thread de chargement n'est plus actif, pour cela tu va t'enregistrer pour recevoir une notification, dans awakeFromNib, tu ajoutes:
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadDidExit:) name:NSThreadWillExitNotification object: nil];

    puis tu ajoutes:
    -(void) threadDidExit:(id)t<br />{<br />	NSLog(@&quot;thread did exit&quot;);<br />&nbsp; &nbsp; &nbsp; &nbsp; [self performSelectorOnMainThread:@selector(updateWindow) withObject:nil waitUntilDone:NO];<br />}
    


    A partir de là , tu peux tout faire, mettre à  jour la fenêtre, par exemple.
  • JijoJijo Membre
    08:17 modifié #22
    J'ai réussi à  le faire mais d'une manière très simple en fait


    -(void) cancel:(id)sender
    {
    [label setStingValue:@Cancel operation, please wait ...];
    }

    La ligne ci-dessous ne faisait rien
    [self performSelectorOnMainThread:@selector(updateWindow) withObject:nil waitUntilDone:NO];


    en tout cas merci
  • mpergandmpergand Membre
    08:17 modifié #23
    La méthode cancel s'exécute dans le main thread, donc pas besoin de performSelectorOnMainThread.


  • mpergandmpergand Membre
    08:17 modifié #24
    dans 1232239299:

    Après, pour le cas qui nous intéresse ici, à  savoir juste charger une liste d'images, c'est pas utile d'utiliser des NSOperations, à  mon avis un simple [NSThread detachNewThreadSelector:... toTarget:... withObject:...] suffit amplement (ou le truc avec les NSRunLoops que j'ai jamais testé peut-être)  ;)


    OK, merci.
  • mpergandmpergand Membre
    08:17 modifié #25
    Et voilà  ma soluce au problème de chargements d'images en multithreads ...

    Le prog charge les images du dossier Images de l'utilisateur (on peut spécifier un sous dossier)
    Un master thread est créé au démarrage du chargement et ensuite un thread pour chaque image, pour un maximum de 8 threads simultanés.

    Le tout sans l'utilisation d'un seul @synchronized (evil inside  :o)

    Les images s'affichent dans un NSCollectionView.

    Projet Xcode 3.1, Leo 10.5.4 mini, dans les versions antérieures, NSImage lockFocus n'est pas thread safe (bouh ! Apple la teuon !)


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