Un bug d'accessoryview ou de focus ? Ou pas ?

ChachaChacha Membre
juin 2006 modifié dans API AppKit #1
Salut,

Je crois avoir trouvé un bug dans Cocoa, mais je ne suis pas sûr. J'aurais besoin d'un petit coup de pouce, à  la fois pour confirmer le bug (si ce n'est pas moi le fautif), et aussi pour trouver une façon de le contourner.

Voici une introduction:
-les NSSavePanel sont bien pratiques, et ils ont le bon goût d'être configurables, grâce à  l'introduction d'une vue personnalisée sous forme d'"accessoryView", qui se glisse au bas du NSSavePanel. On peut ainsi rajouter des contrôles personnalisés.
-il y a une petite subtilité sur le retain/release (expliquée dans la doc), mais rien de compliqué.
-j'ai une application document-based. Le MyDocument.nib contient une accessoryView "préinstanciée", de telle sorte que chaque document dispose en permanence d'une accessoryView, sans avoir à  la créer à  chaque apparition du NSSavePanel. L'idée, c'est que les contrôles présents dans l'accessoryView conservent ainsi leur valeur entre deux apparitions du NSSavePanel.
-dans mon accessoryView, j'ai juste mis un textField

Voici le bug :
-Si je fais apparaà®tre ce fameux NSSavePanel plusieurs fois de suite, le nstextfield contient bien la même valeur, mais cette dernière n'apparaà®t que si le textfield a le focus ! Très étrange...

Bon, vous êtes bien avancés avec ça. Mais heureusement, j'ai fait un mini-projet XCode qui illustre ce comportement.
Des idées ?

+
Chacha

[Fichier joint supprimé par l'administrateur]

Réponses

  • BruBru Membre
    15:59 modifié #2
    Je confirme cet étrange comportement...

    .
  • BruBru Membre
    15:59 modifié #3
    Bon.

    Il faut savoir qu'un NSTextField est quelque chose d'assez complexe.
    Basiquement, NSTextField affiche le contenu de son NSTextFieldCell associé.
    Sauf que, lorsque on édite NSTextField, le mécanisme d'affichage de l'édition est alors pris en charge par l'éditeur NSText de la fenêtre.
    Une fois l'édition terminée, NSText met à  jour (ou non) NSTextFieldCell, puis raffraichit NSTextField pour mettre à  jour l'affichage.

    C'est cette dernière étape qui bug dans ton cas (pour une raison que je ne m'explique pas).

    Pour preuve, relie le NSTextField incriminé à  un outlet et un target/action.
    Moi j'ai nommé l'outlet txt et l'action action.
    Ensuite, implémente dans MyDocument.m les méthodes suivantes :
    <br />- (void)action:(id)sender<br />{<br />    [NSTimer scheduledTimerWithTimeInterval:0.1<br />             target:self<br />             selector:@selector(timer:)<br />             userInfo:nil<br />             repeats:NO];<br />}<br /><br />- (void)timer:(NSTimer *)ti<br />{<br />    [txt drawCellInside:[txt cell]];<br />}<br />
    


    Que se passe t'il ?
    0.1 seconde après avoir quitté le NSTextField (donc déclenchement de la méthode action), celui se raffraichit (dans la méthode du timer), et le texte réapparait.

    A partir de ça, je pense qu'une parade élégante peut être trouvée !

    .
  • ChachaChacha Membre
    juin 2006 modifié #4
    Merci Bru.
    J'ai effectivement pu m'en sortir grâce à  ton idée.

    <br />//une fonction à  déclencher pour mettre à  jour le textfield<br />-(void) updateTextField:(NSTimer*)timer<br />{<br />  NSTextField* txtField = [timer userInfo];<br />  [txtField drawCellInside:[txtField cell]];<br />}<br /><br />-(IBAction) openSheet:(id)sender<br />{<br />  NSSavePanel* savePanel = [NSSavePanel savePanel];<br />  [savePanel setAccessoryView:accessoryView];<br /><br />  // juste avant le beginSheet, je prépare un timer pour déclencher l&#39;udpate<br />  [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(updateTextField:) userInfo:textField repeats:NO];<br /><br />  [savePanel beginSheetForDirectory:nil file:nil modalForWindow:[self windowForSheet] modalDelegate:self<br />                     didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];<br />}<br />
    


    J'ai mis 0 au timer, puisqu'apparemment il ne sera évalué qu'au prochain passage dans la RunLoop, donc après le beginSheet.

    Je vais quand même soumettre ça comme un bug à  Apple.
    +
    Chacha

    [edit]
    Ah, non, ça ne résout pas le bug quand le textfield perd le focus. Bah, j'ai juste à  rajouter ton code et c'est bon, je pense.
    [/edit]
    [edit2]
    Voilà , c'est bon, j'ai rajouté le même timer associé à  un NSControlTextDidChangeNotification
    [/edit2]
  • helgrindhelgrind Membre
    15:59 modifié #5
    Je déterre ce vieux topic car j'ai exactement le même problème...sauf que j'utilise la méthode runModal de NSSavePanel, ce qui bloque la run loop jusqu'à  sa fermeture et le timer est exécuté seulement après...

    Comment faire dans ce cas?
  • Philippe49Philippe49 Membre
    mai 2008 modifié #6
    ou un bug de Chacha ?

    Comme cela ça marche (le savepanel reste le même entre deux appels)


    @interface MyDocument : NSDocument
    {
      IBOutlet NSView* accessoryView;
      NSSavePanel * savePanel;
    }





    -(IBAction) openSheet:(id)sender
    {
      if(!savePanel) {
        savePanel = [[NSSavePanel savePanel] retain];
        [savePanel setAccessoryView:accessoryView];
      }
      [savePanel beginSheetForDirectory:nil file:nil modalForWindow:[self windowForSheet] modalDelegate:self
                         didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
    }

  • helgrindhelgrind Membre
    15:59 modifié #7
    dans 1209828722:

    ou un bug de Chacha ?

    Comme cela ça marche (le savepanel reste le même entre deux appels)


    @interface MyDocument : NSDocument
    {
      IBOutlet NSView* accessoryView;
      NSSavePanel * savePanel;
    }





    -(IBAction) openSheet:(id)sender
    {
      if(!savePanel) {
         savePanel = [[NSSavePanel savePanel] retain];
         [savePanel setAccessoryView:accessoryView];
      }
      [savePanel beginSheetForDirectory:nil file:nil modalForWindow:[self windowForSheet] modalDelegate:self
                         didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
    }




    ah oui ça marche merci  ;)
  • Philippe49Philippe49 Membre
    15:59 modifié #8
    Il me semble que le problème est que le TextFieldEditor (la zone de texte dans laquelle on édite son texte pour changer le contenu d'une Cell) est lié à  la fenêtre et non à  la vue concernée.
    Ici, la cause du problème serait que cet editor disparaà®t avec le panel sans rafraà®chir le text view.
  • mpergandmpergand Membre
    15:59 modifié #9
    dans 1209820103:

    Je déterre ce vieux topic car j'ai exactement le même problème...sauf que j'utilise la méthode runModal de NSSavePanel, ce qui bloque la run loop jusqu'à  sa fermeture et le timer est exécuté seulement après...

    Comment faire dans ce cas?


    Pour utiliser un timer avec un panel, il faut l'installer en mode panel  ;)
    #import &quot;Controller.h&quot;<br /><br />#define PANEL<br /><br />@implementation Controller<br /><br />-(void) openPanel:(id) o<br />{<br />	NSLog(@&quot;open panel&quot;);<br />	<br />	NSTimer* timer=[NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];<br />	NSRunLoop* runLoop=[NSRunLoop currentRunLoop];<br />	NSOpenPanel* openPanel=[NSOpenPanel openPanel];<br /><br />#ifdef PANEL<br />	// mode panel<br />	[runLoop addTimer:timer forMode:NSModalPanelRunLoopMode];<br />	[openPanel runModalForTypes:nil];<br />	[timer invalidate];<br />#else<br />	<br />	// mode sheet<br />	[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];<br />	[openPanel beginSheetForDirectory:nil file:nil modalForWindow:window modalDelegate:self<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:timer];<br />#endif<br />					 <br />	<br />}<br /><br />- (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode&nbsp; contextInfo:(void&nbsp; *)contextInfo<br />{<br />	NSLog(@&quot;Panel did end&quot;);<br />	[(NSTimer*)contextInfo invalidate];<br />}<br /><br />-(void) timerAction:(id) t<br />{<br />	NSLog(@&quot;Timer action&quot;);<br />}<br /><br />@end<br />
    

  • Philippe49Philippe49 Membre
    mai 2008 modifié #10


    Néammoins, pour le problème posé par Chacha, il me semble que si on veut faire propre, on crée une variable pour la mise à  jour ad-hoc de l'accessoryView afin qu'elle présente l'aspect voulu au moment voulu.
    • Soit on retient l'état du panel,
    • Soit, et je préférerais, on retient le contenu pour remplir ce panel (une NSString), en utilisant les delegate method, et en affinant l'appel de setAccessoryView par les mises-à -jour nécessaires du textfield
  • helgrindhelgrind Membre
    15:59 modifié #11
    dans 1209903824:

    Néammoins, pour le problème posé par Chacha, il me semble que si on veut faire propre, on crée une variable pour la mise à  jour ad-hoc de l'accessoryView afin qu'elle présente l'aspect voulu au moment voulu.
    • Soit on retient l'état du panel,
    • Soit, et je préférerais, on retient le contenu pour remplir ce panel (une NSString), en utilisant les delegate method, et en affinant l'appel de setAccessoryView par les mises-à -jour nécessaires du textfield



    J'ai pas compris ta deuxième solution?



    Maintenant j'ai un autre problème: j'ai deux accessory view différentes.
    Et un retain du save panel fait que seulement la première vue est affichée.

    Si j'essaie avec les timer, j'ai un effet de clignotement dès que le textfield perd le focus, même en réglant le timer à  0.
  • mpergandmpergand Membre
    15:59 modifié #12
    Bon, pour le problème de chacha, j'arrive largement après la bataille  :)
    Ya peut-être une soluce simple:
    <br />-(void) savePanelDidEnd:(NSSavePanel*)sheet returnCode:(int)code contextInfo:(void*)contextInfo<br />{<br />[sheet makeFirstResponder:nil];<br />}<br />
    


    En fait, je suis sûr que c'est à  cause du textfield qui est first responder à  la fermeture de la fenêtre et comme accessoryView est rajoutée à  chaque fois, ça fout le bazar !
  • Philippe49Philippe49 Membre
    15:59 modifié #13
    dans 1209923491:


    J'ai pas compris ta deuxième solution?
    ...
    Maintenant j'ai un autre problème: j'ai deux accessory view différentes.
    Et un retain du save panel fait que seulement la première vue est affichée.


    Je navigue un peu à  l'aveugle là  par rapport à  ton problème, mais il me semble que tout serait plus simple à  régler si le contenu de l'accessoryView était défini par le programme à  partir d'une ou plusieurs variables de MyDocument.
    En clair ne pas se contenter de [ savePanel setAccessoryView:accessoryView]
    Mais y rajouter, tant à  l'apparition du panel qu'au retour dans une delegate method (par exemple celle indiquée par mpergand), des mises à  jours du style

    à   l'aller
    [theTextFieldInTheAccessoryView setStringValue: textFieldValue];

    au retour :
    textFieldValue=[[theTextFieldInTheAccessoryView stringValue] copy];

    Dès lors le panel ne sert que de view, le model étant bien à  l'abri derrière le controller, instance de MyDocument.
  • Philippe49Philippe49 Membre
    15:59 modifié #14
    dans 1209923491:


    Et un retain du save panel fait que seulement la première vue est affichée.



    Rien n'empêche non plus de faire après avoir récupéré les infos, un release ou un autorelease sur le savepanel dans la delegate method appelée en didEndSelector:

    didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:)

  • helgrindhelgrind Membre
    15:59 modifié #15
    En fait le retain du savePanel marche très bien, c'est moi qui ai fait un bête copier-coller et j'ai pas changé l'accessory view à  afficher :)
Connectez-vous ou Inscrivez-vous pour répondre.