Accueil > Programmation Cocoa > Créer des barres d’outils pour Mac OS X

Créer des barres d’outils pour Mac OS X

Par Mike Bean, le 15/03/2002

Traduit par Olivier, le 06/11/2002

Aujourd’hui nous allons continuer notre découverte de la nouvelle interface Aqua avec les barres d’outils. Les barres d’outils sont un apport important à l’interface Macintosh. Elles offrent une interface cohérente à travers toutes les applications et sont très personnalisables par les utilisateurs, allégeant le fardeau du développeur.

Pour étudier les barres d’outils, nous allons revoir une vieille application — notre bon vieux carnet d’adresses. Si vous n’avez pas encore lu ces articles, ne vous inquiétez pas, nous l’utilisons juste comme un point de départ pour les événements d’aujourd’hui et vous ne devriez avoir aucun problème pour suivre. Vous pouvez télécharger le dossier du projet que nous allons utiliser aujourd’hui ici.

Vue d’ensemble

Dans l’article d’apprentissage de Cocoa, nous avons vu que les éléments complexes d’interface utilisateur utilisent de nombreux objets travaillant en tâche de fond pour les contrôler. Rappelez-vous les vues tabulaires qui utilisent des objets data source (liés à la base de données) pour afficher leur contenu.

Les barres d’outils ont également besoin d’un objet qui fournit à la barre d’outils les éléments à afficher, ainsi qu’une liste d’identifieurs d’éléments pour que la barre d’outils sache quels éléments sont disponibles. Ces contrôles sont des instances de la classe NSToolbarItem, et sont des éléments de la barre d’outils. Chaque éléments de la barre d’outils est identifié par une chaîne de caractères unique que la barre d’outils utilise pour garder une trace de chaque élément. Cet objet est le délégué de la barre d’outils qui sera initialisé par notre objet existant NSController.

Si nous nous arrêtons un moment pour étudier le comportement des barres d’outils, nous arrivons à cette conclusion : les barres d’outils contiennent un ensemble de contrôles. Cet ensemble de contrôles est personnalisable par l’utilisateur via la palette de personnalisation. La palette contient une collection de tous les contrôles disponibles qui peuvent être placés dans la barre d’outils, ainsi qu’un ensemble prédéfini de contrôles qui définissent la barre d’outils par défaut.

La barre d’outil délègue l’implémentation des trois méthodes suivantes nécessaires pour supporter le comportement décrit ci-dessus :

  • -toolbarAllowedItemIdentifiers:
  • -toolbarDefaultItemIdentifiers:
  • -toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:

Notre barre d’outils aura les éléments personnalisables suivants, que nous ferons nous-même:

  • AddItem — un bouton qui ajoutera un nouvel enregistrement
  • RemoveItem — un bouton qui supprimera l’enregistrement sélectionné
  • SearchItem — un champs de recherche similaire à iTunes

De plus notre barre d’outils contiendra les éléments de barre d’outils suivant, fournit gratuitement par Cocoa :

  • NSToolbarSeparatorItemIdentifier
  • NSToolbarSpaceItemIdentifier
  • NSToolbarFlexibleSpaceItemIdentifier
  • NSToolbarCustomizeToolbarItemIdentifier

Trois autres élément de barre d’outils standard sont disponibles pour NSToolbarItem :

  • NSToolbarShowColorsItemIdentifier
  • NSToolbarShowFontsItemIdentifier
  • NSToolbarPrintItemIdentifier

L’image ci-dessous affiche ces sept éléments par défaut dans une barre d’outils d’une palette personnalisable.

Catégories

Comme mentionné plus haut, NSController sera utilisé comme le délégué de la barre d’outils, mais nous n’allons pas implémenter les méthodes du délégué dans Controller.m. Plutôt, nous allons ajouter une catégorie à Controller. Qu’est-ce qu’une catégorie dites-vous ? C’est essentiellement une extension de l’implémentation d’une classe. La syntaxe d’une catégorie n’est que légèrement différente de la syntaxe d’une classe. Créons un nouveau fichier pour notre catégorie et nommons le ToolbarDelegateCategory.h. Le fichier de l’interface pour une catégorie ressemble à cela :

#import <Cocoa/Cocoa.h>
#import "Controller.h"
@interface Controller (ToolbarDelegateCategory)
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
  itemForItemIdentifier:(NSString *)itemIdentifier
  willBeInsertedIntoToolbar:(BOOL)flag;
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar;
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar;
@end

