Arborescences dans les NSUserDefaultsController

OmfraaxOmfraax Membre
03:23 modifié dans API AppKit #1
Bonsoir à  tous.

En voulant faire le malin pour les préférences de mon appli, je me suis dit: "Tiens, tout marche bien comme ça, et si j'essayais de faire des préférences arborescentes", c'est à  dire un truc comme ça
<br />NSDictionary *bar=[NSDictionary dictionaryWithObjectsAndKeys: <br />[NSNumber numberWithInt:0],@&quot;item1&quot;,<br />[NSNumber numberWithInt:1],@&quot;item2&quot;,nil];<br /><br />NSDictionary *foo=[NSDictionary dictionaryWithObjectAndKeys: bar,@&quot;bar&quot;,nil];<br /><br />NSDictionary *appDefaults=[NSDictionary dictionaryWithObjectAndKeys: foo,@&quot;foo&quot;, nil];<br /><br />[defaults registerDefaults:appDefaults];<br />[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:appDefaults];<br />


le but de la manoeuvre étant de pouvoir faire des bindings sur des keypaths du type foo.bar.item1

Ca marche quand j'accède aux préférences directement dans le code avec le standardUserDefaults, mais ça ne marche pas dans IB quand je binde avec le NSUserDefaultController.
Par contre, quand j'enlève cette arborescence en associant directement les valeurs à  une clé simple de appDefaults, tous les bindings remarchent comme il faut.

Est-ce que tout simplement on ne peut pas utiliser les keypaths ramifiés avec NSUserDefaultController ou alors est-ce qu'il y a une astuce sioux ?

Merci beaucoup.


