Intercepter l'action d'un binding (ici sur NSTextView et attributedString)

ChachaChacha Membre
août 2008 modifié dans API AppKit #1
Salut,

J'ai une NSTextView avec un binding "attributedString" activé et fonctionnel: quand je sélectionne un objet dans une tableview, ma NSTextView affiche une chaà®ne relative à  cet objet.
Par contre, j'aimerais intercepter le moment où la NSTextView a changé son texte, pour effectuer quelques traitements supplémentaires dans cette NSTextView (coloration syntaxique... en fait c'est ici une sous-classe de NSTextView)
Le plus logique serait d'attendre un textDidChange:, mais celui-ci n'est pas déclenché. J'ai bien essayé de surcharger des tas de méthodes dans ma sous-classe de NSTextView, activer des notifications... mais je n'ai pu trouver comment intercepter cette application de attributedString.
Une idée ?

+
Chacha

[edit]
Résolu : http://www.objective-cocoa.org/forum/index.php/topic,2807.msg27989.html#msg27989

Réponses

  • Philippe49Philippe49 Membre
    13:40 modifié #2
    J'essaierais de sous-classer le textview avec une méthode
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

    quite peut-être à  faire le binding à  la main :
    - (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
  • Philippe49Philippe49 Membre
    13:40 modifié #3
    Bon cela n'a pas l'air de marcher.

    NSTextViewDidChangeSelectionNotification ?
  • ChachaChacha Membre
    13:40 modifié #4
    dans 1218007101:

    J'essaierais de sous-classer le textview avec une méthode
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

    quite peut-être à  faire le binding à  la main :
    - (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context


    Oui, j'avais essayé aussi, mais sans succès. Comme je ne suis pas très fort en Bindings, j'ai préféré ne pas dire que c'était une fausse piste, car j'aurais très bien pu m'y fourvoyer.
    Le NSTextViewDidChangeSelectionNotification ne semble pas adapté : ce n'est pas la sélection qui change...

    Je crois que mon problème doit pouvoir s'inscrire dans un cadre plus général; comment "intercepter un binding", NSTextView ou pas... Comme toi, je m'attendais à  ce que le "addObserver:" à  la main fonctionne. Si ce n'est pas le cas, c'est qu'une subtilité nous échappe.

  • Philippe49Philippe49 Membre
    13:40 modifié #5
    Il y a toujours la possibilité de réagir dans le setter de la property bindée.
  • Philippe49Philippe49 Membre
    13:40 modifié #6
    Essai concluant, il faut cocher "Continuously Updates Value" dans le binding
  • Philippe49Philippe49 Membre
    13:40 modifié #7
    Autres pistes
    • NSFormatter
    • La méthode de NSTextView shouldChangeTextInRange:replacementString:
  • ChachaChacha Membre
    13:40 modifié #8
    dans 1218009570:

    Il y a toujours la possibilité de réagir dans le setter de la property bindée.

    Sauf quand il n'y a pas de setter... "setAttributedString" n'existe pas dans NSTextView.

    Essai concluant, il faut cocher "Continuously Updates Value" dans le binding

    Chez moi ça provoque "this class is not key value coding-compliant for the key value" ("value" est bien le nom de ma NSAttributedString) si je coche cette case. C'est bizarre, car par ailleurs, (si je ne coche pas cette case) mon binding marche bien, en lecture/écriture.


    shouldChangeTextInRange:replacementString:

    Même si ça marchait (ce n'est pas le cas), cela interviendrait avant que la nouvelle chaà®ne ait été placée.

    Bref, toujours bloqué  :crackboom:-
  • fouffouf Membre
    13:40 modifié #9
    Tu peux aussi essayer de créer une nouvelle clef qui correspondrait à  la version colorisée correctement. Ainsi, lorsque le texte le textView est changé, tu pourras faire les modifs encadrés par les appels du genre [self willChangeValueForKey:@colorizedString] et [self didChangeValueForKey:@colorizedString].
    Je me demande si tu ne peux pas aussi regarder du côté de setKeys:triggerChangeNotificationsForDependentKey: .
    L'une ou l'autre des méthodes doit dépendre de la manière dont tu traites la colorisation et aussi si tu recois les méhodes du genre textDidChange: avant ou apres les méthodes qui te préviendront du changement de la clef correspondant au texte colorisé.
  • NoNo Membre
    13:40 modifié #10
    dans 1217977635:

    Salut,

    J'ai une NSTextView avec un binding "attributedString" activé et fonctionnel: quand je sélectionne un objet dans une tableview, ma NSTextView affiche une chaà®ne relative à  cet objet.
    Par contre, j'aimerais intercepter le moment où la NSTextView a changé son texte, pour effectuer quelques traitements supplémentaires dans cette NSTextView (coloration syntaxique... en fait c'est ici une sous-classe de NSTextView)
    Le plus logique serait d'attendre un textDidChange:, mais celui-ci n'est pas déclenché. J'ai bien essayé de surcharger des tas de méthodes dans ma sous-classe de NSTextView, activer des notifications... mais je n'ai pu trouver comment intercepter cette application de attributedString.
    Une idée ?

    +
    Chacha


    As tu essayé le delegate de NSTextStorage ?
    Je pense à  textStorageDidProcessEditing: qui devrait être appelé quand textStorage a fini d'être modifié.
    textStorage stocke la attributedString qui est affichée par NSTextView, et est accessible via la méthode textStorage.
  • Philippe49Philippe49 Membre
    13:40 modifié #11
    dans 1218012729:

    Chez moi ça provoque "this class is not key value coding-compliant for the key value" ("value" est bien le nom de ma NSAttributedString) si je coche cette case. C'est bizarre, car par ailleurs, (si je ne coche pas cette case) mon binding marche bien, en lecture/écriture.

    Il faut régler ton text view sur Rich Text pour que que le binding soit en AttributedString, et cela change "value" en quelque chose comme "atributed value"
  • Philippe49Philippe49 Membre
    13:40 modifié #12
    Ceci dit pour ce que tu veux faire j'utiliserais un NSTextField qui peut disposer d'un NSFormatter ..
  • Philippe49Philippe49 Membre
    août 2008 modifié #13
    Voilà  mon essai, avec le binding sur Value.
    Cela marcherait tout autant avec une attributedString, le text view réglé sur Rich Text, le binding sur Attributed String :

    @interface TextViewController : NSObject {
    NSString * textViewValue;
    IBOutlet NSTextView * textView;
    }
    @property (retain) NSString * textViewValue;
    @end

    @implementation TextViewController
    @dynamic textViewValue;
    -(NSString *) textViewValue {return textViewValue;}
    -(void) setTextViewValue:(NSString *) value
    {
    [textViewValue release];
    textViewValue=[[value uppercaseString] copy];
    [textView setString:textViewValue];

    }

    @end
  • Philippe49Philippe49 Membre
    13:40 modifié #14
  • Philippe49Philippe49 Membre
    13:40 modifié #15
    Euh , il y a ceci dans le panel des bindings. Il suffit de se faire un NSFormatter (une description ici) et va bene ...
  • ChachaChacha Membre
    13:40 modifié #16
    dans 1218014100:

    Tu peux aussi essayer de créer une nouvelle clef qui correspondrait à  la version colorisée correctement.

    Ah mais non. Je ne parle pas d'un texte uniformément coloré, mais de coloration syntaxique nécessitant une analyse grammaticale. Je vous ai simplifié le tableau, mais le travail que fait ma sous-classe de NSTextView n'est pas une transformation basique.


    Je me demande si tu ne peux pas aussi regarder du côté de setKeys:triggerChangeNotificationsForDependentKey:

    Tout comme avec les addObserver, je n'arrive pas mieux à  intercepter le binding correspondant à  attributedString.


    textDidChange:

    Justement, si une de ces méthodes était appelée quand le binding attribue une nouvelle attributedString à  ma NSTextView, je n'aurais pas de souci.


    As tu essayé le delegate de NSTextStorage ?
    Je pense à  textStorageDidProcessEditing: qui devrait être appelé quand textStorage a fini d'être modifié.

    Oui, j'ai essayé. Le souci, c'est que ma coloration syntaxique va elle-même appeler textStorageDidProcessEditing:. Je pourrais briser la récursivité avec un truc un peu crade, mais je trouve que c'est une mauvaise solution, parce que c'est s'insérer au mauvais endroit de la cascade d'événements.


    Il faut régler ton text view sur Rich Text pour que que le binding soit en AttributedString, et cela change "value" en quelque chose comme "atributed value"

    Ma textview est déjà  en Rich Text. Je suppose que chez toi, la structure est un peu différente. Ma NSTextView a un binding "attributedString" sur un champ particulier d'un dictionnaire (champ que j'ai labelisé "value", l'autre entrée du dictionnaire étant un "name"), et ce dictionnaire est la "selection" courante d'un NSArrayController (contenant donc plusieurs dictionnaires).


    Ceci dit pour ce que tu veux faire j'utiliserais un NSTextField qui peut disposer d'un NSFormatter ..

    Voir plus haut ce que je détaille à  Fouf : j'ai vraiment besoin d'une NSTextView.


    Euh , il y a ceci dans le panel des bindings. Il suffit de se faire un NSFormatter (une description ici) et va bene ...

    Le formatter ne va pas m'aider.
  • Philippe49Philippe49 Membre
    13:40 modifié #17
    dans 1218022421:

    Ah mais non. Je ne parle pas d'un texte uniformément coloré, mais de coloration syntaxique nécessitant une analyse grammaticale. Je vous ai simplifié le tableau, mais le travail que fait ma sous-classe de NSTextView n'est pas une transformation basique.

    Le formatter ne va pas m'aider.


    Un formatter maison permet de faire n'importe quelle analyse sur le texte que l'on veut formatter.

  • ChachaChacha Membre
    13:40 modifié #18
    dans 1218022731:

    dans 1218022421:

    Le formatter ne va pas m'aider.

    Un formatter maison permet de faire n'importe quelle analyse sur le texte que l'on veut formatter.

    Ma NSTextView fonctionne de concert avec une NSRuleView pour compter les lignes, afficher les numéros, ajouter des marqueurs dans les marges, et aussi colorer le texte.... je pense que cela sort un peu du rôle du formatteur  :)
  • Philippe49Philippe49 Membre
    13:40 modifié #19
    Certes le formatter ne changerais que le texte.
    Pourquoi pas la solution du TextViewController ? Tu l'instancie et connecte dans le nib, il joue le rôle d'un textViewSystemFormatter ...
    A chaque changement de ton texte, tu as la main par un outlet sur ton textview, ou sur l'ensemble de l'UI pour réagir ...
  • Philippe49Philippe49 Membre
    13:40 modifié #20
    dans 1218023433:

    Certes le formatter ne changerais que le texte.
    Pourquoi pas la solution du TextViewController ? Tu l'instancie et connecte dans le nib, il joue le rôle d'un textViewSystemFormatter ...
    A chaque changement de ton texte, tu as la main par un outlet sur ton textview, ou sur l'ensemble de l'UI pour réagir ...


    Tu peux peut-être même profiter de fonctionnalités de la classe NSObjectController
  • ChachaChacha Membre
    13:40 modifié #21
    Bonne nouvelle, j'ai fini par trouver une solution qui ressemble à  ce que j'imaginais.
    Mauvaise nouvelle, je ne la comprends pas vraiment.
    La voici :
    Dans ma sous-classe de NSTextView, je surcharge bind:toObject:withKeyPath:options: pour y effectuer un addObserver:forKeyPath:options:context: pour la clef qui m'intéresse. Ainsi, je peux surcharger observeValueForKeyPath:ofObject:change:context.

    Mes problèmes :
    1)Surcharger bind:toObject:... paraà®t logique pour intercepter la création des bindings déclarés dans IB, mais je ne comprends pas pourquoi le addObserver:... n'est pas implicite dans l'implémentation par défaut de bind:toObject:...
    Je suspecte une histoire d'objets proxy du style NSNotyfyingKVO_... que j'ai déjà  aperçus dans le debbugger.

    2)De plus, pour que ça marche, il semblerait que je ne dois pas appeler [super bind:toObject:...] pour la clef pour laquelle je fais le addObserver: (cela génère des erreurs au run-time). Et ça, ça me paraà®t encore moins logique.

    <br />-(void) bind:(NSString*)binding toObject:(id)observableController withKeyPath:(NSString*)keyPath options:(NSDictionary*)options<br />{<br />&nbsp; if ([binding isEqualToString:@&quot;attributedString&quot;])<br />&nbsp; &nbsp; [observableController addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];<br />&nbsp; else<br />&nbsp; &nbsp; [super bind:binding toObject:observableController withKeyPath:keyPath options:options];<br />}<br /><br />-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context<br />{<br />&nbsp; [[self syntaxColouring] recolourCompleteDocument];<br />&nbsp; //...<br />}<br />//end observeValueForKeyPath:<br /><br />
    


    +
    Chacha
  • Philippe49Philippe49 Membre
    13:40 modifié #22
    dans 1218091656:

    2)De plus, pour que ça marche, il semblerait que je ne dois pas appeler [super bind:toObject:...] pour la clef pour laquelle je fais le addObserver: (cela génère des erreurs au run-time). Et ça, ça me paraà®t encore moins logique.

    <br />-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context<br />{<br />&nbsp; [[self syntaxColouring] recolourCompleteDocument];<br />&nbsp; //...<br />}<br />//end observeValueForKeyPath:<br />
    



    Tu y as sans doute pensé, mais le problème ne serait-il pas que la méthode recolourCompleteDocument changeant les attributs du texte, le binding est réactivé par un setter correspondant, créant ainsi une boucle infinie ?
  • ChachaChacha Membre
    août 2008 modifié #23
    dans 1218127188:

    Tu y as sans doute pensé, mais le problème ne serait-il pas que la méthode recolourCompleteDocument changeant les attributs du texte, le binding est réactivé par un setter correspondant, créant ainsi une boucle infinie ?

    Non non, l'erreur qui s'affichait semblait plutôt due à  une confusion dans les envois de messages.
    Je vois que toi aussi ça te paraà®t bizarre, je ne suis donc pas fou !

    Mais en réalité, c'est maintenant corrigé, et la forme qui marche est bien celle-ci :

    -(void) bind:(NSString*)binding toObject:(id)observableController withKeyPath:(NSString*)keyPath options:(NSDictionary*)options
    {
      [super bind:binding toObject:observableController withKeyPath:keyPath options:options];
      if ([binding isEqualToString:@attributedString])
        [observableController addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
    }

    -(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
    {
      [[self syntaxColouring] recolourCompleteDocument];
      //...
    }
    //end observeValueForKeyPath:


    Apparemment, mon .NIB avait un problème. Des erreurs bizarres m'ont finalement fait remarquer que j'étais dans le cas où InterfaceBuilder sauvegarde les bindings dans un ordre indéterminé (dans des cas d'interdépendances complexes). Parce que en resauvegardant, des fois ça marchait ! http://lists.apple.com/archives/xcode-users/2008/Jul/msg00967.html
    Je m'en suis sorti en virant les bindings de IB et en les créant à  la main dans le code.

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