Vous remarquerez que le bloc de code pour les déclarations de variables d’instance est absent. C’est parce que les catégories ne peuvent pas ajouter de variables d’instances à une interface de classe, seulement des méthodes. Notez également comment nous définissons la catégorie. Le nom de la catégorie suit le nom de la classe entre parenthèses. Enfin, quand une catégorie est déclarée, vous devez importer l’interface de la classe si vous souhaitez utiliser une des variables de l’instance de la classe dans les méthodes de votre catégorie, ce que nous allons faire.

Maintenant créez un autre fichier avec le même nom mais avec un “m” pour extension, dont le contenu est le suivant:

#import "ToolbarDelegateCategory.h"
@implementation Controller (ToolbarDelegateCategory)
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
   itemForItemIdentifier:(NSString *)itemIdentifier
   willBeInsertedIntoToolbar:(BOOL)flag { }
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar { }
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar { }
@end

Les catégories sont utiles pour différentes raisons. Les méthodes implementées dans le fichier catégorie sont équivalentes à celles implémentées dans la classe elle-même. Ainsi, il est possible d’étendre le comportement de n’importe quelle classe d’une façon limitée — mais souvent suffisante — sans rencontrer les problèmes engendrés par le sous-classement. Comme mentionné auparavant, les catégories sont limitées par le fait qu’elles ne peuvent pas ajouter de variables d’instance à la définition de la classe. Comme les sous-classes, les catégories peuvent surcharger les méthodes déclarées dans l’interface.

Les catégories sont utiles pour découpler de larges classes en plusieurs fichiers plus facilement gérables — réutilisation de code — et peuvent faire gagner du temps à la compilation. De plus, si votre catégorie est bien construite, il n’y a aucune raison pour laquelle vous ne pourriez pas réutiliser les fichiers dans d’autres projets avec un minimum de modifications. Pour plus d’informations sur les catégories, lisez le chapitre quatre de Object-Oriented Programming and the Objective-C Language, intitulé “More Objective-C“.

Nous allons commencer maintenant par l’implémentation des méthodes déléguées de la barre d’outils. La première que nous voulons implémenter est -toolbarAllowedItemIdentifiers:. Le rôle de cette méthode est de retourner à la barre d’outils un tableau de chaîne de caractères contenant tous les identifiants des éléments de la barre d’outils qui iront dans la barre d’outils. Pour le moment, nous allons simplement créer un tableau contenant les identifiants des quatres éléments standards de la barre d’outils nous avons vu que la barre d’outils devait avoir (nous ajouterons nos propres éléments après que tout fonctionne correctement avec les éléments standard). Remarquez que l’ordre des identifiants dans le tableau est celui dans lequel les éléments apparaitront dans la palette de personnalisation. Voici la première méthode :

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar {
return [NSArray arrayWithObjects:NSToolbarSeparatorItemIdentifier,
                                 NSToolbarSpaceItemIdentifier,
                                 NSToolbarFlexibleSpaceItemIdentifier,
                                 NSToolbarCustomizeToolbarItemIdentifier,
                                 nil];
}

Tout ce que nous avons fait est d’utiliser la méthode -arrayWithObjects: de NSArray pour créer le tableau qui sera retourné. En utilisant cette méthode, souvenez-vous que la liste des objets doit être terminée par nil. C’est tout.

La prochaine méthode que nous allons implémenter est -toolbarDefaultItemIdentifiers:. Cette méthode est semblable dans son fonctionnement à la méthode ci-dessus. La différence est qu’elle retourne un tableau d’identifiants pour les éléments qui constitueront la barre d’outils de défaut. Notre barre d’outils de défaut est constituée des éléments de la barre d’outils personnalisée placés tout à fait à droite, ce qui est réalisé en insérant un élément espace avant l’élément de la barre d’outils personnalisée. Encore une fois, l’ordre des identifiants dans le tableau définit l’odre des éléments dans la barre d’outils de défaut.

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar {
   return [NSArray arrayWithObjects:NSToolbarFlexibleSpaceItemIdentifier,
           NSToolbarCustomizeToolbarItemIdentifier, nil];
}

La troisième et dernière méthode que nous aurons besoin d’implémenter est -toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar: (que nous appellerons aussi -toolbar). Cette méthode contient l’essentiel de l’implémentation de la barre d’outils. C’est ici que nous créons vraiment les éléments de la barre d’outils.

