Accueil > Programmation Cocoa > Donner plus d’éclat à votre application Cocoa

Donner plus d’éclat à votre application Cocoa

Par Mike Beam, le 14/06/2002

traduit par Thierry, le 10/01/2003

De nombreuses classes Cocoa reposent sur des délégués qui donnent accès à des variétés de comportements de la classe. NSApplication est une de ces classes—une qui fournit de nombreuses fonctions que nous sommes en droit d’attendre de toute application, mais dont nous ignorons comment les implémenter dans Cocoa.

Nous allons aujourd’hui nous pencher en détail sur deux méthodes déléguées de NSApplication qui nous permettent de spécifier si l’application doit ou ne doit pas présenter à l’utilisateur un document sans titre au moment de son initialisation, et qui apporte les moyens à l’application d’ouvrir des documents dont les icônes ont été déposées sur la sienne.

En plus, dans le cadre des efforts fournis pour donner plus d’éclat à ImageApp, nous allons apprendre comment nous pouvons contrôler le titre de la fenêtre du document de manière à afficher des informations arbitraires. Finalement, nous allons doter ImageApp d’un panneau “A propos de” personnalisé.

Le Délégué Application

Le délégué application contrôle les comportements décrits ci-dessus en implémentant deux méthodes particulières : -application:openFile:, et -applicationShouldOpenUntitledFile:. La première indique à l’application comment ouvrir les fichiers déposés sur son icône ; la seconde indique à l’application si elle doit ouvrir un document initial sans titre.

La dernière est surtout importante dans une application comme la nôtre dont le but n’est pas de créer des contenus, mais plutôt d’en visualiser. En d’autres mots, une toile vierge serait superflue sans les moyens de peindre dessus.

La première chose que vous allez probablement avoir envie de savoir est comment assigner un objet en tant que délégué à notre instance de NSApplication. Sans surprise, l’assignation d’un délégué de NSApplication est effectuée de la manière la plus simple dans Interface Builder (IB). Parmis les deux nibs dont nous disposons, MainMenu.nib est le seul qui ait une quelconque relation avec NSApplication, du fait que NSApplication est le possesseur des nib (autrement dit, la classe du File’s Owner—Possesseur de fichier—dans le nib est NSApplication). Nous allons travailler avec ce nib.

Ouvrez MainMenu.nib dans Interface Builder, et sous l’onglet Classes créez une sous-classe de NSObject. Nommez cette sous-classe AppController, et instanciez la. Sous l’onglet Instances connectez AppController à l’outlet déléguée de File’s Owner. Avec cela nous avons désigné AppController comme le délégué application officiel de ImageApp. Pas mal.

Maintenant double-cliquez sur AppController pour revenir au panneau Classes, et créez les fichiers pour AppController de façon à ce que nous puissiions travailler avec eux dans Project Builder. Sauvegardez votre travail et retournez dans Project Builder.

En général, l’implémentation de -applicationShouldOpenUntitledFile: peut être aussi simple que retourner YES ou NO en réponse à la question posée dans le nom de la méthode, ou cela peut être aussi compliqué que de rechercher une valeur utilisateur par défaut et de retourner un booléen basé sur cette valeur stockée. Des schémas plus compliqués sont possibles, mais n’ont pas d’intérêts pratiques dans la plupart des cas. Par défaut, les applications de gestion de document n’ouvre pas de documents sans titre, donc pour interdire ce comportement nous faisons ceci :

- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)theApplication {
   return NO;
}

La deuxième méthode déléguée que nous allons implémenter est -application:openFile:. Cette méthode va permettre à l’application d’ouvrir un fichier déposé sur son icône soit dans le Finder, soit dans le Dock, à condition que nous l’implémentons correctement. Donc comment est ce que nous contrôlons programmatiquement la création de tels documents ? Bien, avec NSDocumentController, bien sûr !

Deux articles auparavant, nous avons appris que NSDocumentController jouait un rôle critique dans les systèmes Cocoa multi-documents en tant que fabrique de document. En fait, NSDocumentController sait comment répondre aux actions transmises par les commandes de menu Ouvrir et Nouveau fichier. Nous pouvons bien sûr invoquer nous mêmes les méthodes de création et d’ouverture de fichier dans NSDocumentController, ce qui est le chemin que nous allons emprunter pour implémenter -application:openFile:.

Tout ce dont nous avons besoin pour charger le contenu d’un fichier dans un document est fourni par le chemin d’accès au fichier dans -application:openFile:. Jetons un oeil et voyons comment nous accomplissons ceci :

