Bases de données dans Xamarin.Mac
Cet article traite de l’utilisation du codage clé-valeur et de l’observation de valeur clé pour permettre la liaison de données entre les bases de données SQLite et les éléments d’interface utilisateur dans le Générateur d’interface de Xcode. Il couvre également l’utilisation de l’ORM SQLite.NET pour fournir l’accès aux données SQLite.
Vue d’ensemble
Lorsque vous utilisez C# et .NET dans une application Xamarin.Mac, vous avez accès aux mêmes bases de données SQLite qu’une application Xamarin.iOS ou Xamarin.Android peut accéder.
Dans cet article, nous allons couvrir deux façons d’accéder aux données SQLite :
- Accès direct : en accédant directement à une base de données SQLite, nous pouvons utiliser des données de la base de données pour le codage clé-valeur et la liaison de données avec des éléments d’interface utilisateur créés dans le Générateur d’interface de Xcode. En utilisant des techniques de codage clé-valeur et de liaison de données dans votre application Xamarin.Mac, vous pouvez réduire considérablement la quantité de code que vous devez écrire et gérer pour remplir et utiliser des éléments d’interface utilisateur. Vous bénéficiez également d’un découplage supplémentaire de vos données de stockage (modèle de données) de votre interface utilisateur frontale (modèle-vue-contrôleur), ce qui facilite la maintenance et la conception d’applications plus flexibles.
- SQLite.NET ORM : à l’aide du code source ouvert SQLite.NET Object Relationship Manager (ORM), nous pouvons réduire considérablement la quantité de code nécessaire pour lire et écrire des données à partir d’une base de données SQLite. Ces données peuvent ensuite être utilisées pour remplir un élément d’interface utilisateur tel qu’un affichage table.
Dans cet article, nous allons aborder les principes fondamentaux de l’utilisation du codage clé-valeur et de la liaison de données avec des bases de données SQLite dans une application Xamarin.Mac. Il est fortement suggéré que vous travaillez tout d’abord dans l’article Hello, Mac , en particulier les sections Introduction to Xcode and Interface Builder et Outlets and Actions , car elle couvre les concepts et techniques clés que nous utiliserons dans cet article.
Étant donné que nous allons utiliser le codage clé-valeur et la liaison de données, veuillez travailler tout d’abord sur la liaison de données et le codage clé-valeur, car les techniques et concepts de base seront abordés qui seront utilisés dans cette documentation et son exemple d’application.
Vous pouvez également examiner les classes /méthodes C# exposantes dans Objective-C la section du document interne Xamarin.Mac , ainsi que les Register
Export
attributs utilisés pour connecter vos classes C# à des objets et des Objective-C éléments d’interface utilisateur.
Accès DIRECT SQLite
Pour les données SQLite qui vont être liées à des éléments d’interface utilisateur dans le Générateur d’interface de Xcode, il est fortement suggéré d’accéder directement à la base de données SQLite (par opposition à l’utilisation d’une technique telle qu’un ORM), car vous avez un contrôle total sur la façon dont les données sont écrites et lues à partir de la base de données.
Comme nous l’avons vu dans la documentation sur la liaison de données et le codage clé-valeur, en utilisant des techniques de codage et de liaison de données clé-valeur dans votre application Xamarin.Mac, vous pouvez réduire considérablement la quantité de code que vous devez écrire et gérer pour remplir et utiliser des éléments d’interface utilisateur. Lorsqu’elle est combinée avec un accès direct à une base de données SQLite, elle peut également réduire considérablement la quantité de code nécessaire pour lire et écrire des données dans cette base de données.
Dans cet article, nous allons modifier l’exemple d’application à partir de la liaison de données et du document de codage clé-valeur pour utiliser une base de données SQLite comme source de stockage pour la liaison.
Prise en charge de la base de données SQLite
Avant de continuer, nous devons ajouter la prise en charge de la base de données SQLite à notre application en incluant des références à quelques-uns des deux. Fichiers DLL.
Effectuez les actions suivantes :
Dans le panneau Solution, cliquez avec le bouton droit sur le dossier Références , puis sélectionnez Modifier les références.
Sélectionnez les assemblys Mono.Data.Sqlite et System.Data :
Cliquez sur le bouton OK pour enregistrer vos modifications et ajouter les références.
Modification du modèle de données
Maintenant que nous avons ajouté la prise en charge de l’accès direct à une base de données SQLite à notre application, nous devons modifier notre objet de modèle de données pour lire et écrire des données à partir de la base de données (ainsi que fournir le codage à valeur clé et la liaison de données). Dans le cas de notre exemple d’application, nous allons modifier la classe PersonModel.cs et la rendre semblable à ce qui suit :
using System;
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
using Foundation;
using AppKit;
namespace MacDatabase
{
[Register("PersonModel")]
public class PersonModel : NSObject
{
#region Private Variables
private string _ID = "";
private string _managerID = "";
private string _name = "";
private string _occupation = "";
private bool _isManager = false;
private NSMutableArray _people = new NSMutableArray();
private SqliteConnection _conn = null;
#endregion
#region Computed Properties
public SqliteConnection Conn {
get { return _conn; }
set { _conn = value; }
}
[Export("ID")]
public string ID {
get { return _ID; }
set {
WillChangeValue ("ID");
_ID = value;
DidChangeValue ("ID");
}
}
[Export("ManagerID")]
public string ManagerID {
get { return _managerID; }
set {
WillChangeValue ("ManagerID");
_managerID = value;
DidChangeValue ("ManagerID");
}
}
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("Occupation")]
public string Occupation {
get { return _occupation; }
set {
WillChangeValue ("Occupation");
_occupation = value;
DidChangeValue ("Occupation");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("isManager")]
public bool isManager {
get { return _isManager; }
set {
WillChangeValue ("isManager");
WillChangeValue ("Icon");
_isManager = value;
DidChangeValue ("isManager");
DidChangeValue ("Icon");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("isEmployee")]
public bool isEmployee {
get { return (NumberOfEmployees == 0); }
}
[Export("Icon")]
public NSImage Icon {
get {
if (isManager) {
return NSImage.ImageNamed ("group.png");
} else {
return NSImage.ImageNamed ("user.png");
}
}
}
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
[Export("NumberOfEmployees")]
public nint NumberOfEmployees {
get { return (nint)_people.Count; }
}
#endregion
#region Constructors
public PersonModel ()
{
}
public PersonModel (string name, string occupation)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
}
public PersonModel (string name, string occupation, bool manager)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
this.isManager = manager;
}
public PersonModel (string id, string name, string occupation)
{
// Initialize
this.ID = id;
this.Name = name;
this.Occupation = occupation;
}
public PersonModel (SqliteConnection conn, string id)
{
// Load from database
Load (conn, id);
}
#endregion
#region Array Controller Methods
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
#endregion
#region SQLite Routines
public void Create(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Create new record ID?
if (ID == "") {
ID = Guid.NewGuid ().ToString();
}
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Save manager ID and create the sub record
Person.ManagerID = ID;
Person.Create (conn);
}
// Save last connection
_conn = conn;
}
public void Update(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Update sub record
Person.Update (conn);
}
// Save last connection
_conn = conn;
}
public void Load(SqliteConnection conn, string id) {
bool shouldClose = false;
// Clear last connection to prevent circular call to update
_conn = null;
// Is the database already open?
if (conn.State != ConnectionState.Open) {
shouldClose = true;
conn.Open ();
}
// Execute query
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT * FROM [People] WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Pull values back into class
ID = (string)reader [0];
Name = (string)reader [1];
Occupation = (string)reader [2];
isManager = (bool)reader [3];
ManagerID = (string)reader [4];
}
}
}
// Is this a manager?
if (isManager) {
// Yes, load children
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (conn, childID);
_people.Add (person);
}
}
}
}
// Should we close the connection to the database
if (shouldClose) {
conn.Close ();
}
// Save last connection
_conn = conn;
}
public void Delete(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "DELETE FROM [People] WHERE (ID = @COL1 OR ManagerID = @COL1)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
// Save last connection
_conn = conn;
}
#endregion
}
}
Examinons en détail les modifications ci-dessous.
Tout d’abord, nous avons ajouté plusieurs instructions using requises pour utiliser SQLite et nous avons ajouté une variable pour enregistrer notre dernière connexion à la base de données SQLite :
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
...
private SqliteConnection _conn = null;
Nous allons utiliser cette connexion enregistrée pour enregistrer automatiquement les modifications apportées à l’enregistrement dans la base de données lorsque l’utilisateur modifie le contenu de l’interface utilisateur via la liaison de données :
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("Occupation")]
public string Occupation {
get { return _occupation; }
set {
WillChangeValue ("Occupation");
_occupation = value;
DidChangeValue ("Occupation");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("isManager")]
public bool isManager {
get { return _isManager; }
set {
WillChangeValue ("isManager");
WillChangeValue ("Icon");
_isManager = value;
DidChangeValue ("isManager");
DidChangeValue ("Icon");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
Toutes les modifications apportées aux propriétés Name, Occupation ou isManager sont envoyées à la base de données si les données ont été enregistrées précédemment (par exemple, si la _conn
variable n’est pas null
). Examinons ensuite les méthodes que nous avons ajoutées à Créer, mettre à jour, charger et supprimer des personnes de la base de données.
Créer un enregistrement
Le code suivant a été ajouté pour créer un enregistrement dans la base de données SQLite :
public void Create(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Create new record ID?
if (ID == "") {
ID = Guid.NewGuid ().ToString();
}
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Save manager ID and create the sub record
Person.ManagerID = ID;
Person.Create (conn);
}
// Save last connection
_conn = conn;
}
Nous utilisons un SQLiteCommand
pour créer l’enregistrement dans la base de données. Nous obtenons une nouvelle commande à partir du SQLiteConnection
(conn) que nous avons passé à la méthode en appelant CreateCommand
. Ensuite, nous définissons l’instruction SQL pour écrire réellement le nouvel enregistrement, en fournissant des paramètres pour les valeurs réelles :
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
Plus tard, nous définissons les valeurs des paramètres à l’aide de la Parameters.AddWithValue
méthode sur le SQLiteCommand
. En utilisant des paramètres, nous nous assurons que les valeurs (par exemple, un guillemet unique) sont correctement encodées avant d’être envoyées à SQLite. Exemple :
command.Parameters.AddWithValue ("@COL1", ID);
Enfin, étant donné qu’une personne peut être un responsable et avoir une collection d’employés sous eux, nous appelons de manière récursive la Create
méthode sur ces personnes pour les enregistrer dans la base de données également :
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Save manager ID and create the sub record
Person.ManagerID = ID;
Person.Create (conn);
}
Mise à jour d'un enregistrement
Le code suivant a été ajouté pour mettre à jour un enregistrement existant dans la base de données SQLite :
public void Update(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Update sub record
Person.Update (conn);
}
// Save last connection
_conn = conn;
}
Comme Créer ci-dessus, nous obtenons une SQLiteCommand
à partir du passé SQLiteConnection
et nous définissons notre sql pour mettre à jour notre enregistrement (en fournissant des paramètres) :
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
Nous remplissons les valeurs des paramètres (exemple : command.Parameters.AddWithValue ("@COL1", ID);
) et à nouveau, appelons de manière récursive la mise à jour sur tous les enregistrements enfants :
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Update sub record
Person.Update (conn);
}
Chargement d’un enregistrement
Le code suivant a été ajouté pour charger un enregistrement existant à partir de la base de données SQLite :
public void Load(SqliteConnection conn, string id) {
bool shouldClose = false;
// Clear last connection to prevent circular call to update
_conn = null;
// Is the database already open?
if (conn.State != ConnectionState.Open) {
shouldClose = true;
conn.Open ();
}
// Execute query
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT * FROM [People] WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Pull values back into class
ID = (string)reader [0];
Name = (string)reader [1];
Occupation = (string)reader [2];
isManager = (bool)reader [3];
ManagerID = (string)reader [4];
}
}
}
// Is this a manager?
if (isManager) {
// Yes, load children
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (conn, childID);
_people.Add (person);
}
}
}
}
// Should we close the connection to the database
if (shouldClose) {
conn.Close ();
}
// Save last connection
_conn = conn;
}
Étant donné que la routine peut être appelée de manière récursive à partir d’un objet parent (par exemple, un objet gestionnaire qui charge leur objet employés), du code spécial a été ajouté pour gérer l’ouverture et la fermeture de la connexion à la base de données :
bool shouldClose = false;
...
// Is the database already open?
if (conn.State != ConnectionState.Open) {
shouldClose = true;
conn.Open ();
}
...
// Should we close the connection to the database
if (shouldClose) {
conn.Close ();
}
Comme toujours, nous définissons notre sql pour récupérer l’enregistrement et utiliser les paramètres :
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
Enfin, nous utilisons un lecteur de données pour exécuter la requête et retourner les champs d’enregistrement (que nous copieons dans l’instance de la PersonModel
classe) :
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Pull values back into class
ID = (string)reader [0];
Name = (string)reader [1];
Occupation = (string)reader [2];
isManager = (bool)reader [3];
ManagerID = (string)reader [4];
}
}
Si cette personne est un responsable, nous devons également charger tous ses employés (à nouveau, en appelant de façon récursive leur Load
méthode) :
// Is this a manager?
if (isManager) {
// Yes, load children
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (conn, childID);
_people.Add (person);
}
}
}
}
Suppression d’un enregistrement
Le code suivant a été ajouté pour supprimer un enregistrement existant de la base de données SQLite :
public void Delete(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "DELETE FROM [People] WHERE (ID = @COL1 OR ManagerID = @COL1)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
// Save last connection
_conn = conn;
}
Ici, nous fournissons le code SQL pour supprimer à la fois l’enregistrement des gestionnaires et les enregistrements de tous les employés sous ce responsable (à l’aide de paramètres) :
// Create new command
command.CommandText = "DELETE FROM [People] WHERE (ID = @COL1 OR ManagerID = @COL1)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
Une fois l’enregistrement supprimé, nous supprimons l’instance actuelle de la PersonModel
classe :
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
Initialisation de la base de données
Avec les modifications apportées à notre modèle de données en place pour prendre en charge la lecture et l’écriture dans la base de données, nous devons ouvrir une connexion à la base de données et l’initialiser lors de la première exécution. Ajoutons le code suivant à notre fichier MainWindow.cs :
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
...
private SqliteConnection DatabaseConnection = null;
...
private SqliteConnection GetDatabaseConnection() {
var documents = Environment.GetFolderPath (Environment.SpecialFolder.Desktop);
string db = Path.Combine (documents, "People.db3");
// Create the database if it doesn't already exist
bool exists = File.Exists (db);
if (!exists)
SqliteConnection.CreateFile (db);
// Create connection to the database
var conn = new SqliteConnection("Data Source=" + db);
// Set the structure of the database
if (!exists) {
var commands = new[] {
"CREATE TABLE People (ID TEXT, Name TEXT, Occupation TEXT, isManager BOOLEAN, ManagerID TEXT)"
};
conn.Open ();
foreach (var cmd in commands) {
using (var c = conn.CreateCommand()) {
c.CommandText = cmd;
c.CommandType = CommandType.Text;
c.ExecuteNonQuery ();
}
}
conn.Close ();
// Build list of employees
var Craig = new PersonModel ("0","Craig Dunn", "Documentation Manager");
Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
Craig.Create (conn);
var Larry = new PersonModel ("1","Larry O'Brien", "API Documentation Manager");
Larry.AddPerson (new PersonModel ("Mike Norman", "API Documentor"));
Larry.Create (conn);
}
// Return new connection
return conn;
}
Examinons de plus près le code ci-dessus. Tout d’abord, nous choisissons un emplacement pour la nouvelle base de données (dans cet exemple, le Bureau de l’utilisateur), recherchez si la base de données existe et si ce n’est pas le cas, créez-la :
var documents = Environment.GetFolderPath (Environment.SpecialFolder.Desktop);
string db = Path.Combine (documents, "People.db3");
// Create the database if it doesn't already exist
bool exists = File.Exists (db);
if (!exists)
SqliteConnection.CreateFile (db);
Ensuite, nous établissons la connexion à la base de données à l’aide du chemin que nous avons créé ci-dessus :
var conn = new SqliteConnection("Data Source=" + db);
Ensuite, nous créons toutes les tables SQL dans la base de données dont nous avons besoin :
var commands = new[] {
"CREATE TABLE People (ID TEXT, Name TEXT, Occupation TEXT, isManager BOOLEAN, ManagerID TEXT)"
};
conn.Open ();
foreach (var cmd in commands) {
using (var c = conn.CreateCommand()) {
c.CommandText = cmd;
c.CommandType = CommandType.Text;
c.ExecuteNonQuery ();
}
}
conn.Close ();
Enfin, nous utilisons notre modèle de données (PersonModel
) pour créer un jeu d’enregistrements par défaut pour la base de données la première fois que l’application est exécutée ou si la base de données est manquante :
// Build list of employees
var Craig = new PersonModel ("0","Craig Dunn", "Documentation Manager");
Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
Craig.Create (conn);
var Larry = new PersonModel ("1","Larry O'Brien", "API Documentation Manager");
Larry.AddPerson (new PersonModel ("Mike Norman", "API Documentor"));
Larry.Create (conn);
Lorsque l’application démarre et ouvre la fenêtre principale, nous créons une connexion à la base de données à l’aide du code que nous avons ajouté ci-dessus :
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Get access to database
DatabaseConnection = GetDatabaseConnection ();
}
Chargement de données liées
Avec tous les composants permettant d’accéder directement aux données liées à partir d’une base de données SQLite en place, nous pouvons charger les données dans les différentes vues que notre application fournit et elle s’affiche automatiquement dans notre interface utilisateur.
Chargement d’un enregistrement unique
Pour charger un enregistrement unique où l’ID est connu, nous pouvons utiliser le code suivant :
Person = new PersonModel (Conn, "0");
Chargement de tous les enregistrements
Pour charger toutes les personnes, qu’elles soient responsables ou non, utilisez le code suivant :
// Load all employees
_conn.Open ();
using (var command = _conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People]";
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (_conn, childID);
AddPerson (person);
}
}
}
_conn.Close ();
Ici, nous utilisons une surcharge du constructeur pour que la PersonModel
classe charge la personne en mémoire :
var person = new PersonModel (_conn, childID);
Nous appelons également la classe Data Bound pour ajouter la personne à notre collection de personnes AddPerson (person)
, ce qui garantit que notre interface utilisateur reconnaît la modification et l’affiche :
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
Chargement des enregistrements de niveau supérieur uniquement
Pour charger uniquement des gestionnaires (par exemple, pour afficher des données en mode Plan), nous utilisons le code suivant :
// Load only managers employees
_conn.Open ();
using (var command = _conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE isManager = 1";
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (_conn, childID);
AddPerson (person);
}
}
}
_conn.Close ();
La seule différence réelle dans l’instruction SQL (qui charge uniquement les gestionnaires command.CommandText = "SELECT ID FROM [People] WHERE isManager = 1"
) mais fonctionne de la même façon que la section ci-dessus dans le cas contraire.
Bases de données et zones de liste modifiable
Les contrôles de menu disponibles pour macOS (tels que la zone de liste modifiable) peuvent être définis pour remplir la liste déroulante à partir d’une liste interne (qui peut être prédéfinie dans le Générateur d’interface ou renseignée via du code) ou en fournissant votre propre source de données personnalisée et externe. Pour plus d’informations, consultez Fournir des données de contrôle de menu.
Par exemple, modifiez l’exemple De liaison simple ci-dessus dans Interface Builder, ajoutez une zone de liste modifiable et exposez-la à l’aide d’une sortie nommée EmployeeSelector
:
Dans l’inspecteur d’attributs, case activée les propriétés de saisie semi-automatique et utilise les propriétés de source de données :
Enregistrez vos modifications et revenez à Visual Studio pour Mac à synchroniser.
Fourniture de données de zone de liste modifiable
Ensuite, ajoutez une nouvelle classe au projet appelé ComboBoxDataSource
et faites-le ressembler à ce qui suit :
using System;
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
using Foundation;
using AppKit;
namespace MacDatabase
{
public class ComboBoxDataSource : NSComboBoxDataSource
{
#region Private Variables
private SqliteConnection _conn = null;
private string _tableName = "";
private string _IDField = "ID";
private string _displayField = "";
private nint _recordCount = 0;
#endregion
#region Computed Properties
public SqliteConnection Conn {
get { return _conn; }
set { _conn = value; }
}
public string TableName {
get { return _tableName; }
set {
_tableName = value;
_recordCount = GetRecordCount ();
}
}
public string IDField {
get { return _IDField; }
set {
_IDField = value;
_recordCount = GetRecordCount ();
}
}
public string DisplayField {
get { return _displayField; }
set {
_displayField = value;
_recordCount = GetRecordCount ();
}
}
public nint RecordCount {
get { return _recordCount; }
}
#endregion
#region Constructors
public ComboBoxDataSource (SqliteConnection conn, string tableName, string displayField)
{
// Initialize
this.Conn = conn;
this.TableName = tableName;
this.DisplayField = displayField;
}
public ComboBoxDataSource (SqliteConnection conn, string tableName, string idField, string displayField)
{
// Initialize
this.Conn = conn;
this.TableName = tableName;
this.IDField = idField;
this.DisplayField = displayField;
}
#endregion
#region Private Methods
private nint GetRecordCount ()
{
bool shouldClose = false;
nint count = 0;
// Has a Table, ID and display field been specified?
if (TableName !="" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT count({IDField}) FROM [{TableName}]";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read count from query
var result = (long)reader [0];
count = (nint)result;
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return the number of records
return count;
}
#endregion
#region Public Methods
public string IDForIndex (nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {IDField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
public string ValueForIndex (nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
public string IDForValue (string value)
{
NSString result = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {IDField} FROM [{TableName}] WHERE {DisplayField} = @VAL";
// Populate parameters
command.Parameters.AddWithValue ("@VAL", value);
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
result = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return result;
}
#endregion
#region Override Methods
public override nint ItemCount (NSComboBox comboBox)
{
return RecordCount;
}
public override NSObject ObjectValueForItem (NSComboBox comboBox, nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
public override nint IndexOfItem (NSComboBox comboBox, string value)
{
bool shouldClose = false;
bool found = false;
string field = "";
nint index = NSRange.NotFound;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read () && !found) {
// Read the display field from the query
field = (string)reader [0];
++index;
// Is this the value we are searching for?
if (value == field) {
// Yes, exit loop
found = true;
}
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return index;
}
public override string CompletedString (NSComboBox comboBox, string uncompletedString)
{
bool shouldClose = false;
bool found = false;
string field = "";
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Escape search string
uncompletedString = uncompletedString.Replace ("'", "");
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] WHERE {DisplayField} LIKE @VAL";
// Populate parameters
command.Parameters.AddWithValue ("@VAL", uncompletedString + "%");
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
field = (string)reader [0];
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return field;
}
#endregion
}
}
Dans cet exemple, nous créons une nouvelle NSComboBoxDataSource
zone de liste modifiable qui peut présenter des éléments de zone de liste modifiable à partir de n’importe quelle source de données SQLite. Tout d’abord, nous définissons les propriétés suivantes :
Conn
- Obtient ou définit une connexion à la base de données SQLite.TableName
- Obtient ou définit le nom de la table.IDField
- Obtient ou définit le champ qui fournit l’ID unique pour la table donnée. La valeur par défaut estID
.DisplayField
- Obtient ou définit le champ affiché dans la liste déroulante.RecordCount
- Obtient le nombre d’enregistrements dans la table donnée.
Lorsque nous créons une instance de l’objet, nous transmettons la connexion, le nom de la table, éventuellement le champ ID et le champ d’affichage :
public ComboBoxDataSource (SqliteConnection conn, string tableName, string displayField)
{
// Initialize
this.Conn = conn;
this.TableName = tableName;
this.DisplayField = displayField;
}
La GetRecordCount
méthode retourne le nombre d’enregistrements dans la table donnée :
private nint GetRecordCount ()
{
bool shouldClose = false;
nint count = 0;
// Has a Table, ID and display field been specified?
if (TableName !="" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT count({IDField}) FROM [{TableName}]";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read count from query
var result = (long)reader [0];
count = (nint)result;
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return the number of records
return count;
}
Elle est appelée à tout moment où la TableName
valeur , IDField
ou DisplayField
les propriétés sont modifiées.
La IDForIndex
méthode retourne l’ID unique (IDField
) de l’enregistrement à l’index d’élément de liste déroulante donné :
public string IDForIndex (nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {IDField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
La ValueForIndex
méthode retourne la valeur (DisplayField
) de l’élément à l’index de liste déroulante donnée :
public string ValueForIndex (nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
La IDForValue
méthode retourne l’ID unique (IDField
) de la valeur donnée (DisplayField
) :
public string IDForValue (string value)
{
NSString result = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {IDField} FROM [{TableName}] WHERE {DisplayField} = @VAL";
// Populate parameters
command.Parameters.AddWithValue ("@VAL", value);
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
result = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return result;
}
Renvoie ItemCount
le nombre précomputé d’éléments de la liste comme calculé lorsque les TableName
IDField
propriétés sont DisplayField
modifiées :
public override nint ItemCount (NSComboBox comboBox)
{
return RecordCount;
}
La ObjectValueForItem
méthode fournit la valeur (DisplayField
) pour l’index d’élément de liste déroulante donné :
public override NSObject ObjectValueForItem (NSComboBox comboBox, nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
Notez que nous utilisons les instructions et OFFSET
les LIMIT
instructions de notre commande SQLite pour limiter à l’enregistrement que nous avons besoin.
La IndexOfItem
méthode retourne l’index d’élément déroulant de la valeur (DisplayField
) donnée :
public override nint IndexOfItem (NSComboBox comboBox, string value)
{
bool shouldClose = false;
bool found = false;
string field = "";
nint index = NSRange.NotFound;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read () && !found) {
// Read the display field from the query
field = (string)reader [0];
++index;
// Is this the value we are searching for?
if (value == field) {
// Yes, exit loop
found = true;
}
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return index;
}
Si la valeur est introuvable, la NSRange.NotFound
valeur est retournée et tous les éléments sont désélectionnés dans la liste déroulante.
La CompletedString
méthode retourne la première valeur correspondante (DisplayField
) pour une entrée partiellement typée :
public override string CompletedString (NSComboBox comboBox, string uncompletedString)
{
bool shouldClose = false;
bool found = false;
string field = "";
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Escape search string
uncompletedString = uncompletedString.Replace ("'", "");
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] WHERE {DisplayField} LIKE @VAL";
// Populate parameters
command.Parameters.AddWithValue ("@VAL", uncompletedString + "%");
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
field = (string)reader [0];
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return field;
}
Affichage des données et réponse aux événements
Pour rassembler toutes les pièces, modifiez-les SubviewSimpleBindingController
et faites-le ressembler à ce qui suit :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
using Foundation;
using AppKit;
namespace MacDatabase
{
public partial class SubviewSimpleBindingController : AppKit.NSViewController
{
#region Private Variables
private PersonModel _person = new PersonModel();
private SqliteConnection Conn;
#endregion
#region Computed Properties
//strongly typed view accessor
public new SubviewSimpleBinding View {
get {
return (SubviewSimpleBinding)base.View;
}
}
[Export("Person")]
public PersonModel Person {
get {return _person; }
set {
WillChangeValue ("Person");
_person = value;
DidChangeValue ("Person");
}
}
public ComboBoxDataSource DataSource {
get { return EmployeeSelector.DataSource as ComboBoxDataSource; }
}
#endregion
#region Constructors
// Called when created from unmanaged code
public SubviewSimpleBindingController (IntPtr handle) : base (handle)
{
Initialize ();
}
// Called when created directly from a XIB file
[Export ("initWithCoder:")]
public SubviewSimpleBindingController (NSCoder coder) : base (coder)
{
Initialize ();
}
// Call to load from the XIB/NIB file
public SubviewSimpleBindingController (SqliteConnection conn) : base ("SubviewSimpleBinding", NSBundle.MainBundle)
{
// Initialize
this.Conn = conn;
Initialize ();
}
// Shared initialization code
void Initialize ()
{
}
#endregion
#region Private Methods
private void LoadSelectedPerson (string id)
{
// Found?
if (id != "") {
// Yes, load requested record
Person = new PersonModel (Conn, id);
}
}
#endregion
#region Override Methods
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Configure Employee selector dropdown
EmployeeSelector.DataSource = new ComboBoxDataSource (Conn, "People", "Name");
// Wireup events
EmployeeSelector.Changed += (sender, e) => {
// Get ID
var id = DataSource.IDForValue (EmployeeSelector.StringValue);
LoadSelectedPerson (id);
};
EmployeeSelector.SelectionChanged += (sender, e) => {
// Get ID
var id = DataSource.IDForIndex (EmployeeSelector.SelectedIndex);
LoadSelectedPerson (id);
};
// Auto select the first person
EmployeeSelector.StringValue = DataSource.ValueForIndex (0);
Person = new PersonModel (Conn, DataSource.IDForIndex(0));
}
#endregion
}
}
La DataSource
propriété fournit un raccourci vers l’élément ComboBoxDataSource
(créé ci-dessus) attaché à la zone de liste déroulante.
La LoadSelectedPerson
méthode charge la personne à partir de la base de données pour l’ID unique donné :
private void LoadSelectedPerson (string id)
{
// Found?
if (id != "") {
// Yes, load requested record
Person = new PersonModel (Conn, id);
}
}
Dans la substitution de méthode AwakeFromNib
, nous attachons d’abord une instance de notre source de données de zone de liste modifiable personnalisée :
EmployeeSelector.DataSource = new ComboBoxDataSource (Conn, "People", "Name");
Ensuite, nous répondons à l’utilisateur qui modifie la valeur de texte de la zone de liste modifiable en recherchant l’ID unique associé (IDField
) des données présentant et en chargeant la personne donnée si elle est trouvée :
EmployeeSelector.Changed += (sender, e) => {
// Get ID
var id = DataSource.IDForValue (EmployeeSelector.StringValue);
LoadSelectedPerson (id);
};
Nous chargeons également une nouvelle personne si l’utilisateur sélectionne un nouvel élément dans la liste déroulante :
EmployeeSelector.SelectionChanged += (sender, e) => {
// Get ID
var id = DataSource.IDForIndex (EmployeeSelector.SelectedIndex);
LoadSelectedPerson (id);
};
Enfin, nous remplit automatiquement la zone de liste déroulante et affichons la personne avec le premier élément de la liste :
// Auto select the first person
EmployeeSelector.StringValue = DataSource.ValueForIndex (0);
Person = new PersonModel (Conn, DataSource.IDForIndex(0));
SQLite.NET ORM
Comme indiqué ci-dessus, en utilisant le code source ouvert SQLite.NET Object Relationship Manager (ORM), nous pouvons réduire considérablement la quantité de code nécessaire pour lire et écrire des données à partir d’une base de données SQLite. Ce n’est peut-être pas le meilleur itinéraire à prendre lors de la liaison de données en raison de plusieurs exigences liées au codage clé-valeur et à la liaison de données sur un objet.
Selon le site web SQLite.Net, « SQLite est une bibliothèque de logiciels qui implémente un moteur de base de données SQL transactionnel, serverless, serverless, zéro configuration. SQLite est le moteur de base de données le plus largement déployé dans le monde. Le code source de SQLite se trouve dans le domaine public. »
Dans les sections suivantes, nous allons montrer comment utiliser SQLite.Net pour fournir des données pour une vue table.
Inclusion de l’SQLite.net NuGet
SQLite.NET est présenté sous la forme d’un package NuGet que vous incluez dans votre application. Avant de pouvoir ajouter la prise en charge de la base de données à l’aide de SQLite.NET, nous devons inclure ce package.
Procédez comme suit pour ajouter le package :
Dans le panneau Solution, cliquez avec le bouton droit sur le dossier Packages , puis sélectionnez Ajouter des packages...
Entrez
SQLite.net
dans la zone de recherche et sélectionnez l’entrée sqlite-net :Cliquez sur le bouton Ajouter un package pour terminer.
Création du modèle de données
Ajoutons une nouvelle classe au projet et appelons OccupationModel
. Ensuite, nous allons modifier le fichier OccupationModel.cs et le faire ressembler à ce qui suit :
using System;
using SQLite;
namespace MacDatabase
{
public class OccupationModel
{
#region Computed Properties
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set;}
public string Description { get; set;}
#endregion
#region Constructors
public OccupationModel ()
{
}
public OccupationModel (string name, string description)
{
// Initialize
this.Name = name;
this.Description = description;
}
#endregion
}
}
Tout d’abord, nous incluons SQLite.NET (using Sqlite
), puis nous exposons plusieurs propriétés, chacune d’elles étant écrite dans la base de données lorsque cet enregistrement est enregistré. La première propriété que nous faisons en tant que clé primaire et la définissez sur incrément automatique comme suit :
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
Initialisation de la base de données
Avec les modifications apportées à notre modèle de données en place pour prendre en charge la lecture et l’écriture dans la base de données, nous devons ouvrir une connexion à la base de données et l’initialiser lors de la première exécution. Ajoutons le code suivant :
using SQLite;
...
public SQLiteConnection Conn { get; set; }
...
private SQLiteConnection GetDatabaseConnection() {
var documents = Environment.GetFolderPath (Environment.SpecialFolder.Desktop);
string db = Path.Combine (documents, "Occupation.db3");
OccupationModel Occupation;
// Create the database if it doesn't already exist
bool exists = File.Exists (db);
// Create connection to database
var conn = new SQLiteConnection (db);
// Initially populate table?
if (!exists) {
// Yes, build table
conn.CreateTable<OccupationModel> ();
// Add occupations
Occupation = new OccupationModel ("Documentation Manager", "Manages the Documentation Group");
conn.Insert (Occupation);
Occupation = new OccupationModel ("Technical Writer", "Writes technical documentation and sample applications");
conn.Insert (Occupation);
Occupation = new OccupationModel ("Web & Infrastructure", "Creates and maintains the websites that drive documentation");
conn.Insert (Occupation);
Occupation = new OccupationModel ("API Documentation Manager", "Manages the API Documentation Group");
conn.Insert (Occupation);
Occupation = new OccupationModel ("API Documenter", "Creates and maintains API documentation");
conn.Insert (Occupation);
}
return conn;
}
Tout d’abord, nous obtenons un chemin d’accès à la base de données (bureau de l’utilisateur dans ce cas) et voyons si la base de données existe déjà :
var documents = Environment.GetFolderPath (Environment.SpecialFolder.Desktop);
string db = Path.Combine (documents, "Occupation.db3");
OccupationModel Occupation;
// Create the database if it doesn't already exist
bool exists = File.Exists (db);
Ensuite, nous établissons une connexion à la base de données au niveau du chemin que nous avons créé ci-dessus :
var conn = new SQLiteConnection (db);
Enfin, nous créons la table et ajoutons quelques enregistrements par défaut :
// Yes, build table
conn.CreateTable<OccupationModel> ();
// Add occupations
Occupation = new OccupationModel ("Documentation Manager", "Manages the Documentation Group");
conn.Insert (Occupation);
Occupation = new OccupationModel ("Technical Writer", "Writes technical documentation and sample applications");
conn.Insert (Occupation);
Occupation = new OccupationModel ("Web & Infrastructure", "Creates and maintains the websites that drive documentation");
conn.Insert (Occupation);
Occupation = new OccupationModel ("API Documentation Manager", "Manages the API Documentation Group");
conn.Insert (Occupation);
Occupation = new OccupationModel ("API Documenter", "Creates and maintains API documentation");
conn.Insert (Occupation);
Ajout d’une vue table
Par exemple, nous allons ajouter une vue table à notre interface utilisateur dans le générateur d’interface de Xcode. Nous allons exposer cette vue de table via un point de sortie (OccupationTable
) afin de pouvoir y accéder via du code C# :
Ensuite, nous allons ajouter les classes personnalisées pour remplir cette table avec des données de la base de données SQLite.NET.
Création de la source de données de table
Nous allons créer une source de données personnalisée pour fournir des données pour notre table. Tout d’abord, ajoutez une nouvelle classe appelée TableORMDatasource
et faites-la ressembler à ce qui suit :
using System;
using AppKit;
using CoreGraphics;
using Foundation;
using System.Collections;
using System.Collections.Generic;
using SQLite;
namespace MacDatabase
{
public class TableORMDatasource : NSTableViewDataSource
{
#region Computed Properties
public List<OccupationModel> Occupations { get; set;} = new List<OccupationModel>();
public SQLiteConnection Conn { get; set; }
#endregion
#region Constructors
public TableORMDatasource (SQLiteConnection conn)
{
// Initialize
this.Conn = conn;
LoadOccupations ();
}
#endregion
#region Public Methods
public void LoadOccupations() {
// Get occupations from database
var query = Conn.Table<OccupationModel> ();
// Copy into table collection
Occupations.Clear ();
foreach (OccupationModel occupation in query) {
Occupations.Add (occupation);
}
}
#endregion
#region Override Methods
public override nint GetRowCount (NSTableView tableView)
{
return Occupations.Count;
}
#endregion
}
}
Lorsque nous créons une instance de cette classe ultérieurement, nous transmettons notre connexion de base de données ouverte SQLite.NET. La LoadOccupations
méthode interroge la base de données et copie les enregistrements trouvés en mémoire (à l’aide de notre OccupationModel
modèle de données).
Création du délégué de table
La classe finale dont nous avons besoin est un délégué de table personnalisé pour afficher les informations que nous avons chargées à partir de la base de données SQLite.NET. Nous allons ajouter un nouveau TableORMDelegate
à notre projet et le faire ressembler à ce qui suit :
using System;
using AppKit;
using CoreGraphics;
using Foundation;
using System.Collections;
using System.Collections.Generic;
using SQLite;
namespace MacDatabase
{
public class TableORMDelegate : NSTableViewDelegate
{
#region Constants
private const string CellIdentifier = "OccCell";
#endregion
#region Private Variables
private TableORMDatasource DataSource;
#endregion
#region Constructors
public TableORMDelegate (TableORMDatasource dataSource)
{
// Initialize
this.DataSource = dataSource;
}
#endregion
#region Override Methods
public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{
// This pattern allows you reuse existing views when they are no-longer in use.
// If the returned view is null, you instance up a new view
// If a non-null view is returned, you modify it enough to reflect the new data
NSTextField view = (NSTextField)tableView.MakeView (CellIdentifier, this);
if (view == null) {
view = new NSTextField ();
view.Identifier = CellIdentifier;
view.BackgroundColor = NSColor.Clear;
view.Bordered = false;
view.Selectable = false;
view.Editable = false;
}
// Setup view based on the column selected
switch (tableColumn.Title) {
case "Occupation":
view.StringValue = DataSource.Occupations [(int)row].Name;
break;
case "Description":
view.StringValue = DataSource.Occupations [(int)row].Description;
break;
}
return view;
}
#endregion
}
}
Ici, nous utilisons la collection de la source de Occupations
données (que nous avons chargée à partir de la base de données SQLite.NET) pour renseigner les colonnes de notre table via la GetViewForItem
substitution de méthode.
Remplissage de la table
Avec tous les éléments en place, nous allons remplir notre table lorsqu’elle est gonflée à partir du fichier .xib en substituant la AwakeFromNib
méthode et en la faisant ressembler à ce qui suit :
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Get database connection
Conn = GetDatabaseConnection ();
// Create the Occupation Table Data Source and populate it
var DataSource = new TableORMDatasource (Conn);
// Populate the Product Table
OccupationTable.DataSource = DataSource;
OccupationTable.Delegate = new TableORMDelegate (DataSource);
}
Tout d’abord, nous avons accès à notre base de données SQLite.NET, en créant et en la remplit s’il n’existe pas déjà. Ensuite, nous créons une nouvelle instance de notre source de données table personnalisée, transmettons notre connexion de base de données et nous l’attachons à la table. Enfin, nous créons une instance de notre délégué de table personnalisé, transmettons notre source de données et l’attachons à la table.
Résumé
Cet article a examiné en détail l’utilisation de la liaison de données et du codage clé-valeur avec des bases de données SQLite dans une application Xamarin.Mac. Tout d’abord, il a examiné l’exposition d’une classe C# à l’aide Objective-C du codage clé-valeur (KVC) et de l’observation de clé-valeur (KVO). Ensuite, il a montré comment utiliser une classe conforme KVO et Data Bind à des éléments d’interface utilisateur dans le Générateur d’interface de Xcode. L’article a également abordé l’utilisation des données SQLite via l’orm SQLite.NET et l’affichage de ces données dans une vue table.
Liens associés
- Hello, Mac
- Liaison de données et codage clé-valeur
- Contrôles standard
- Vues Table
- Affichages hiérarchiques
- Vues de collection
- Guide de programmation de codage clé-valeur
- Présentation des rubriques de programmation des liaisons Cocoa
- Présentation de la référence sur les liaisons de cacao
- NSCollectionView
- Human Interface Guidelines pour macOS