Nous initialisons une instance de NSToolbarItem en utilisant la méthode -initWithIdentifier:. Dans cette méthode nous passons l’identifiant de l’élément de la barre d’outils donné par l’argument de itemforItemidentifier:. Si l’identifiant est un de ceux de la barre d’outils de défaut alors une instance de NSToolbarItem est initialisée, complètement configuré pour effectuer la fonction standard spécifiée.

Si l’identifiant est autre que l’un des identifiants standard, alors seul l’identifiant est initialisé. C’est au programmeur de configurer l’élément, ce que nous ferons bientôt pour nos éléments personnalisés. Pour le moment, préparons ce qu’il faut pour travailler juste avec les éléments standard :

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
   itemForItemIdentifier:(NSString *)itemIdentifier
   willBeInsertedIntoToolbar:(BOOL)flag {
       NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
       return [item autorelease];
}

Nous envoyons à l’élément (item) un message autorelease puisque la barre d’outils conserve l’élément qui est retourné par cette méthode. Nous n’en avons pas pour autre chose, donc nous pouvons laisser l’objet à la barre d’outils. La raison pour laquelle j’ai utilisé deux lignes de code plutôt qu’une seule est parce que nous allons ajouter des lignes de code entre ces deux lignes quand nous allons construire nos propres éléments de barre d’outils.

Maintenant que nous avons pris soin de la méthode déléguée, il y a encore une ou deux choses que nous devons faire avant que tout soit prêt. L’une d’elle est de créer l’instance de la barre d’outils. Nous allons le faire dans la méthode -awakeFromNib du Contrôleur en appelant la méthode appelée -setupToolbar, que nous implémentons dans notre catégorie de la façon suivante:

- (void)setupToolbar {
   NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
   [toolbar autorelease];
   [toolbar setDelegate:self];
   [toolbar setAllowsUserCustomization:YES];
   [toolbar setAutosavesConfiguration:YES];
   [mainWindow setToolbar:[toolbar autorelease]];
}

Ce que nous avons fait ici est de créer une nouvelle instance de NSToolbar et de l’initaliser avec initWithIdentifier:. L’identifiant de la barre d’outils peut être n’importe quoi. Il est utilisé en interne par NSToolbar, et nous n’avons aucune possibilité d’y accéder directement.

Dans les lignes suivantes, nous positionnons l’objet délégué de la barre d’outils à lui-même (self), et nous initialisons quelques options de la barre d’outils -la barre d’outils permet à l’utilisateur de la personnaliser, et sauvegarde automatiquement toutes modifications effectuées. Dans la dernière ligne de code nous envoyons un message setToolbar à mainWindow avec [toolbar autorelease] comme paramètre. Le principe que nous avons vu pour l’autorelease des éléments de la barre d’outils avant que nous les retournions s’applique ici — nous voulons laisser le contrôle de la barre d’outils à mainWindow.

La dernière chose que nous devons faire est de mettre en place un élément de menu qui permettra à l’utilisateur d’ouvrir le panneau de personnalisation. NSWindow nous permet cela par une méthode d’action appelée -runToolbarCustomizationPalette: qui ouvrira le panneau de personnalisation de la barre d’outils. Ce que nous ferons dans Interface Builder (IB) est d’utiliser mainWindow le destinataire du message d’action -runToolbarCustomizationPalette: envoyé par l’élément de menu “Customize Toolbar” que nous allons ajouter au menu Window.

A présent, dans IB ajoutez un élément de menu “Customize Toolbar…” (Personnaliser la barre d’outils…) au menu Window (Fenêtre) (vous pouvez créer les points de suspension avec la combinaison de touche option-point virgule). Connectez cet élément de menu à l’action -runToolbarCustomizationPalette: de votre fenêtre principale. Sauvegardez votre travail, compilez le projet, et vous êtes prêts pour continuer.

Quand l’application est démarrée pour la première fois vous devriez apercevoir l’élément de personnalisation de la barre d’outils complètement à droite. Vous devriez pouvoir lancer le panneau de personnalisation en cliquant sur cet élément ou en sélectionnant notre menu.

Les éléments de la barre d’outils personnalisée