- (BOOL)application:(NSApplication *)theApplication
   openFile:(NSString *)filename {
   NSDocumentController *dc;
   id doc;
   dc = [NSDocumentController sharedDocumentController];
   doc = [dc openDocumentWithContentsOfFile:filename display:YES];
   return ( doc != nil);
}

Toute application de gestion de document comporte un contrôleur de document, que nous obtenons en utilisant la méthode +sharedDocumentController. Ensuite nous envoyons un message -openDocumentWithContentsOfFile:display: au contrôleur de document et nous assignons la variable doc à l’objet document retourné.

Cette méthode prend le chemin d’accès au document, fourni par le paramètre openFile: de la méthode délélguée, crée une nouvelle instance de MyDocument, et indique à cet objet de charger le contenu du fichier en tant que données du document. Si le contrôleur de document n’a pas été capable de charger le fichier, alors nil est retourné. Nous pouvons utiliser la valeur de doc pour tester si l’opération s’est bien passée.

L’argument display: indique à l’objet document s’il doit créer l’interface vers le document—c’est à dire la fenêtre. Si vous vous demandez pourquoi vous ne voudriez pas montrer l’interface du document, considérez les cas où ImageApp est exécuté par autre chose qu’un utilisateur, disons un AppleScript. AppleScript n’a sûrement pas besoin de voir le contenu du document à l’inverse d’un humain. Tout ce dont il a besoin est de créer une instance de la sous-classe NSDocument pour le document, et d’effectuer des opérations dessus.

Cela nous mène dans des notions de conception applicative en relation avec le support de la scriptabilité, dans lesquelles nous plongerons une autre fois. Un des principes basiques de la conception d’applications scriptables veut que la capacité à opérer sur les données d’un document ne doit pas dépendre de la présence de l’interface utilisateur du document—le document ne doit pas avoir besoin d’afficher son contenu pour être manipulé. Vous devriez garder ceci à l’esprit lorsque vous construisez vos classes de document ; cela vous facilitera la vie si d’aventure vous souhaitiez rendre votre application scriptable.

Bien, c’était une longue parenthèse pour dire que la réponse à la question relative à l’affichage pourrait être négative. Pour en finir avec l’implémentation de cette méthode nous vérifions qu’un objet est assigné à doc, et nous retournons un BOOL indiquant le succès de l’opération d’ouverture. Voila (sic) ! Compilez le, lancez le et voyez comment il fonctionne.

NSApplication déclare beaucoup d’autres méthodes déléguées intéressantes que vous jugerez peut-être utiles pour votre application. Nous ne les passerons pas toutes en détail, mais laissez moi vous donner un aperçu. La méthode -applicationDockMenu: retourne un objet NSMenu qui sera affiché en tant que menu dock de l’application. C’est utile si vous souhaitez que votre application supporte un menu dock dynamique qui change en fonction de l’état de l’application. Le menu retourné peut être créé par programme, ou il peut être chargé à partir d’un nib.

Une autre méthode déléguée que certains d’entre vous jugeront utile et intéressante est -applicationShouldTerminateAfterLastWindowClosed:. Par défaut, les applications Cocoa restent actives lorsque la dernière fenêtre a été fermée. Cependant, si vous construisez un utilitaire, vous pourriez suivre l’exemple de la majorité des utilitaires Apple et faire en sorte que cette méthode retourne YES.

Ainsi, lorsque la dernière fenêtre est fermée, l’application quittera. Maintenant, n’abusez pas de cette méthode. Les utilisateurs Mac ne s’attendent pas à ce que leurs applications quitte lorsque la dernière fenêtre est fermée, et seraient probablement mécontents si des applications se comportaient ainsi. C’est particulièrement vrai pour des applications de gestion de documents, mais pas pour des applications lancées brièvement dans le but d’observer des données systèmes, comme la plupart des utilitaires.

Il y a aussi de nombreuses méthodes déléguées qui sont envoyées en réponse à l’application qui est en train de devenir l’application courante, ou se démettent de ce statut ; il y en a de nombreuses envoyées à l’application qui est masquée ou affichée ; de telles méthodes sont même invoquées en réponse au lancement ou à l’arrêt d’une application.

Les méthodes en rapport avec le lancement d’applications peuvent être utiles si vous souhaitez implémenter un écran d’accueil. Par exemple, la méthode -applicationDidFinishLaunching: pourrait être utilisée pour fermer une fenêtre servant d’écran d’accueil.

