Comment définir le First Responder à l'exécution?
Bonjour,
Je galère un peu dans la "chaà®ne des répondeurs" Cocoa. Quelle est la manière la plus efficace de procéder lorsque, dans la même fenêtre, j'ajoute et je retire des NSViews à la volée et que je souhaite rétablir la chaà®ne?
La méthode [NSWindow setNextResponder: myViewController]; va fonctionner lorsque j'insère la NSView dans la fenêtre. Cette NSView vient cacher celle qui est à l'arrière-plan et qui, donc, ne recevra plus les évents. Mais lorsque je retire cette NSView, la chaà®ne de commande est brisée.
Comment la rétablir? Est-ce possible, souhaitable, ou pas très malin de faire ceci:
interface MyViewController : NSViewController
@property NSResponder *previousResponder;
...
self.previousResponder = [self.view window]nextResponder;
[[self.view window]setNextResponder: self];
...
- (void) dealloc
{
[[self.view window]setNextResponder: self.previousResponder];
}
Cette façon de faire ne me donne pas entièrement satisfaction: parfois, les menus ne sont pas accessibles, alors que dans IB ils sont connectés au First Responder adéquat.
Que fais-je de faux ?
D'avance merci.
Réponses
et [window setNextResponder:nil] ça donne quoi ?
Je dirais qu'il ne faut pas toucher à la chaà®ne des répondeurs dans une arborescence de vues. Le répondeur suivant est toujours la supervue dans Cocoa. On a juste le droit de définir le répondeur suivant de la fenêtre pour "sortir" de l'arborescence de vues.
En principe, c'est la fenêtre qui recevra les events. Mais je suis trop "haut" dans la chaà®ne.
Cela marcherait probablement si j'avais
NSWindow --> NSView --> NSView
mais là j'ai
NSWindow -->NSView
"-->NSView
Tu confondrais pas avec makeFirstResponder ?
Et si tu faisais: MyView->MyViewController
Si j'ai bien suivi, setNextResponder est une méthode commune aux NSResponder (dont NSView, NSWindow héritent). En revanche, makeFirstResponder est une méthode de fenêtre. C'est entre les deux que je m'embrouille. Quand les appeler (ou ne pas les appeler)...
Pour être précis, j'ai une fenêtre et deux NSViewControllers. Ils ont une durée de vie égale à la session -- en fait, ce sont deux "shared instances" que je peux joindre de partout.
L'un d'eux, appelons-le BackgroundViewController, crée sa vue par code au lancement de l'application. Sa vue est insérée dans la fenêtre et y demeure jusqu'à la fin.
L'autre, que je nommerai FrontViewController, affiche sur demande une vue (pas toujours la même) chargée par nib et la place dans la fenêtre, "devant" la vue du BackgroundViewController. Sur demande également, il retire sa vue de la fenêtre (removeFromSuperview), et la met à NIL pour plus de sûreté.
J'ai des commandes de menu qui doivent être actives quand FrontViewController affiche sa vue, inactives lorsqu'il la retire. L'une des vues de FrontViewController peut également recevoir des keyEvents.
Mon problème est celui de la transition, car l'activation des commandes ne se fait pas correctement. Je n'ai aucune erreur ni avertissement, mais je vois bien que la chaà®ne des répondeurs n'est pas correcte.
Comme le dit fort justement jpimbert, "on ne touche pas à la suite des contrôleurs". C'est vrai que l'AppKit fait très bien son travail et que c'est la première fois que j'ai à me pencher sur cette particularité -- la faute à ma gestion folklorique des vues.
D'un autre côté, si makeFirstResponder et setNextResponder existent, ce n'est sûrement pas pour rien...
Pour l'instant j'essaie de résoudre à l'aide du makeFirstResponder de NSWindow. Quand FrontViewController affiche sa vue, il passe:
[[self.view window]makeFirstResponder: self];
et juste avant de retirer sa vue, il passe:
self.view window]makeFirstResponder: [BackgroundViewController sharedInstance;
FrontViewController parvient à "prendre le commandement" mais le renvoi d'ascenseur à BackgroundViewController se passe mal.
C'est cette partie du code qui fait problème.
Procédons logiquement.
Soit un viewController et sa view
Lors de l'ajout de la vue, faire:
et lors du retrait, faire la même chose mais
viewController = le backGroundViewController
view = la superView
Mmh... résultat de cette logique:
0x7fff99d11f44: callq *10209598(%rip) EXC_BAD_ACCESS (code=2, address=0x7fff5f3ffff8)
0x7fff99d11f4a: cmpq $27, %rax
0x7fff99d11f4e: ja 0x7fff99d1203a ; -[NSView _nextResponderForEvent:] + 421
Au risque de me répéter, je ne me risquerais pas à trifouiller la chaà®ne de répondeurs dans une arborescence de vues.
J'ai fais un test avec un ViewController et un AppController et pas de plantage.
J'ai une méthode action similaire dans les deux controllers.
Après l'ajout de la vue, c'est celle-ci qui reçoit l'action.
Et après le retrait c'est de nouveau AppController qui la recoit.
Chez toi ça plante car tu as un responder qui est releasé.
Maintenant, pour aller dans le sens de jpimbert, si tu veux pas de probléme utilise un NSTabView (sans tabs visibles) pour afficher tes différentes vues.
mpergand,
ce serait une excellente idée si je n'avais pas un effet de transition entre les vues (la vue frontale glisse vers le haut avant d'être retirée).
jpimbert,
ok, je ne trifouille pas... et adieu les clics, les menus et les équivalents-clavier...?
PS : de l'eau au moulin de ceux qui flanquent tout dans l'appDelegate... <_<
Pour les clics, la chaà®ne de répondeurs définie par défaut, et basée sur la hiérarchie des vues, marche très bien : ne surtout pas trifouiller.
Pour les menus et les équivalents-claviers, la hiérarchie de vues n'est pas concernée. Tu peux trifouiller la chaà®ne des répondeurs sur les contrôleurs de vue, par sur les vues.
Il me semble qu'on y est obligé quand on utilise un NSViewController. En effet, il est assez logique que le view controller soit le next responder de sa vue (c'est d'ailleurs ce qui se passe sous iOS), mais la placer en subview d'une autre vue fait que celle-ci est son next responder.
jpimbert,
A l'origine, j'avais un système qui marchait cahin-caha. Le switch ne se faisait qu'entre les contrôleurs (j'essaie de coller au pattern MVC: les vues reçoivent les clics, les contrôleurs reçoivent les commandes).
J'avais essayé de faire d'une vue le FirstResponder, plus rien ne marchait correctement, j'ai donc stoppé net.
Pour le reste, la doc dit: lorsque on insère une vue par code, elle ne reçoit pas les événements, à moins de surcharger acceptFirstResponder... je crois.
@Céroce (réponse pendant que j'éditais): voilà où est l'os alors. Dois-je faire un setNextResponder:self dans le contrôleur après l'insertion de la vue?
Oui, quelque chose comme ça, il faut que le VC s'intercalle entre la vue et la vue parente.
Note que c'est le même problème quand on retire la vue: il faut retirer le VC de la chaà®ne.
J'ai compris le problème parce que mon VC n'interceptait pas les raccourcis-clavier comme je m'y attendais.
Personnellement, j'ai une sous-classe de NSViewController qui possède des méthodes du genre -addViewToView: et -removeViewFromSuperview, qui en plus de faire des addSubview: et removeFromSuperview, intercallent le VC dans la chaà®ne des répondeurs.
Merci Céroce, si un contrôleur d'arômes se donne la peine de définir de telles méthodes, c'est que ma gestion des vues n'est pas si alambiquée. Je vais creuser de ce côté-là .
Pour tout dire, je programme cette appli en OSX en ayant en tête sa "portabilité" sur iOS... (ou qui sait, sous OSX 10.n "SantaMonica" avec écran tactile)...
Ouh, là ... je suis contrôleur d'arômes parce que je viens souvent, depuis longtemps et que je m'entends bien avec muqaddar, ça n'a pas grand chose à voir avec un quelconque talent.
J'essaie juste de faire part de mon expérience, après ça m'arrive souvent de me tromper aussi.
Cette modestie t'honore. +1