L’implémentation les éléments le la barre d’outils personnalisée est un peu plus complexe que pour les éléments de la barre d’outils standard. Tout ce dont nous avons besoin est d’un peu de configuration après l’initialisation. Rappelez-vous que les trois éléments de la barre d’outils que nous allons créer auront les identifiants AddItem, RemoveItem et SearchItem. Pour supporter ces méthodes, ajouter ces trois chaînes de caractères aux tableaux retournés par -toolbarAllowedItemIdentifiers: et -toolbarDefaultItemIdentifiers:. La méthode -toolbarAllowedItemIdentifiers: devrait ressembler à :

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar {
   return [NSArray arrayWithObjects:NSToolbarSeparatorItemIdentifier,
           NSToolbarSpaceItemIdentifier,
           NSToolbarFlexibleSpaceItemIdentifier,
           NSToolbarCustomizeToolbarItemIdentifier,
           @"AddItem", @"RemoveItem, @"SearchItem", nil];
}

Et -toolbarDefaultItemIdentifiers: ressemble à :

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar {
   return [NSArray arrayWithObjects:@"AddItem", @RemoveItem", @SearchItem",
           NSToolbarFlexibleSpaceItemIdentifier,
           NSToolbarCustomizeToolbarItemIdentifier, nil]; }

Maintenant, revenons à la méthode où nous créons les éléments et voyons ce que nous avons à faire.

Quand nous l’avons laissée, -toolbar:itemForIdentifier:willBeInsertedIntoToolbar: était implémentée telle que:

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
   itemForItemIdentifier:(NSString *)itemIdentifier
   willBeInsertedIntoToolbar:(BOOL)flag {
       NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
       return [item autorelease];
}

Cela marche très bien pour les éléments standards parce que pour les implémenter, tout ce que nous avions à faire était d’initialiser une instance de NSToolbarItem avec les identifiants voulus, et nous obtenions un élément de barre d’outils complet. Maintenant nous avons des éléments non standard qui ont besoin de plus qu’une simple initialisation.

Pour réaliser cela, nous allons ajouter une série d’instructions if dans lesquelles nous comparerons itemIdentifier aux trois identifiants personnalisés. A l’intérieur de chaque bloc if, nous effectuerons le code de configuration spécifique à l’élément dont l’identifiant est semblable à itemIdentifier. Cette série d’instructions if sera insérée entre les deux lignes déjà existantes dans le code de -toolbar….

Soyons bien organisés et ajoutons dabord le squelette de la série de if à cette méthode :

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
   itemForItemIdentifier:(NSString *)itemIdentifier
   willBeInsertedIntoToolbar:(BOOL)flag {
       NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
       if ( [itemIdentifier isEqualToString:@"AddItem"] ) {
           // Configuration code for "AddItem"
       } else
       if ( [itemIdentifier isEqualToString:@"RemoveItem"] ) {
          // Configuration code for "RemoveItem"
       } else
      if ( [itemIdentifier isEqualToString:@"SearchItem"] ) {
         // Configuration code for "SearchItem"
      }
      return [item autorelease];
}

Comme nous l’avons mentionné auparavant, tout le code de configuration spécifique à chaque élément ira à l’intérieur du bloc if correspondant. L’implémentation des deux premiers éléments sera effectué entièrement par code, alors que l’implémentation de SearchItem sera effectuée en partie dans Interface Builder. Voyons les deux premiers maintenant.

Les éléments Ajouter et Supprimer

En configurant ces deux éléments, nous positionnons cinq propriétés minimum qui font qu’un contrôle soit utilisable. Un éléments de barre d’outils a deux labels — un utilisé quand l’élément est affiché dans la barre d’outils, et un autre quand l’élément est affiché dans la palette de personnalisation.

C’est ce que font les méthodes -setLabel: et -setPaletteLabel:, alors nous appelons ces méthodes durant la configuration. De plus, nous voulons donner une image à notre élément de barre d’outils. Vous pouvez voir que j’ai inclus deux images que vous pouvez utiliser pour cette application dans le dossier projet — Add.tiff et Remove.tiff. Les images de la barre d’outils sont positionnées par la méthode -setImage:.

Enfin, nous voulons positionner le destinataire de l’élément de la barre d’outils et l’action à invoquer par le destinataire. On effectue cela par -setTarget: et -setAction: respectivement. Voici le code pour effectuer ces cinq choses dans AddItem et RemoveItem:

if ( [itemIdentifier isEqualToString:@"AddItem"] ) {
    [item setLabel:@"Add Record"];
    [item setPaletteLabel:[item label]];
    [item setImage:[NSImage imageNamed:@"Add"]];
    [item setTarget:self];
    [item setAction:@selector(addRecord:)];
} else
if ( [itemIdentifier isEqualToString:@"RemoveItem"] ) {
    [item setLabel:@"Remove Record"];
    [item setPaletteLabel:[item label]];
    [item setImage:[NSImage imageNamed:@"Remove"]];
    [item setTarget:self];
    [item setAction:@selector(deleteRecord:)];
} else
if ( [itemIdentifier isEqualToString:@"SearchItem"] ) {
    // Configuration code for "SearchItem"
}

Il y a plusieurs choses à noter ici. Premièrement, remarquez que nous positionnons le label de la palette de sorte qu’il soit le même que le label de la barre d’outils. Si vous le vouliez, vous pourriez donner à l’élément un label de palette plus descriptif, puisque l’espace n’est pas un problème ici comme il l’était dans la barre d’outils. Nous avons utilisé la méthode -label pour obtenir la chaine de caractères du label plutôt qu’une duplication de la chaine de caractères. Ce n’est pas bien grave, mais c’est une chose de moins à changer si nous voulions modifier le label.

Deuxièmement, remarquez comment nous avons créé une instance de NSImage à partir du fichier image pour le passer à l’élément par le message -setImage:. +imageNamed: de NSImage est très pratique pour cela. Vous n’avez pas à passer l’extension ou le chemin du fichier — juste le nom — et +imageNamed: cherchera l’image dans les ressources de l’application (bundle).

Enfin, remarquez que les actions de ces deux éléments de barres d’outils utilisent les mêmes actions que les boutons Add et Remove de l’interface. Pour en savoir plus sur tout ce que vous pouvez changer dans la barre d’outils, regardez la référence de NSToolbarItem.

L’élément Chercher

Nous arrivons maintenant à l’élément Chercher de la barre d’outils. Nous allons utiliser Interface Builder pour faire une partie du travail. Pour préparer le travail, ajoutez l’outlet suivant dans la déclaration à Controller.h:

 IBOutlet id searchItemView;

Puis allez dans Interface Builder et lisez le fichier Controller.h pour mettre à jour la classe d’interface NSController, pour que l’outlet soit disponible. Ce que nous voulons, c’est créer un contrôle dans IB qui sera encapsulé dans un élément de barre d’outil. Puisqu’il n’y a pas de barre d’outils ou quoique ce soit pour y mettre le champ texte, nous allons créer notre élément de barre d’outils dans une instance autonome de NSView. Nous ajouterons une instance de NSView dans notre nib, comme nous l’avons appris la dernière fois, puis nous placerons un champ texte dans cette vue.

Nous devons également faire en sorte que les dimensions de la vue soient les mêmes que celles du champ texte. Cela, pour faire en sorte que l’élément de la barre d’outils soit aussi compact que possible. Faites attention à ce niveau — ne modifiez pas la taille de la vue manuellement. Redimensionner plutôt les éléments par le panneau Info Panel dans la section Dimension/Taille (Size). Pour notre champ texte, modifiez le bas/gauche du cadre en mettant x = 0 et y = 0. Cela calera le champ text dans le coin inférieur gauche de la vue. Ensuite, prenez note de la largeur et hauteur du cadre du champ texte, et dans la section information dimension (size info) de la vue, tapez ces valeurs pour la hauteur et la largeur.

Dimensionner les éléments de cette manière devient très importante quand vous travaillez avec des boutons ou d’autres objets ayant des ombres. On ne veut pas rogner sur les ombres des contrôles, sinon ils apparaitraient de façon bizarre. Pour en terminer ici, effectuer la connection entre l’outlet searchItem et l’instance de NSView qui contient le champ texte.

Une dernière chose à faire avant d’en terminer est de connecter NSController à l’outlet délégué du champ texte. Nous faisons cela pour pouvoir utiliser plus tard la méthode délégué de NSControl. Faites-le maintenant, sauvegarder votre travail et revenez sur Project Builder.

Le code pour configurer l’élément Search de la barre d’outils débute de la même façon que les autres, en initialisant les labels de cet élément. Après les chose deviennent différentes. Plutôt que de positionner l’image de l’élément de la barre d’outils, nous initialisons la vue de l’élément. L’élément de la barre d’outils devient un groupe de contrôles contenu dans l’objet vue — dans notre cas le simple champ texte. Ce qui permet de créer des éléments de barre d’outils compliqués qui comportent une variété de contrôles tels que des boutons pop-up ou des boutons checkbox.

Après avoir mis en place la vue de l’élément de la barre d’outils, nous devons indiquer à l’élément quels sont ces dimensions maximum et minimum. Cela se fait en utilisant les méthodes -setMinimize: et -setMaximize:. Nous initialiserons ces deux méthodes aux dimensions actuelles de searchItemView, que nous obtenons directement depuis la vue.

De par la nature du champ texte, nous n’avons pas besoin de specifier le destinataire ou l’action -nous allons utiliser le statut du Controller comme délégué du champ texte pour travailler avec le contrôle. Voyons enfin le code qui fait que tout cela fonctionne (c’est le troisième bloc else-if):

} else if ( [itemIdentifier isEqualToString:@"SearchItem"] ) {
    NSRect fRect = [searchItemView frame];
    [item setLabel:@"Search Records"];
    [item setPaletteLabel:[item label]];
    [item setView:searchItemView];
    [item setMinSize:fRect.size];
    [item setMaxSize:fRect.size];
}

