NSDictionary et NSMutableArray
Rocou
Membre
Je n'arrive pas à extraire des données précises d'un objet NSMutableArray ou NSDictionary.
Je tente d'accéder à une base de données PostgreSQL avec le Framework PGSQLKit. ça fonctionne, voici le code:
Le résultat affiché sur la console est le suivant:
result: (
{
client = "Client1";
}
)
result: (
{
client = "Client1";
},
{
client = "Client2";
}
)
...
Et ainsi de suite jusqu'à la fin de la requete.
Comment faire pour remplir un objet "Table View" avec cela? Faut-il se débarrasser des parenthèses, des accolades et du "client=" avant? Si oui comment faire?
J'ai cru comprendre qu'un NSDictionary ou un NSMutableArray est un tableau. J'ai essayé d'afficher result[1] ou result[1,1] mais le compilateur n'a pas l'air d'apprécier.
Je tente d'accéder à une base de données PostgreSQL avec le Framework PGSQLKit. ça fonctionne, voici le code:
#import "CollecteControl.h"<br />#import "/Library/Frameworks/PGSQLKit.framework/Versions/A/Headers/PGSQLConnection.h"<br /><br />//-(NSDictionary *)dictionaryFromRecord; ***** juste pour rappel<br /><br />@implementation CollecteControl<br />- (IBAction)a_recapclper:(id)sender<br />{<br /> NSString *user = @"postgres";<br /> NSString *password = @"";<br /> <br /> NSString *serverName = @"localhost";<br /> NSString *serverPort = @"5432";<br /> NSString *databaseName = @"sxcollecte";<br /> <br /> PGSQLConnection *connection = [[PGSQLConnection alloc] init];<br /> <br /> [connection setUserName:user];<br /> [connection setPassword:password];<br /> <br /> [connection setServer:serverName];<br /> [connection setPort:serverPort];<br /> [connection setDatabaseName:databaseName];<br /> <br /> if ([connection connect])<br /> {<br /> NSLog(@"connexion ok");<br /><br /> NSString *query = @"select clients.nom as client from clients";<br /> // query="SELECT clients.nom as client, conteneurs.volume, dechets.nature, COUNT(conteneurs.volume) as nbBacs FROM ""prestations"" INNER JOIN clients ON prestations.client_id = clients.id INNER JOIN conteneurs ON prestations.conteneur_id = conteneurs.id INNER JOIN tournees ON prestations.tournee_id=tournees.id INNER JOIN dechets ON tournees.dechet_id=dechets.id INNER JOIN collaborateurs ON collaborateurs.id=clients.facturation_id "+ "AND " + "clients.nom=" +"'"+ PopupMenu1.text+"'" + " WHERE prestations.ladate >= "+ "'"+ dstockdebut + "'" + " AND prestations.ladate <= "+"'"+ dstockfin + "' "+ "GROUP BY clients.nom, conteneurs.volume, dechets.nature"<br /> <br /> NSMutableArray *result = nil;<br /> <br /> PGSQLRecordset *rs = [connection open:query];<br /> if (rs != nil) {<br /> if (![rs isEOF])<br /> {<br /> if (result != nil)<br /> {<br /> [result release];<br /> result = nil;<br /> }<br /> result = [[NSMutableArray alloc] init];<br /> while (![rs isEOF])<br /> {<br /> [result addObject:[rs dictionaryFromRecord]];<br /> NSLog(@"result: %@", result);<br /> <br /><br /><br /> [rs moveNext];<br /> } <br /> }<br /> [rs close]; <br /> [result release];<br /> }<br /> <br /> [connection close];<br /> <br /> // return result; <br /> } else {<br /> // setup the error dictionary<br /> NSLog(@"Impossible de se connecter à la base de données");<br /> [self performSelector:@selector(getConnectionWithLogin) withObject:nil afterDelay:0.0]; <br /> return;<br /> <br /> }<br /> return nil; <br /> } <br /><br />@end<br />
Le résultat affiché sur la console est le suivant:
result: (
{
client = "Client1";
}
)
result: (
{
client = "Client1";
},
{
client = "Client2";
}
)
...
Et ainsi de suite jusqu'à la fin de la requete.
Comment faire pour remplir un objet "Table View" avec cela? Faut-il se débarrasser des parenthèses, des accolades et du "client=" avant? Si oui comment faire?
J'ai cru comprendre qu'un NSDictionary ou un NSMutableArray est un tableau. J'ai essayé d'afficher result[1] ou result[1,1] mais le compilateur n'a pas l'air d'apprécier.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Alors NSDictionary et NSMutableDictionary sont un peu des sortes de tableau si tu veux (quoique c'est plutôt NS(Mutable)Array qui sont des tableaux), mais ça reste des objets Cocoa. Il faut donc accéder à leurs éléments en utilisant les méthodes de la classe NS(Mutable)Dictionary, et non pas avec une syntaxe à la C avec des crochets, car ce ne sont pas des tableaux C pour autant.
1) NSMutableArray dérive de NSArray (c'est jsute un NSArray à qui est rajouté la possibilité d'être modifiable, en somme), et pour accéder à ses éléments, tu as la méthode objectAtIndex:
2) NSDictionary est un dictionnaire de données, c'est à dire un ensemble de paires { clé = valeur } (key/value pairs).
Dans ton cas, à chaque enregistrement (record) résultat de ta requête PostgreSQL correspond un dictionnaire, dont les clés sont les noms des champs de ta table et les valeurs... ben la valeur de ce champ pour cet enregistrement. Et il se trouve que d'après ton exemple, chacun de tes dictionnaires (que tu mets dans ton NSMutableArray) n'a qu'une seul clé, nommée "client".
3) Ce que tu vois dans la console n'est qu'une représentation textuelle du contenu de l'objet. Ce n'est pas comme ça explicitement que l'objet est stocké : c'est juste qu'avec la commande NSLog tu lui as demandé de t'afficher sur la console l'objet, et pour ça il faut bien qu'il t'en sorte une représentation textuelle juste pour l'affichage dans la console. Mais dans ton programme ça reste un objet Cocoa, à manipuler avec les méthodes qui vont bien.
Par exemple pour récupérer la valeur de la clé "client" du 2e élément de ton NSMutableArray (autrement dit la valeur du champ "client" de ton 2e enregistrement retourné par ta requête PostgreSQL), il suffit de faire :
Ou encore si tu veux décomposer :
Il manque un tutorial à -la-tablier (ie pour débutant total) concernant objective-c. S'il y a cela sur le net, je suis preneur...
j'en remet une couche : je ne vais pas te faire un tuto (le temps où j'en faisais est révolu ) mais juste t'orienter aussi sur NSEnumerator qui sont faites pour parcourir des classes de type "collection" (comme NSArray, NSDictionary, ... classes faites pour contenir des objets quoi) facilement.
Ainsi, une fois que tu as ton tableau "result", si tu veux le parcourir dans son ensemble, tu peux faire comme ceci (là je demande d'afficher dans la console le client de chaque fiche) :
C'est un code équivalent à :
Merci pour ces précisions.
N'y-a-t-il pas une erreur de syntaxe dans le NSLog? Après test, "pour client" n'est pas affiché.
A propos, comment afficher "result" dans un "tableview"?
J'ai bien essayé ceci:
mais cela ne fonctionne pas du tout. En outre la doc dit qu'on ne devrait jamais avoir à utiliser la méthode setTableValue.
Dois-je initialiser toutes les cellules de la table les unes après les autres après avoir déterminé le nombre de colonnes nécessaires? Mais dans ce cas, faut-il créer des outlets dynamiquement? (un par cellule?)
Using Table View data Source
Dans IB associe l'outlet datasource de ta table view à l'instance de CollecteControl, et implémente les méthodes du protocole NSTableDataSource.
Il y a un modèle dans Cocoa par la pratique.
Ou encore depuis Objective C 2.0
for(NSDictionary * fiche in result){
NSLog(@Cette fiche a %@ pour client." , [fiche valueForKey: @client]);
}
Oui mais ça ne fonctionne pas chez moi. J'ai même récupéré un "project" dont tu es l'auteur (http://www.objective-cocoa.org/forum/index.php?action=dlattach;topic=2692.0;attach=2048). J'ai copié-collé tout ce qui pouvait l'être mais rien ne s'affiche dans ma tableview.
Je n'arrive pas à comprendre comment est "reconnue" la tablewiew puisqu'il n'y a pas d'outlet.
Instancier un CollecteControler dans le MainMenu.nib à l'aide d'un "cube bleu"
Clic-droit sur la table view, connecter Dataspurce au CollecteController
CollecteController peut définir un IBOutlet NSTableView * tableView;
Connecter cet outlet à la tableview dans le nib
Si cela ne marche pas, c'est le code qui coince, envoie-le (sans le build pour alléger le poids)Â Â on peut regarder
L'outlet tableView n'est pas nécessaire, car dans les requêtes au datasource, la tableview se déclare :
Par exemple, la méthode
- (id)tableView:(NSTableView *)aTableView
  objectValueForTableColumn:(NSTableColumn *)aTableColumn
  row:(int)rowIndex
dit quelle tableview demande de l'info ...
une même (instance) classe peut donc être datasource pour plusieurs table view.
Justement, c'est cela que je ne comprends pas: où voit-on quelle tableview demande l'info?
Si un objet A est défini comme étant le datasource d'une tableview T ([T setDataSource:A]), alors, quand T a besoin des données, elle appelle la méthode tableView:objectValueForTableColumn:row:.
Or, comme tu le dis, il faut bien qu'elle s'identifie (car A pourrait être fournisseurs de plusieurs tableViews). C'est tout simple : la tableview qui demande est celle qui est passé en paramètre à la méthode !
Exemple :
+
Chacha
D'accord mais comment faire pour identifier la tableview par T?
J'ai essayé de définir un outlet T, j'ai regardé s'il avait une zone "titre" dans "l'inspector" de la tableview. Je sèche.
Par ailleurs, dans ton exemple, que doit on mettre à la place du commentaire pour remplir la tableview?
Si il n'y a qu'une table view, le problème ne se pose pas ...
Sinon, et si on renâcle à mettre un outlet dans le controller, on peut comme avec tous les nscontrol attribuer une valeur de tag différente à chaque tableview
enum {TABLEVIEWACCOUNTS=0,TABLEVIEWSAVINGS=1};
-(id) tableView:(NSTableView*)aTableView objectValueForTableColumn:(NSTableColumn*)aTableColumn
  row:(int)rowIndex
{
 if ([aTableView tag]== TABLEVIEWACCOUNTS)
 {
  //c'est T qui réclame des données
 }
}
Le fait de définir et connecter un outlet fait que T pointe sur l'adresse mémoire où est stockée la table view.
D'autre part la valeur envoyée dans la méthode est également cette adresse.
Donc le test que t'indique Chacha aTableView == T compare l'adresse de la table view qui demande l'info à l'adresse référencée par l'outlet.
Si tu veux qu'à cette ligne (row) et colonne soit écrit "toto" , tu renvoies
return @toto
En général, on crée une NSArray d'objets, la "row" correspond à l'indice dans la NSArray, et l'objet à un des champs de l'objet (ou du dictionnaire) de la NSArray.
Il n'y a pas d'astuce particulière... Ton objet A peut avoir un IBOutlet NSTableView* maTableView. Il faut juste savoir que lorsque l'on réalise le lien dans InterfaceBuilder entre A.maTableView et l'instance de la NSTableView, il faut bien viser, pour accrocher la tableview et pas une de ses colonnes, par exemple...
Je ne comprends pas bien ce que tu veux dire; cela signifie peut-être qu'on n'est pas tout à fait sur la même longueur d'onde, et que l'on répond à côté de ce que tu veux vraiment savoir... ou a besoin de savoir ! Que sais-tu des IBOutlets ?
On ne "remplit" pas la tableview ! La tableview n'est qu'un élément de l'interface, il ne contiendra jamais de valeur. En revanche, si la tableview a un datasource, elle peut, elle-même, demander au datasource les valeurs qu'il faut afficher (en fonction du scrolling, etc). Pour cela, elle appelle la méthode
tableView:objectValueForTableColumn:row:, les paramètres étant :
tableView : la table view qui demande
objectValueForTableColumn:l'identifiant de la colonne
row:l'identifiant de la ligne
et le datasource est censé renvoyer la valeur qu'il faut afficher à la ligne row de la colonne donnée.
Ce mécanisme, un peu bizarre, est formidable, puisqu'il sépare complètement l'interface des données. Une tableview réagit à des événements, mais n'affiche que ce qui est nécessaire, obtenu sur demande auprès du datasource.
+
Chacha
A vrai dire comme cela ne fonctionne pas, je me suis demandé si une tableview était gérée d'une façon particulière. J'ai pourtant l'impression d'avoir fait ce qu'il fallait: définir un outlet, faire le lien entre mon controleur et ma tableview dans IB.
J'ai ajouté le code (un copier-coller de l'exemple donné par Philippe49 dans un autre fil) dans l'implementation de mon controleur "collecteControl" et ça ne fonctionne toujours pas (ça se compile sans warning ni erreur mais rien ne s'affiche dans ma tableview)
C'est de moins en moins clair
Comment faire pour que la tableview demande au datasource les valeurs qu'il faut afficher?
Tu écris, elle "appelle la méthode" mais où faut-il insérer ce code d'appel?
Concernant les paramètres, à la place de tableView, je mets mon outlet?
Par contre il y a une connexion à faire absolument dans IB, c'est celle du dataSouce. C'est à dire cliquer sur la TableView, et connecter son Outlet "dataSource" (qui est déjà présent dans tous les objets TableView, ce n'est pas à toi de le créer tu as juste à le relier) à ton cube bleu "MyController".
Ensuite voilà comment ça se passe : à chaque fois qu'une TableView a besoin d'une donnée dans sa table (par exemple la colonne 5 de la ligne 3), elle demande (toute seule) à l'objet qui est défini comme son DataSource (donc en l'occurence à ton MyController) "tiens bonjour je suis la TableView machin et j'aimerais savoir quelle valeur je dois mettre pour la colonne C ligne L" : donc elle appelle la méthode [tt]tableView:objectValueForTableColumn:row:[/tt] sur son dataSource, et le dataSource qui implémente cette méthode retourne la bonne valeur à la tableView.
Donc ce n'est pas ton MyController qui a connaissance de ta TableView, mais bien l'inverse, la TableView à qui tu as dit (via la connexion de son dataSource) à qui il fallait qu'elle demande pour obtenir ses diverses valeurs. C'est pour ça qu'il n'est pas nécessaire d'avoir un Outlet vers ta TableView dans ton MyController.
Par contre il y a une connexion à faire absolument dans IB, c'est celle du dataSouce. C'est à dire cliquer sur la TableView, et connecter son Outlet "dataSource" (qui est déjà présent dans tous les objets TableView, ce n'est pas à toi de le créer tu as juste à le relier) à ton cube bleu "MyController".
Ensuite voilà comment ça se passe : à chaque fois qu'une TableView a besoin d'une donnée dans sa table (par exemple la colonne 5 de la ligne 3), elle demande (toute seule) à l'objet qui est défini comme son DataSource (donc en l'occurence à ton MyController) "tiens bonjour je suis la TableView machin et j'aimerais savoir quelle valeur je dois mettre pour la colonne C ligne L" : donc elle appelle la méthode [tt]tableView:objectValueForTableColumn:row:[/tt] sur son dataSource, et le dataSource qui implémente cette méthode retourne la bonne valeur à la tableView.
Donc ce n'est pas ton MyController qui a connaissance de ta TableView, mais bien l'inverse, la TableView à qui tu as dit (via la connexion de son dataSource) à qui il fallait qu'elle demande pour obtenir ses diverses valeurs. C'est pour ça qu'il n'est pas nécessaire d'avoir un Outlet vers ta TableView dans ton MyController.
[/quote]
Bon si j'ai bien compris, il suffit que j'alimente le datasource pour la tableview se modifie toute seule.
(Cela veut-il dire que les méthodes contenuent dans mon controleur sont exécutées en boucle?)
J'ai cela comme code dans mon controleur:
Mon datasource est bien connecté à mon controleur. Où est donc mon erreur?
D'ailleurs, si tu modifies tes données de ton modèle, il sera sans doute nécessaire d'en informer ta TableView pour lui indiquer de refaire appel aux méthodes de son dataSource et mettre à jour ses données (c'est avec la méthode [tt]reloadData[/tt] de NSTableView que l'on fait cela). Et là en effet tu auras alors sans doute besoin, quand tu arriveras à ce cas de figure, d'un Outlet vers ta TableView pour lui envoyer ce message [tt]reloadData[/tt]... mais bon, commence sans pour ne pas te perturber et te concentrer sur le pb qui nous occupe.
En effet comme cela ça devrait marcher... A condition que tu aies bien mis aussi respectivement "first" et "second" comme [tt]identifier[/tt] de tes deux TableColumns de ta TableView dans IB pour les identifier.
Tu peux peut-être rajouter des NSLog dans ton code, pour au moins vérifier que les méthodes de ton dataSource sont bien appelées ? et en profiter pour afficher à ce moment les valeurs intermédiaires ([tt][model objectAtIndex:rowIndex][/tt] et/ou [tt][aTableColumn identifier][/tt] par exemple).
- (id)tableView:(NSTableView *)aTableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
return model objectAtIndex:rowIndex] objectForKey:[b][aTableColumn identifier][/b;
}
- (void)tableView:(NSTableView *)aTableView
setObjectValue:anObject
forTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
model objectAtIndex:rowIndex] setObject:anObject forKey:[b][aTableColumn identifier][/b;
}
Il faut que dans IB les tableColumn possède un identifier et que cela corresponde à un champ du modèle. Dans le code que tu m'as envoyé aucun identifier n'est défini pour les tableColumns.
Autrement dit, si les clés sont "first" et "second" dans le model (dans awakeFromNib), les identifier définis pour la table view doivent être identiques
Ha! Enfin ça fonctionne. Merci!
Cela dit vers où s'orienter quand le nombre de colonnes (donc leur identifier) n'est pas connu? Peut-on allouer un identifier dynamiquement?
Il y a des méthodes dans NSTableColumn et NSTableView pour cela
NSTableColumn * newColumn=[[NSTableColumn alloc] initWithIdentifier:@third];
[tableView addTableColumn:newColumn];
[newColumn release];
Ok. Merci à tous. Je vais bosser sur ces nouvelles acquisitions avant de revenir vous harceler
Pour dire à la tableview d'aller chercher les données à afficher, je mets le code suivant dans mon "controlleur":
tableView:objectValueForTableColumn:row:
Mais comment alimenter le datasource?
Des exemples y sont donnés.
Le plus simple c'est de faire un NSArray de NSMutableDictionary ou d'une classe perso. Chaque élément de la table view correspond à un indice dans le tableau. En l'occurrence, chaque ligne de ton fichier doit correspondre soit à un NSMutableDictionary, ou à une instance de ta classe perso.
Dans ma boucle (celle qui ligne les lignes de mon fichier une par une), j'ai mis ce code:
NSMutableDictionary * maligne=[NSMutableDictionary dictionaryWithObjectsAndKeys:valeur,@first,nil];
model=[[NSArray alloc] initWithObjects:maligne,nil];
tableView:objectValueForTableColumn:row:
Mais rien ne s'affiche dans ma tableview et j'obtiens l'erreur suivante à l'exécution:
*** -[NSCFArray objectAtIndex:]: index (1) beyond bounds (1)
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
  return [model count];
}
Cette fonction n'est-elle pas appelée automatiquement?