Follow me
RSS feed
My sources
My Viadeo

Binding avec MacRuby

Greg | 18 Mar 2010

Dev Il y a quelques jours, je vous ai présenté les principes KVC et KVO avec MacRuby. Je vous disais alors que le Key Value Coding n'avait d'intérêt que dans le cadre de la mise en place de Key Value Observing. Ce n'est pas tout à fait exact. En effet, le KVC entre également en jeu dans le cas du Binding.

Je vous propose de voir aujourd'hui le principe du Binding en mettant principalement l'accent sur l'économie de code que peu présenter cette solution. Pour cela je vais partir d'un exemple de remplissage de tableau. L'idée est de créer une petite application dans laquelle nous avons un tableau dans lequel nous pourrons ajouter, modifier ou supprimer des lignes :

binding

La méthode longue

Dans cette première partie, nous allons détailler la méthode longue pour la mise en place de notre outil de backlog.

Démarrez Xcode et créez un nouveau projet de type "MacRuby Application" que nous appellerons BindingExampleTableLong.

Nous allons commencer par mettre en place l'interface utilisateur via Interface Builder. Afin de ne pas me perdre en longues explications, voici un petit film qui détaille cette étape :

Ceci étant fait, nous retournons dans Xcode et nous ajoutons une nouvelle classe dans notre projet. Pour cela, dans la zone Groups & Files, faites un clic droit sur l'entrée Classes.

binding

Choisissez un fichier de type Ruby File que nous nommerons AppController.rb.

Pour contrôler notre tableau, nous avons besoin de pouvoir y accéder. Pour cela nous mettons en place un accesseur dans la classe AppController. Nous avons également besoin de deux actions, l'une pour gérer l'ajout de ligne dans le tableau, l'autre pour la suppression. A ce stade, nous pouvons déjà modifier le fichier AppController.rb comme ceci :

# AppController.rb
# BindingExampleTableLong
#
# Created by greg on 19/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.

class AppController
  attr_accessor :leTableau
  
  def ajouterUneLigne(sender)
  
  end
  
  def supprimerUneLigne(sender)
  
  end
end

Nous pouvons maintenant mettre en place les liens entre notre interface et les éléments de la classe AppController. Pour cela, revenons dans Interface Builder et connectons le tableau à l'outlet leTableau et les actions des boutons + et -, respectivement aux actions ajouterUneLigne et supprimerUneLigne :

Comme vous pouvez le voir, nous en avons profité pour définir la classe AppController comme délégué (delegate) et source de données (dataSource) pour le tableau. Le fait de définir AppController comme source de données nous impose d'y ajouter deux méthodes :

AppController.rb
# AppController.rb
# BindingExampleTableLong
#
# Created by greg on 19/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.

class AppController
  attr_accessor :leTableau
  
  def ajouterUneLigne(sender)
  
  end
  
  def supprimerUneLigne(sender)
  
  end

  def numberOfRowsInTableView(aTable)

  end

  def tableView(aTable, objectValueForTableColumn:theColumn, row:theRow)

  end
end

Il ne nous reste plus qu'à remplir les blancs.

Dans AppController, nous allons définir la structure du contenu de la table comme un tableau de hachage. Chaque élément du tableau correspondant à une ligne. Nous aurons donc quelque chose comme cela :

@contenuDuTableau = [
  {
    "id" => ..., 
    "demande" => ..., 
    "priorite" => ..., 
    "statut" => ..., 
    "description" => ..., 
    "taille" => ...
  },
  ...
]

Nous pouvons donc ajouter cette déclaration dans la méthode d'initialisation de la classe et coder le contenu de numberOfRowsInTableView: :

def numberOfRowsInTableView(aTable)
  return @contenuDuTableau.size
end

Pour la méthode tableView:objectValueForTableColumn:row:, le paramètre theRow correspond au numéro de la ligne, le paramètre theColumn est quant à lui un objet de type NSTableColumn. Il est donc facile de récupérer dans @contenuDuTableau la ligne correspondante. Pour la colonne, il faut regarder ce que propose la classe NSTableColumn. Si vous regardez dans la documentation vous remarquerez qu'il existe un identifiant de colonne. Cet identifiant peut être récupéré en utilisant theColumn.identifier. Pour définir l'identifiant de chaque colonne, il faut retourner dans Interface Builder :

A partir de là, il est très facile de coder le corps de tableView:objectValueForTableColumn:row: :

def tableView(aTable, objectValueForTableColumn:theColumn, row:theRow)
  return @contenuDuTableau[theRow][theColumn.identifier]
end