Premièrement nous avons obtenu le cadre de searchItemView et sauvegardé dans fRect — ce qui s’avérera très pratique quand nous aurons besoin de spécifier les dimensions minimum et maximum du contrôle. Dans les deux lignes suivantes nous initialisons les labels comme nous l’avons fait auparavant. Ensuite, nous invoquons -setView: pour initialiser la vue de l’élément de la barre d’outils. Dans les deux lignes suivantes, nous utilisons -setMinSize: et -setMaxSize: pour initialiser les dimensions correspondantes de l’élément. Ces deux méthodes ont NSSize comme arguments, qui sont fournis par fRect.size. Avec cela, nous sommes finalement prêts à compiler et essayer notre nouvelle barre d’outils.

Maintenant, j’aimerais vous montrer une dernière chose à propos des barres d’outils, puis je vous montrerez comment la fonction de recherche est implémentée.

Validation d’éléments de barre d’outils

La validation d’éléments de barre d’outils est une moyen de tester l’état de votre application pour déterminer si notre élément de barre d’outils doit être actif ou inactif. Par exemple, dans notre situation, les principes d’un bon design d’interface voudrait que l’on active l’élément remove seulement si un ou plusieurs éléments du carnet d’adresse sont sélectionnés. Dans ce cas le nombre de lignes sélectionnées nous indique quelque chose à propos de l’état de l’application, que nous pouvons comparer par rapport à des critères de validation spécifiques.