Beaucoup de ces méthodes déléguées gèrent aussi l’envoi de notifications vers le centre de notification par défaut lorsque elles sont invoquées. Par exemple, si vous souhaitiez exécuter du code en réponse au passage à l’état d’application courante, vous pourriez implémenter dans le délégué application la méthode -applicationDidBecomeActive:, ou vous pourriez enregistrer un objet sans relation dans votre application pour répondre à la notification NSApplicationDidBecomeActiveNotification.

L’avantage des notifications tient dans le fait que vous n’avez pas à explicitement déclarer un objet en tant que délégué de l’application ; vous n’avez qu’à enregistrer l’objet pour répondre à n’importe quelle notification qui vous intéresse.

Voilà donc l’histoire des méthodes déléguées de NSApplication. Passons maintenant à la personnalisation du titre de la fenêtre du document.

Changer le titre de la fenêtre du document

Les fenêtres classiques affichent en tant que titre le dernier composant du chemin d’accès sous lequel le document est sauvegardé—en fait, le nom du document. Ainsi, un document sauvegardé sous /Users/mike/Pictures/pigs.jpg affiche un titre de fenêtre équivallent à “pigs.jpg.”

Il est possible, cependant, de personnaliser le titre de la fenêtre de façon à afficher des données en plus (ou en remplacement) du nom du document. Nous voyons ce type de chose dans Photoshop où l’échelle en cours et l’espace colorimétrique sont ajoutés au nom du document dans le titre de la fenêtre.

En surpassant une méthode de NSWindowController, il n’est pas difficle de mettre ce que l’on veut dans le titre de la fenêtre. La méthode à surpasser est -windowTitleForDocumentDisplayName:, qui retourne une chaîne à afficher en tant que titre de la fenêtre et dont le seul argument est le nom du document.

En guise d’exemple de ce que l’on peut faire, implémentons cette méthode de façon à ajouter l’échelle courante au nom du document de la manière suivante :