La suppression d'une ligne est relativement simple à mettre en place. Il suffit de récupérer le numéro de la ligne sélectionnée dans le tableau, de supprimer les données correspondantes dans @contenuDuTableau et de recharger le contenu du tableau.

def supprimerUneLigne(sender)
  if( leTableau.selectedRow < 0 or leTableau.selectedRow >= @contenuDuTableau.size)
    return
  end
  @contenuDuTableau.delete_at(leTableau.selectedRow)
  leTableau.reloadData
end

Pour l'ajout d'une ligne, nous ferons l'inverse, à savoir : nous commençons pas ajouter une entrée dans @contenuDuTableau, nous rechargeons les données du tableau, nous nous positionnons sur la nouvelle ligne en mode d'édition :

def ajouterUneLigne(sender)
  @contenuDuTableau << {
    "id" => "", 
    "demande" => "", 
    "priorite" => "", 
    "statut" => "", 
    "description" => "", 
    "taille" => ""
  }
  leTableau.reloadData
  leTableau.selectRow(@contenuDuTableau.size - 1, byExtendingSelection:false)
  leTableau.editColumn(0, row:(@contenuDuTableau.size - 1), withEvent:nil, select:true)
end

A ce niveau de notre développement, si vous testez, vous vous rendrez rapidement compte d'un petit défaut. En effet, lorsque nous éditons le contenu du tableau, nos changements ne sont pas pris en compte. Il est en effet nécessaire d'ajouter une méthode de délégation indiquant comment traiter ce changement. Ceci se met en place très simplement :

def tableView(aTable, setObjectValue:anObject, forTableColumn:theColumn, row:theRow)
  @contenuDuTableau[theRow][theColumn.identifier] = anObject
  leTableau.reloadData
end

"Et le bouton 'verify' ?
- Nous l'utiliserons pour afficher dans les logs le contenu de @contenuDuTableau afin de nous assurer que tout fonctionne. Je vous laisse faire ;)"

binding

Comme vous avez pu le voir, nous avons écrit pas mal de code pour mettre en place notre petit outil. Je vous propose de le refaire en quelques lignes...

La méthode courte

Pour mettre en place cette solution, nous pouvons revenir au sujet de cet article : le Binding. Le principe est relativement simple, il s'agit de synchroniser une propriété d'une vue avec l'attribut d'un objet. Dans notre exemple, nous allons donc simplement définir cette synchronisation entre le tableau et le contenu (@contenuDuTableau) qui sera alors défini comme un attribut de AppController.

Pour plus de simplicité, créez un nouveau projet avec Xcode. Vous pouvez utiliser exactement la même démarche pour mettre en place l'interface graphique. Une fois ceci fait, créons la classe AppController en déclarant @contenuDuTableau comme attribut et en l'initialisant comme un tableau vide :

# AppController.rb
# BindingExampleTableShort
#
# Created by greg on 19/03/10.
# Copyright 2010 Gregoire Lejeune. All rights reserved.

class AppController
  attr_accessor :contenuDuTableau
  
  def initialize
    self.setContenuDuTableau( [] )
  end
end

Vous noterez que j'initialise contenuDuTableau via setContenuDuTableau. En effet, le binding est intimement lié au principe KVC qui ne fonctionne pas si nous faisons contenuDuTableau = []

Voilà. C'est le seul code que nous avons à écrire ! Tout le reste va être fait via Interface Builder. Pour mettre en place le binding, nous allons ajouter dans IB un objet de type AppController que nous utiliserons en lien avec un contrôleur de type NSArrayController.

Comme vous pouvez le voir sur la vidéo ci-dessous, il suffit d'ajouter l'objet de type AppController, un NSArrayController de mettre en place le binding entre ce dernier et l'objet de type AppController puis, pour chaque colonne du tableau, de mettre en place un binding avec le NSArrayController :

Comme vous pouvez le voir, nous en avons également profité pour lier les boutons + et -, respectivement, aux action add: et remove: du contrôleur de tableau.

Terminé ! Il ne reste plus qu'à tester.

Comme vous pouvez le voir, le principe du Binding est beaucoup plus simple à mettre en place. Il faut cependant faire attention quand on l'utilise. Non pas qu'il représente un risque dans son utilisation, mais si jamais vous travaillez avec des personnes débutantes, ce n'est pas forcement la première chose à laquelle elles vont penser. Ceci donnera un petit côté magique à votre développement, mais risque de faire perdre beaucoup de temps à une personne qui ne serait pas habituée à ce genre de chose.

Copyright © 2009 - 2011 Grégoire Lejeune.
All documents licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License, except ones with specified licence.
Powered by Jekyll.