Ce comportement est supporté par la méthode -validateToolbarItem: du protocole NSToolbarItemValidation. Pour tout élément de la barre d’outils qui possède un couple destinataire/action valide, cette méthode est appelée sur l’objet destinataire. Puisque NSController (par self) est le destinataire de l’élément Remove, nous allons implémenter -validateToolbarItem: dans ToolbarDelegateCategory. Maintenant dans ToolbarDelegateCategory.h ajoutez la déclaration de la méthode.

La valeur retournée par cette méthode est un BOOL, donc nous devons faire des tests dans l’implémentation de la méthode qui retourne un BOOL. Nous avons déjà dit que nous voulons que l’élément Remove ne soit actif que quand il y a des éléments selectionnés, donc notre test ici est de vérifier s’il y a des lignes sélectionnées dans la tableview (vue tabulaire). NSTableView nous fournit la méthode -numberOfSelectedRows pour faire cela. Si le nombre est supérieur à 0, alors nous activons l’élément en retournant YES. Maintenant, nous avons tout ce qu’il nous faut pour valider l’élément Remove de la barre d’outils:

- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem {
   if ( [theItem action] == @selector(deleteRecord:) )
       return [tableView numberOfSelectedRows] > 0;
}

Si vous avez plusieurs éléments de barre d’outils ayant le même destinataire mais invoquant différentes actions, il est alors impératif que vous testiez, d’une façon ou d’une autre, que l’élément est l’élément de la barre d’outils que nous voulons valider avant de considérer que la validation est correcte. Un bon moyen de le faire est de comparer l’action de l’élément à la méthode que nous voulons valider. C’est tout à fait logique puisque ce que nous voulons vraiment faire c’est activer ou désactiver un comportement plutôt qu’un élément.