Réponses

  • Philippe49Philippe49 Membre
    octobre 2008 modifié #2
    Pour moi cela marche avec les keyPath textPrefs.textColor et textPrefs.fontSize

    +(void) initialize
    {
    // Registering initial values for preferences
    NSUserDefaults * userDefaults=[NSUserDefaults standardUserDefaults];
    NSData * colorData=[NSArchiver archivedDataWithRootObject: [NSColor purpleColor]];
    NSDictionary * values=[NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithFloat:30.],@frameRotation,
    [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithFloat:18.],@fontSize,
    colorData,@textColor,
    nil], @textPrefs,
    nil];
    [userDefaults registerDefaults:values];
    [[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:values];
    }

  • Philippe49Philippe49 Membre
    octobre 2008 modifié #3
    ci-joint un exemple
  • OmfraaxOmfraax Membre
    03:23 modifié #4
    Je n'arrive pas à  lancer ton exemple (un problème d'autorisation je ne sais trop quoi qui "exit with signal 5") mais je te crois si tu dis que ça marche.

    J'ai changé un peu la définition de mon appDefaults sans passer par des variables intermédiaires (en encapsulant directement les [NSDictionary dictionaryWithObjectsAndKeys:....]) pour avoir quelque chose de réellement identique à  ton exemple mais ça ne marche toujours pas.

    Cependant, j'ai une piste :
    C'est peut-être encore une histoire d'initialisation car je déclare ces appDefaults dans le initialize d'une classe Preferences qui ne possède que des methodes de classe (un peu comme le Controller de ton exemple) alors que le reste de l'application est contrôlé par une classe GeneralController. J'ai bien instancié ces deux classes dans IB (les petits cubes bleus) pour que les +initialize des deux méthodes soit appelé. Mais je me suis aperçu en faisant des tests sur l'accès aux données de [NSUserDefaults standardUserDefaults] qu'elles sont null dans le -init du GeneralController mais qu'elles sont bien définies dans son -awakeFromNib.
    Le problème pourrait venir de là  mais je croyais pourtant que le nib appelait d'abord tous les initialize puis tous les inits, faisait ses connections et envoyait ensuite un awakeFromNib ?

    Merci en tout cas pour tes réponses.

    J'ai encore tout faux ?  ??? :crackboom:-
  • Philippe49Philippe49 Membre
    octobre 2008 modifié #5
    dans 1223720127:

    Je n'arrive pas à  lancer ton exemple (un problème d'autorisation je ne sais trop quoi qui "exit with signal 5") mais je te crois si tu dis que ça marche.

    Oui j'ai encore oublié de mettre le projet compatible avec XCode 3.1
    cela est corrigé, cela doit marcher maintenant si tu es sous XCode 3.0
  • Philippe49Philippe49 Membre
    octobre 2008 modifié #6
    dans 1223720127:

    Cependant, j'ai une piste :
    C'est peut-être encore une histoire d'initialisation car je déclare ces appDefaults dans le initialize d'une classe Preferences qui ne possède que des methodes de classe (un peu comme le Controller de ton exemple) alors que le reste de l'application est contrôlé par une classe GeneralController. J'ai bien instancié ces deux classes dans IB (les petits cubes bleus) pour que les +initialize des deux méthodes soit appelé. Mais je me suis aperçu en faisant des tests sur l'accès aux données de [NSUserDefaults standardUserDefaults] qu'elles sont null dans le -init du GeneralController mais qu'elles sont bien définies dans son -awakeFromNib.
    Le problème pourrait venir de là  mais je croyais pourtant que le nib appelait d'abord tous les initialize puis tous les inits, faisait ses connections et envoyait ensuite un awakeFromNib ?


    Je pense que c'est quelque chose comme cela. La méthode +(void)initialize doit être exécutée avant que le nib soit désarchivé. En général je le mets dans une classe qui contrôle l'application.
  • quinoaquinoa Membre
    03:23 modifié #7
    Pour un binding des préferences avec arborescence, j'ai utilisé un objet intermédiaire instancié avec IB (en fait dans mon cas deux objets intermédiaires pour deux niveaux d'arborescence).
    Je me suis inspiré d'un post trouvé sur :
    http://www.cocoabuilder.com/archive/message/cocoa/2007/11/10/192782
    En pratique :
    Le début de mon fichier préférence (plist) :
    &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />&lt;!DOCTYPE plist PUBLIC &quot;-//Apple Computer//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;<br />&lt;plist version=&quot;1.0&quot;&gt;<br />&lt;dict&gt;<br />	&lt;key&gt;biddingPrefs&lt;/key&gt;<br />	&lt;dict&gt;<br />		&lt;key&gt;agressiveness&lt;/key&gt;<br />		&lt;integer&gt;0&lt;/integer&gt;<br />		...<br />	&lt;/dict&gt;
    


    Pour binder dans IB :
    • Instancier un objet NSUserDefaultsController
    • Instancier un objet NSObjetController (nommé biddingPrefsGroup)
      (Sa propriété onglet Atributes 'Automatically prepares contents' doit être coché)
    • Binder  le 'Controller content' :
      Bind to : NSUserDefaultsController (NSUserDefaultsController)
      Controller key : selection
      Model Key Path : biddingPrefs
    • Binder l'élement de préférence (dans mon cas un NSSlider)
      Bind to : biddingPrefsGroup
      Controller key : selection
      Model key path : agressiveness


    Pour l'initialisation des préférences, je fais çà  dans l'initialisation de l'application, car lecture d'un fichier tout prêt de défauts placé dans le bundle de l'application.
    Le binding avec des Key Path n'a jamais marché dans mes essais.

  • Philippe49Philippe49 Membre
    octobre 2008 modifié #8
    @quinoa : !! Bienvenu !! 

    Il y a plusieurs articles et tutoriels sur ce site concernant le binding des préférences, aucun ne rajoute d'object controller  ;)

    dans 1223845494:

    Le binding avec des Key Path n'a jamais marché dans mes essais.

    L'exemple donné un peu au-dessus à  ce post fonctionne.
  • OmfraaxOmfraax Membre
    03:23 modifié #9
    Je n'ai pas encore testé ta solution quinoa mais comme le dit Philippe49 ça devrait marcher normalement !

    J'ai fait mes initialisations dans une méthodes de classe de la classe Preferences, méthode que j'appelle dans le-initialize du controleur général de l'application (delegate de l'application et tout et tout).
    Quand je fais des tests avec le code suivant  dans le -init :
    <br />NSArray* test=[[[NSUserDefaultsController sharedUserDefaultsController] initialValues] valueForKey:@&quot;foo.bar.items&quot;];<br />NSArray* test2=[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKeyPath:@&quot;foo.bar.items&quot;];<br />
    


    le premier renvoie (null) partout alors que le deuxième renvoie les bonnes valeurs !
    Donc il ya une histoire de valueForKey et de valueForKeyPath, sachant que NSDictionary n'implémente pas valueForKeyPath

    Peut-être qu'IB execute le premier choix quand il binde ?
  • Philippe49Philippe49 Membre
    octobre 2008 modifié #10
    dans 1223930862:

    J'ai fait mes initialisations dans une méthodes de classe de la classe Preferences, méthode que j'appelle dans le-initialize du controleur général de l'application (delegate de l'application et tout et tout).

    Hillegass, page 203, Third Edition
    "Each class is sent the initial message initialize before any other message. To ensure that your defaults are registered early, you will override +(void)initialize in AppController.m

    Un message à  une autre classe dans un +(void) initialize me paraà®t douteux, et de toutes façons inhabituel. Il faut éviter.
    Mettre tout simplement les initialisations du Userdefaults dans le +(void) initialize de AppController sans message à  une autre classe me paraà®t plus prudent. 

    dans 1223930862:

    Quand je fais des tests avec le code suivant  dans le -init :
    <br />NSArray* test=[[[NSUserDefaultsController sharedUserDefaultsController] initialValues] valueForKey:@&quot;foo.bar.items&quot;];<br />NSArray* test2=[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKeyPath:@&quot;foo.bar.items&quot;];<br />
    


    le premier renvoie (null) partout alors que le deuxième renvoie les bonnes valeurs !

    Logique, ce dictionnaire n'a pas de clé dont le nom serait @foo.bar.items

    dans 1223930862:

    Donc il ya une histoire de valueForKey et de valueForKeyPath, sachant que NSDictionary n'implémente pas valueForKeyPath
    Peut-être qu'IB execute le premier choix quand il binde ?

    valueForKeyPath est une méthode du protocole NSKeyValueCoding, auquel se conforme NSDictionary. Ainsi, on ne trouve pas directement dans la doc sur NSDictionary la méthode valueForKeyPath: , mais cette classe, ou une classe dont elle hérite, implémente néammoins cette méthode.
    Quand au binding, il ne se met pas en observation du dictionnaire, mais en observation des setter de l'objet final "items" de l'objet "bar", ceci sans doute par l'intermédiaire d'un proxy.


  • quinoaquinoa Membre
    03:23 modifié #11
    dans 1223848601:

    @quinoa : !! Bienvenu !! 

    Il y a plusieurs articles et tutoriels sur ce site concernant le binding des préférences, aucun ne rajoute d'object controller  ;)

    dans 1223845494:

    Le binding avec des Key Path n'a jamais marché dans mes essais.

    L'exemple donné un peu au-dessus à  ce post fonctionne.



    Je suis désolé mais je suis encore sous 10.4 et donc XCode2 :(.
    Je n'ai pas pu exploiter cet exemple.

    Dans ma doc, il n'est pas dit que NSUserDefaultController adopte le protocole NSKeyValueCoding ou alors çà  m'a échappé ce qui est fort possible.
    Toujours est-il qu'en essayant de binder avec les keyPath directement dans IB, j'avais pour résultat la création d'une préférence libellée :
    biddingPrefs.agressiveness
    de type NSNumber
    au lieu d'un Dictionary pour biddingPrefs avec un élément de type NSNumber nommé agressiveness
    et il semble que ce soit aussi le cas pour Omfraax
  • CéroceCéroce Membre, Modérateur
    03:23 modifié #12
    dans 1224106462:

    Dans ma doc, il n'est pas dit que NSUserDefaultController adopte le protocole NSKeyValueCoding ou alors çà  m'a échappé ce qui est fort possible.

    Les bindings sont basés entièrement sur le Key-Value Coding et le Key-Value Observing. Je te conseille fortement de lire les docs d'Apple qui leur sont consacrés, parce que là , tu as loupé un éléphant dans un corridor.
  • OmfraaxOmfraax Membre
    03:23 modifié #13
    Bon, je crois que je vais abandonner l'idée de ces préférences arborescentes et je vois pourquoi ça ne marche pas chez moi et pourquoi ça a l'air de marcher dans l'application de Philippe.

    Je dis bien "ça a l'air de marcher" puisqu'en y regardant de plus près, on remarque que le revertToInitialValues n'affecte ni la couleur ni la taille de police, autrement dit les paramètres inclus dans ce fameux Dictionary. Et effectivement, quand on regarde le fichier .plist, on voit bien des propriétés textPrefs.trucMuche probablement générés par IB quand les boutons de contrôles bougent mais qui n'ont rien à  voir avec ce que l'on aurait attendu, à  savoir un Dictionary textPrefs contenant deux items.

    Dans mon programmes, les propriétés que je veux afficher ne sont pas bindées à  un contrôle quelconque (il s'agit en fait de NSArray contenant les valeurs d'un bouton Pop-up) et leur valeur est uniquement déterminée "par défaut" dans le programme donc je pense que c'etait pour ça qu'on ne voyait rien.

    Peut-être qu'il n'y a finalement pas d'autres solutions que celle de Qinoa pour faire ces préférences arborescentes...
    Qu'en pensez-vous ?


  • Philippe49Philippe49 Membre
    octobre 2008 modifié #14
    Effectivement, je n'avais pas eu la curiosité d'aller voir dans le plist , excès de confiance ... L'enregistrement se fait effectivement avec l'identificateur "textPrefs.tsoinTsoin" qui n'est pas considéré comme un keyPath, sans s'adapter à  la structure espérée avec le +(void) initialize
    En clair, les bindings sont faits pour éviter les problèmes simples. Pour les problèmes plus compliqués rien ne vaut quelques lignes de code, en l'occurrence l'utilisation d'un PreferenceController pour le nib des préférences.

      
  • Philippe49Philippe49 Membre
    03:23 modifié #15
    La solution :
    Créer des object controller intermédiaires et cocher "Handles Content As Compound Values"
  • AliGatorAliGator Membre, Modérateur
    03:23 modifié #16
    Wow ça c'est du déterrage de post en puissance :P
Connectez-vous ou Inscrivez-vous pour répondre.