- (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName {
   return [displayName stringByAppendingFormat:@" @ %2.2f%%", scale * 100];
}

Si vous ne comprenez pas le formattage utilisé dans la chaîne de caractère ci-dessus, vous devriez peut-être en lire un peu plus sur la fontion printf (en utilisant votre livre favori sur le C ou en tapant man printf dans le Terminal). Pour résumé, nous indiquons que le nombre doit être affiché en tant que float avec deux décimales, suivies par le signe pourcent. (Puisque “%” est utilisé en guise de caractère d’échappement, nous devons le faire précéder d’un autre “%” pour qu’il apparaisse).

Le titre de fenêtre produit par cette méthode ressemble à cela :

Screen shot. Le titre de fenêtre produit par la méthode du dessus.

Maintenant, il y a un problème dans cette méthode. Lorsque nous changeons l’échelle de l’image, nous voudrions que le titre reflète ces changements. Nous devons indiquer au contrôleur de fenêtre que l’état du contenu de la fenêtre a changé et que ce changement d’état doit être répercuté sur les informations affichées dans le titre de la fenêtre. NSWindowController fournit une méthode pour forcer une mise à jour du titre de la fenêtre, -synchronizeWindowTitleWithDocumentName. Puisque le facteur d’échelle est affiché dans le titre, nous voulons le mettre à jour lorsque l’échelle change. Le -scaleImageTo: est le plus petit dénominateur commun de toutes les opérations de mise à l’échelle, nous allons donc mettre cette dernière ligne dans cette méthode :

[self synchronizeWindowTitleWithDocumentName]

Avec ceci, nous avons ajouté un peu plus d’éclat à notre application. Pour terminer aujourd’hui nous allons ajouter un panneau “A propos de” personnalisé à notre application.

Un Panneau “A propos de” Personnalisé

Au moment de construire notre panneau “A propos de”, nous allons nous servir d’un nib dédié à ce seul but. La raison principale pour laquelle vous allez placer la fenêtre “A propos de” dans un nib séparé tient dans le désir d’améliorer le temps de lancement de votre application, tout autant que celui de diminuer la capacité mémoire requise. Lorsque l’application démarre, elle doit charger le contenu complet de MainMenu.nib en mémoire, donc, en plaçant la fenêtre “A propos de” dans un nib séparé, l’application a un objet de moins à charger en même temps que MainMenu.nib. Le nib contenant le panneau “A propos de” n’est pas chargé tant que l’utilisateur ne veut pas le voir.

Bien sûr, pour notre relativement petite application et son petit panneau “A propos de”, un nib séparé n’apportera pas d’améliorations significatives. Cependant, la technique mérite d’être apprise et pratiquée pour le jour où votre applications deviendra plus importante et plus complexe.

Dans Interface Builder, créez un nouveau nib à partir de File–>New… ; dans le dialogue de départ choisissez “Empty” dans la liste des modèles. Comme le nom le suggère, cela va créer un fichier nib vide, auquel vous allez maintenant ajouté une fenêtre. Placez le texte et les images dans le panneau pour refléter les informations que vous souhaitez afficher. Il ne devrait y avoir aucun élément de contrôle tels que des boutons ou des curseurs dans notre implémentation.

Sauvegardez le nib sous AboutPanel.nib, et placez le dans le dossier English.lproj (NdT : ou French.lproj si votre projet est en français) de votre dossier projet ImageApp. Il vous sera alors demandé de choisir vers quel cible ajouter le nib. Choisissez ImageApp. J’ai constitué un panneau simple sans information tel qu’illustré ci-dessous :

Screen shot.
Sûrement pas le meilleur panneau “A propos de” qu’une application puisse avoir.

Pour ouvrir la fenêtre en réponse au choix utilisateur de l’article de menu “A propos de ImageApp”, nous avons besoin d’une classe contrôleur pour le nib du panneau “A propos de” doté d’une outlet au travers de laquelle nous pouvons communiquer avec la fenêtre “A propos de”. C’est à dire, de manière à ce que nous puissions envoyer un message à la fenêtre pour lui dire qu’elle s’affiche.

Comme vous pouvez le voir, AboutPanel.nib n’a actuellement aucun objet capable de remplir cet objectif. Pour remplir ce rôle, nous employons les services de notre classe AppController. Avec quelques modifications elle peut être configurée pour jouer le double rôle de délégué application et de contrôleur de notre panneau “A propos de”. Pour préparer AppController à son nouveau job, ajoutez la déclaration d’outlet suivante au fichier interface :

IBOutlet NSWindow *aboutWindow;

Sauvegardez les changements apportés à ce fichier et déposez le sur AboutPanel.nib pour ajouter AppController au répertoire nib des classes.

Maintenant, nous n’allons pas créer une instance de AppController dans le nib du panneau “A propos de” comme nous l’avons fait pour MainMenu.nib—notre application n’a besoin que d’une seule instance de cette classe. A la place, nous allons faire en sorte que AppController soit la classe File’s Owner de AboutPanel.nib. Cela me mène à un apparté sur le File’s Owner.

Une chose importante à garder en mémoire concernant le File’s Owner est que ce n’est pas une instance de la classe que nous avons assignée au File’s Owner. La nature de File’s Owner est assez différente de celle de l’objet fenêtre, ou de l’instance de AppController dans MainMenu.nib. Ceux-ci sont de réelles instances de classes sauvegardées sur le disque puis rechargées en mémoire quand le nib est chargé.

Tel est le prodige d’Interface Builder, de Cocoa et des fichiers nib. On fait communément référence à cela en tant qu’exemple de la faculté de Cocoa à “congeler à sec” les objets (ce qui est accomplit en utilisant les classes NSArchiver et NSUnarchiver. Nous couvrirons ces classes plus tard).

File’s Owner, d’un autre coté, est un objet proxy : un objet qui représente un lien vers une instance de sa classe désignée qui existe quelque part au moment de l’exécution. Nous assignons une classe à un File’s Owner de façon à pouvoir établir des connexions significatives qui deviendront de réelles connexions entre objets lorsque le nib sera chargé au moment de l’exécution. Ces connexions n’existent pas encore dans le nib, comme le sont celles entre des instances réelles. Nous verrons dans un moment comment nous indiquons au nib au moment de l’exécution quel objet est réellement le File’s Owner.

Avant d’en arriver là, faites une connexion entre la fenêtre et l’outlet du panneau “A propos de” du File’s Owner. Sauvegardez votre travail dans AboutPanel.nib, et retournons dans Project Builder pour faire une méthode qui charge le nib et ouvre la fenêtre.

La prochaine étape consiste à établir un point pour y accéder. En fait, nous devons écrire une méthode qui chargera le nib contenant la fenêtre et ouvrira la fenêtre en réponse à la sélection par l’utilisateur de la commande de menu “A propos de ImageApp”. Cette méthode ira dans AppController, ce qui est commode puisque l’instance de AppController fait partie de MainMenu.nib où nous pouvons facilement connecter cette action à la commande de menu en question.

Pour mettre ceci en place, ajoutez cette déclaration de méthode à AppController.h :

- (IBAction)showAboutPanel:(id)sender;

Ouvrez MainMenu.nib dans Interface Builder et faites y une lecture de AppController.h pour mettre à jour les informations d’Interface Builder à propos des méthodes disponibles de AppController. Ensuite, sélectionnez “About NewApplication” à partir du menu NewApplication, et changez son nom en “A propos de ImageApp” (changez aussi tout autre NewApplication en ImageApp). Ensuite, ouvrez le Connections Info Panel et déconnectez sa connexion actuelle vers File’s Owner. La connexion est faite par défaut vers une action de NSApplication qui par ouvre le panneau standard Cocoa “A propos de”.

Maintenant glisser-déposez une nouvelle connexion à partir de l’élément de menu vers AppController, et faites une connexion vers l’action showAboutPanel:. Sauvegardez les changements de MainMenu.nib, et revenez dans Project Builder.

Nous voyons maintenant la manifestation dans le code de notre longue discussion sur le File’s Owner. Rappelez-vous, j’ai dit que le File’s Owner n’était pas un objet réel tant que nous n’avions pas chargé le nib et assigner un objet au rôle de owner. Pour effectuer cela, nous utilisons une méthode de NSBundle appelée -loadNibNamed:owner:. Cela devrait sonner comme un déclic ; le second argument est owner:.

L’objet que nous passons comme argument est celui vers lequel toutes nos connexions factices vers File’s Owner deviendront réelles. La seule restriction portant sur l’objet passé ici est qu’il doit être de la même classe que celle que nous avons assignée, dans le nib, au File’s Owner. En ce qui nous concerne, nous allons passer self, qui est l’instance de AppController dans MainMenu.nib. Le premier argument est le nom du nib que nous souhaitons charger, AboutPanel dans notre cas.

Jetons un oeil à l’implémentation de -showAboutPanel:

- (IBAction)showAboutPanel:(id)sender {
   if ( !aboutWindow ) 	[NSBundle loadNibNamed:@"AboutPanel" owner:self];
   [aboutWindow makeKeyAndOrderFront:nil];
}

Vous voyez ici que la méthode de chargement du nib est conditionnée dans une instruction if. Tout ceci pour dire que nous souhaitons charger le nib que si aboutWindow ne point pas vers un objet. Avant de charger le nib, aboutWindow est nul. Lorsque nous chargeons le nib avec self en tant que possesseur, la connexion entre la fenêtre et l’outlet de aboutWindow est actualisée, et aboutWindow pointe vers un objet réel.

Les appels suivants à -showAboutPanel: éviteron le code de chargement puisque le nib sera déjà chargé en mémoire. Finalement, nous affichons la fenêtre en envoyant un message makeKeyandOrderFront: à aboutWindow. Et c’est fini !

Quand nous avons démarré cette section, j’ai indiqué que je ne souhaitais pas mettre des éléments de contrôle d’interface dans le panneau “A propos de”. Ce n’était que dans un but de simplicité. Tout ce que vous avez à faire est de déclarer les actions que vous souhaitez que les éléments de contrôle invoquent dans AppController, et de faire les connexions au travers du File’s Owner.

Si vous avez beaucoup d’éléments de contrôle, vous aurez alors peut-être intérêt à créer une classe dédiée AboutPanelController. Si vous choisissez cette voie, suivez l’exemple de l’utilisation de AppController en tant que contrôleur du panneau “A propos de”. C’est à dire, créez et instanciez la classe contrôleur dans MainMenu.nib de façon à pouvoir y connecter la commande de menu “A propos de …”, puis faites en le File’s Owner du AboutPanel.nib.

La Fin

Ainsi, voilà plusieurs choses que vous pouvez faire pour donner plus d’éclat à votre application avant de la lacher dans la nature. J’espère que vous avez plein d’idées pour vous lancer dans l’aventure quelque soit votre application. Dans le prochain article nous allons explorer la classe NSBitmapImageRep, et nous apprendrons comment travailler sur des images pixel par pixel en construisant une classe qui convertira la couleur en dégradés de gris.

Textes originaux en anglais sur O’Reilly : Donner plus d’éclat à votre application Cocoa par Mike Beam

Thierry Programmation Cocoa , , ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
Vous devez être identifié pour poster un commentaire