Dans ce cas, nous avons vérifié si l’action de l’élément en question était la méthode deleteRecord:. Si c’est le cas, nous vérifions alors combien de lignes sont sélectionnées. Si une ou plus sont sélectionnées, alors YES est retourné, sinon, NO. Et c’est comme cela que fonctionne la validation d’élément de barre d’outils.

La recherche

Pour que la fonction de recherche fonctionne, de grosses modifications dans la façon de sauvegarder les enregistrements dans l’application de carnet d’adresses doivent être effectuées. Plutôt que de vous entrainer dans une description détaillée, étape par étape de tous les changements qui ont été effectués, je vous donnerai une simple explication de ce qui se passe et je vous laisserai télécharger le code et l’étudier. Le dossier du projet dans lequel toutes les modifications ont été effectuées peut être téléchargé ici.

Dans l’état actuel des choses, les enregistrements individuels sont sauvegardés dans une instance de NSMutableArray assignée à la variable d’instance records. Quand une information est affichée dans la vue tabulaire, records fournit cette information par les méthodes de NSTableDataSource.

Maintenant, supposez que nous voulions limiter l’information affichée dans la vue tabulaire à un sous-ensemble de records répondant au critère de recherche. Comment peut-on obtenir et utiliser un sous-ensemble de records comme source de données sans modifier le contenu de records ? La façon dont nous allons procéder est en introduisant deux nouvelles variables d’instance de NSMutableArray appelées subset et activeSet. Pour voir où activeSet est utilisé dans le code, faites une recherche par le panneau Recherche.

Plutôt que d’utiliser la variable records dans les méthodes de source de données, nous utilisons activeSet. L’astuce est que l’objet NSMutableArray initialisé à activeSet change en fonction de la fonction qui est exécutée. Quand l’application se charge, activeSet est initialisé à records immédiatement après qu’un NSMutableArray ait été initialisé à records. De cette manière activeSet et records pointent sur le même objet en mémoire. Deux variables, un objet.

Maintenant, quand nous effectuons une recherche, nous créons une autre instance de NSMutableArray et l’initialisons à la variable subset. La recherche consistera à énumérer records et comparer le premier et le dernier nom dans le dictionnaire de l’enregistrement à la chaine de caractères recherchée. Si il existe une ressemblance partielle, alors cet enregistrement est ajouté à subset. Une fois que records à été complètement énuméré, nous assignons activeSet à subset, et la vue tabulaire affichera le contenu de subset –c’est à dire, le résultat de la recherche. Quand le champ texte de recherche est mis à blanc, activeSet est une fois de plus initialisé à records.

Vous comprenez maintenant que quand nous disons que nous ajoutons des objets à subset, nous ajoutons en fait les pointeurs sur ces objets à subset, et subset envoie un message retain à ces objets. Maintenant, records et subset ont la possession d’un groupe partagé d’objets. C’est la même situation “deux variables, un objet” que nous avions précédemment, excepté que les variables sont des éléments de records et subset. C’est important vis-à-vis de la façon dont la vue tabulaire travaille. En particulier quand vous éditez des informations directement dans la table, l’information n’est pas seulement modifiée dans les enregistrements contenus dans subset (via activeSet); les modifications sont également effectuées sur l’objet.

Voici comment cela fonctionne : Quand records est initialisé pour la première fois, nous initialisons activeSet à cette valeur. Quand nous effectuons une recherche, nous assignons activeSet à subset. Enfin, dans la méthode de recherche, nous aurons un bloc if qui vérifiera si la longueur de la chaine de caractères recherchée est 0. Dans ce cas, nous réinitialisons activeSet avec records.

Voilà ce qui se passe donc avec nos changements de variables et l’introduction de nouvelles variables d’instance. Comment la fonction de recherche marche ? Rappelez-vous que dans Interface Builder, nous avons mis NSController en délégué du champ texte dans searchItemView. La raison pour laquelle nous avons fait cela est pour implémenter une méthode déléguée de NSControl appelée -controlTextDidChange:, qui est invoquée chaque fois que le texte dans le champ texte est modifié. De cette façon, une nouvelle recherche est effectuée dès qu’une lettre est tapée dans le champ texte.

Ajoutez à Controller.m la définition suivante de -controlTextDidChange :

- (void)controlTextDidChange:(NSNotification *)aNotification {     
   NSString *searchString = [[[aNotification object] stringValue] lowercaseString];
   NSEnumerator *e = [records objectEnumerator];
   NSString *fnString, *lnString;
   id object;
   if ( [searchString length] == 0 ) {
       activeSet = records;
       [tableView reloadData];
       return;
   }
   [subset release];
   subset = [[NSMutableArray alloc] init];
   while ( object = [e nextObject] ) {
       fnString = [[object objectForKey:@"First Name"] lowercaseString];
       lnString = [[object objectForKey:@"Last Name"] lowercaseString];
       if ( [fnString hasPrefix:searchString] || [lnString hasPrefix:searchString] )
	    [subset addObject:object];
       }
       activeSet = subset;
      [tableView reloadData];
}

Ce que nous avons ici est un énumérateur pour records qui vérifie chaque enregistrement contenu dans ce tableau pour voir si la chaine de caractères recherchée est un préfix, le prénom ou le nom. Auquel cas, nous l’ajoutons à subset et continuons à énumérer. Une fois que l’énumération est complète nous faisons pointer activeSet sur subset puis nous indiquons à tableView de recharger ses données.

Il y a certaines choses à noter ici. Premièrement, nous faisons en sorte que la recherche ne soit pas sensible à la casse en envoyant des messages lowercaseString à toutes les chaines de caractères impliquées et nous utilisons la chaine de caractères retournée dans la recherche.

De plus, remarquez l’argument de cette méthode, NSNotification. C’est une classe dont nous n’avons pas encore discutée au cours de ces articles. Si vous vous reportez à la documentation de cette méthode dans les spécifications de NSControl et NSTextField vous verrez que la méthode controlTextDidChange: est appelée dans le délégué par une notification. Une notification est un moyen de communication entre objets qui n’ont aucune connaissance l’un de l’autre, en utilisant un concept de diffusion. Des objets peuvent s’abonner eux-même à un centre de notification, qui est une instance de la classe NSNotificationCenter. En s’abonnant soit-même de cette façon un objet devient un observateur qui ne répond qu’a certain type de notifications. Par ce concept d’abonnement les objets indiquent laquelle de ses méthodes sera invoquée en réponse à l’invocation.

Vous abonnez votre objet au centre de notification par défaut en utilisant la méthode -addObserver:selector:name:objet:. Ici, observer est l’objet répondant à la notification, selector est la méthode invoquée sur l’observateur, name est le nom de la notification à laquelle l’observateur répondra, et l’argument objet est utilisé pour filtrer les notifications auquelles l’observateur répondra. Pour répondre à toutes les notifications ayant à un nom particulier, passez nil en argument. Sinon, vous pouvez faire en sorte que votre observateur ne réponde qu’à certaines notifications qui ont été postées par un objet spéficié par cet argument.

Heureusement, en assignant NSController en tant que délégué du champ de recherche, tout cela est automatiquement pris en charge pour nous. Tout ce que nous avons eu à faire est d’implémenter -controlTextDidChange:.

L’autre partie de l’histoire avec les notifications est à propos de l’objet postant la notification. Des objets postent des notifications au centre de notification en utilisant soit -postNotification: ou -postNotificationName:object: de NSNotificationCenter. Dans la seconde méthode object est souvent self. Quand une notification est postée au centre de notification elle est diffusée à tous les observateurs de cette notification. Le centre de notification joue le rôle du médiateur dans cette communication unidirectionnelle entre deux objets ne se connaissant pas.

Retournons à notre méthode de recherche. L’objet qui a envoyé la notification peut être accédé en passant un message objet à l’objet notification. C’est de cette façon que nous accédons au champ texte dont le contenu a changé et que nous retrouvons la chaine de caractères qu’il contient.

Maintenant, avec cette méthode ajoutée à votre code, et avec les modifications nécessaire décrites plus haut, vous devriez avoir une fonction de recherche dans votre application.

Ceci conclut votre apprentissage sur la création de barre d’outils. Comme toujours, je vous recommande fortement de lire la documentation sur les classes vu que je n’ai évoqué que quelques unes des possibilités. A la prochaine fois !

Textes originaux en anglais sur O’Reilly : Création de barre d’outils pour Mac OS X, Part 1 par Mike Beam

opoppon Programmation Cocoa , , ,

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