L'application WinForm

L'application WinForm et le composant d'accès aux données

Version PDF

Vous disposez d'un squelette de l'application –AntoineVersion0- ainsi que de la base de données
à restaurer dans SqlServer –Bd_Antoine.dat-.

 

Travail à faire
Télécharger les deux fichiers, restaurer la base Bd_Antoine, ouvrir la solution avec VisualStudio.

Dans le formulaire FrmDebut, écrire le code qui permet d'enchaîner les différents formulaires.

 

     1.-Cas d'utilisation : gestion du personnel de service

Le formulaire à construire doit permettre de gérer toutes les opérations concernant les serveurs :

 


Formulaire de gestion des serveurs

Travail à faire
Ajouter les composants nécessaires sans faire figurer (pour l'instant) le composant de navigation. Le composant qui permet d'afficher les dates d'ambauche est un DateTimePicker.

Le code d'accès à la base de données va être placé dans un composant spécifique –une DLL- ; ceci permet de déporter dans un module spécifique une fonctionnalité qui pourra être éventuellement partagée. Cette organisation offre également de bien meilleures performances en terme de maintenance. Le formulaire ne collaborera ainsi qu'avec une structure en mémoire, un DataSet (typé) et bénéficiera néanmoins du mécanisme de liaison de données (DataBinding) ; et ceci sans avoir à connaître l'origine du DataSet.

 

1.a Création de la DLL d'accès aux données.

 

Travail à faire
Dans l'explorateur de solution, faire un click droit sur la Solution et choisir "Ajouter un projet" ; choisissez ensuite un projet "Bibliothèque de classes" (Visual C#) :


Ajout d'un nouveau projet de type "Bibliothèque de classes"

Nommez-le "LibDB", et faire "OK".
Un deuxième projet apparaît dans la solution (explorateur de solutions).
Supprimer la classe "Class1" générée automatiquement.
Nous obtenons la configuration suivante :


Structure de la solution

A partir d'un click droit sur le projet "LibDB", ajouter un nouvel élément de type "Classe Component" :


Ajout d'un composant au projet "LibDB"

Nommez le composant "FacadeRestaurant" (la classe se chargera de l'accès aux données pour l'application de type WinForm).

 

Remarque :
         - Si l'on ouvre l'explorateur Windows, on peut visualiser l'organisation des répertoires :

 


Organisation des répertoires

          - Si l'on génère la solution (compilation à partir du menu Générer/Générer la solution), on peut voir dans l'explorateur Windows la DLL LibDB.dll (LibDB/debug/bin).

La classe FacadeRestaurant de type composant permet d'utiliser le mode conception et dispose de la boite à outil des WinForm ; ce qui sera bien pratique pour générer un DataSet liée à la base de données !

Travail à faire.
En mode conception, ajouter une source donnée (qui va pointer sur la base de données Bd_Antoine de SQL Server) ; configurez-le en utilisant l'assistant : Données/Ajouter une source de données.

Préciser la source de données (SQL Server), le Serveur et le nom de la base :

Choisir les objets de la base (tables et procédures stockées), nommer le DataSet (DSRestaurant) :

Remarque : la base contient des procédures stockées que nous allons utiliser ; c'est pourquoi nous demandons à Visual Studio de générer un objet qui nous aidera dans l'appel de ces procédures.

Terminer la configuration de la source de données.
En observant l'explorateur de solution, on peut observer la présence dans la solution du DataSet DSRestaurant :

Nous allons lier ce DataSet (typé) à la classe FacadeRestaurant.
Revenir sur la classe FacadeRestaurant en mode Design ; ajouter un DataSet (à partir de la boite à outils) :

Nommer le DataSet typé dsRestaurant
Ajouter un composant de typa TableAdapter, nommez-le adapterServeur :

L'adapter aura la responsabilité de charger le DataSet à partir de la base et assurera les mises à jours de la base à partir du dataset.

Ecrire les deux méthodes publiques qui vont permettre de charger le DataSet (méthode fill), mettre à jour la base de données (méthode update) :
                                        
public void chargeListeServeur(DSRestaurant dsRestaurant)
                                         public void sauveListeServeurs(DSRestaurant dsRestaurant)
    

La gestion de l'identifiant de la table serveur est assurée par SqlServer ; il est en effet possible de demander la génération d'identifiants aléatoires.
Une procédure stockée dans Sql Server, appelée nouvelID, prend en charge cette génération et retourne (en argument) l'identifiant :

Pour appeler cette procédure stockée, il faut procéder ainsi :
                   - Déposer un TableAdapter dans le formulaire ; celui dont la classe a été générée par l'environnement (QueriesTableAdapter) ; nommez-le AdapterProcStock
                   - Appeler la procédure stockée, par l'intermédiaire de cet objet, dans une méthode spécifique que l'on vous fournit :

public object getNouveauId()
{
       Guid? id = null;
       AdapterProcStock.nouvelID(ref id);
       return id;
}

Travail à faire.
Copiez cette méthode dans la classe FacadeRestaurant. Après avoir ajouté les deux clauses using :
             using System.Data;
             using System.Data.SqlClient;

Générez le projet afin de vérifier l'absence d'erreur.

 

 
1.b Réalisation de l'interface de gestion du serveur : FrmServeur

Travail à faire.
Ouvrir le formulaire de gestion des serveurs, aller dans l'explorateur de solution et ajouter dans le projet WinForm une référence à la DLL (LibDB) :

Faire Sélectionner/OK ; on voit apparaître la nouvelle référence dans le projet WinForm :

Déposer dans le formulaire "FrmServeur" un DataSet typé (référencé)


Ajout d'un DataSet typé référencé

Modifier le nom du DataSet :


dsRestaurant de type DSRestaurant

Travail à faire
Ajouter une clause using : using LibDB
Ajouter un attribut privé de la classe FacadeRestaurant ; dans le constructeur, générer une instance.
Appeler sur cette instance la méthode de chargement du DataSet.

Il est maintenant possible réaliser le DataBinding liant le DataSet aux composants graphiques. ceci va se réaliser en trois étapes :

- Etape 1. Configuration du Binding

Ajouter dans le formulaire le composant qui va réaliser la liaison de données entre le formulaire et le DataSet (dsRestaurant).
Configurez-le comme indiqué :

- Etape 2. Configuration de la navigation.

Ajouter dans le formulaire le composant qui va permettre de naviguer dans le DataSet.
Configurez-le comme indiqué :

 

- Etape 3. Liaison avec les composant graphiques.
Lier chaque composant graphique à un champ de la BindingSource.
Par exemple pour la zone de texte txtNom :

 

Liaison du champ nom du BindingSource à la propriété Text du composant graphique

Ajouter le code nécessaire concernant les deux boutons de confirmation et d'annulation des mises à jour (utiliser les méthodes de la classe FacadeRestaurant)
Tester l'application sur deux points toujours délicats : l'ajout d'un nouveau serveur et la suppression d'un serveur qui a des liens avec une autre table.
Dans ces deux cas l'application actuelle n'est pas satisfaisante.

Gestion de l'ajout d'un nouveau serveur.

Le problème provient de l'identifiant qui n'est pas présent dans le formulaire ; ainsi lorsque l'on ajoute un nouveau serveur, la table Serveur du DataSet tente d'ajouter une ligne avec un identifiant null !!

Pour résoudre ce problème, on va intervenir au moment de la demande d'ajout en demandant à la classe FacadeRestaurant de nous fournir cet identifiant.
Double-clicker sur l'icone + du BindingNavigator afin de faire apparaître le code (vide actuellement) de son gestionnaire d'événement. Copier le code suivant :

private void bindingNavigatorAddNewItem_Click(object sender, EventArgs e)
{
          DataRow ligne = ((DataRowView)bdgServeur.Current).Row;
          ligne[0] = fc.getNouveauId();
}

Gestion de la suppression d'un serveur lié à une autre table.
La stratégie sera différente ici, nous allons utiliser un gestionnaire d'erreur (try/catch)

 

Travail à faire
Mettre en oeuvre le gestionnaire d'erreur

Gestion de l'anulation des modifications : bouton Annuler
Utiliser la méthode RejectChanges.

        2.-Cas d'utilisation : gestion des plats

Deux formulaires seront utilisés, l'un pour afficher la liste des plats par famille, et l'autre pour créer un nouveau plat.

                 2.a Liste des plats.

la sélection de la famille fait apparaître les plats de cette famille.
Le formulaire attendu est le suivant :

Travail à faire.
Ajouter au formulaire les composants nécessaires : une combolist et un datagridview

La zone de liste contiendra toutes les familles et le DataGridView les plats associés.

Comme pour le premier cas d'utilisation, nous allons d'abord enrichir la DLL d'accès aux données par des méthodes de chargement des familles et des plats pour un type de famille.

2.a.1 Ajout de méthodes dans la classe FacadeRestaurant.

Le formulaire souhaité fait appel à une relation entre deux tables liées par une clé étrangère ; il faut vérifier que cette relation est bien présente dans le DataSet. Pour cela ouvrir le fichier DSRestaurant.xsd (à partir de l'exploratuer de solution) :

Si ce n'est pas le cas, mettre en place cette relation en joignant (cliquer/glisser) les champs à lier :

 

Travail à faire.
Dans le composant d'accès aux données, ajouter en mode conception deux adapters :

Ecrire les 2 méthodes qui chargent le DataSet:
                       public void chargeListePlats(DSRestaurant dsRestaurant)
                       public void chargeListeFamilles(DSRestaurant dsRestaurant)

Générer le projet afin de vérifier qu'il n'y a pas d'erreur de syntaxe.

2.a.2 Mise en oeuvre dans le formulaire

 

Ajouter un DataSet et un composant de binding dans l'interface :


Ajout des composants DataSet et BindingSouce

Liaison de la comboxbox des familles

Configurer le bindingsource afin qu'il pointe sur la table Famille :

Pour configurer le DataGridView, configurer afin que la souce de donnée pointe sur la table Plat liée à la table Famille :

 

Par défaut, le dataGridView fait apparaître toutes les colonnes de la table Plat, pour ne faire apparaître que certaine colonnes, procérer ainsi :

Sélectionner les deux colonnes désignation et prix :


Selection des colonnes du dataGridView

Travail à faire.
Dans le constructeur du formulaire écrire le code qui permet de charger le DataSet avec les tables Plat et Famille. Pour empécher les modificatins du DataGridView, mettre la propriété Enabled à false. Tester l'ensemble.

                      1.b-2 Ajout d'un nouveau plat

Nous désirons obtenir la formulaire suivant :


Création d'un plat

Le ComboBox est lié à la table famille, les deux zones de texte (désignation et prix) sont indépendants des données.

Travail à faire.
En s'inspirant du formulaire précédent, ajouter les composants et écrire le code qui va permettre d'afficher les différentes familles dans le ComboBox (cmbFamille); tester.

Pour insérer un nouveau plat, une procédure stockée est présente dans la base Sql Server ; elle n'est pas à écrire. On vous fournit son code :

Cette procédure stockée est appelée dans la DLL, grace à la méthode creeNouveauPlat dont on vous fournit une partie du code :

public void creeNouveauPlat(string description, string prix, string idFamille)
{
        try
        {
               // code à écrire
         }
         catch (Exception ex)
       {
              throw new Exception("Erreur à l'insertion");
        }
}

        
   

Le bouton valider doit appeler cette méthode.

Travail à faire
En vous inspirant de l'appel de la procédure stockée présentée dans le premier cas d'utilisation, compléter le code cette méthode.
Appelez cette méthode dans le bouton Valider de l'interface
.
Tester.

 

      3. Cas d'utilisation : gestion des tables, mise à jour des nombres de couverts par table

Le formulaire attendu est le suivant :


Liste des tables

Remarque : la suppression d'une table a dans notre application peu de sens (des attributions pointant sur des tables inexistantes)

3.a Première version du formulaire

Travail à faire.
En vous inspirant de la gestion des serveurs, écrire dans la DLL et dans le formulaire le code nécessaire.

Modifier une propriété du dataGridView afin d'interdire toute suppression de table.

3.b Deuxième version en gérant les erreurs

Si l'on teste systématique le formulaire, on est confronté à deux types d'erreur possibles : un format de donnée non conforme (données saisies non numériques) et un ajout d'un numéro de table déjà existant.
Nous allons intervenir sur ces deux types d'erreur en utilisant un événement dédié aux erreurs, associé au DataGridView :
private void dgvTables_DataError(object sender, DataGridViewDataErrorEventArgs e)
Cet événement prend comme paramètre l'erreur (e) cause de l'exception.
On vous fournit une partie du code de gestion de l'événement

private void dgvTables_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
           if(e.Exception is ?)
                     MessageBox.Show("Ce numéro de table existe déjà");
           if(e.Exception is ? )
                     MessageBox.Show("le format de la donnée doit être numérique");
}

Travail à faire.
Faire apparaître la signature du gestionnaire d'événement
En regardant l'aide concernant la classe DataGridViewDataErrorEventArgs et en vous aidant de l'aide contextuelle, copier et compléter le code fourni ; tester.

 

 
 

         4. Cas d'utilisation : gestion des tables, attribution des tables aux serveurs

4.a Sélection du serveur


Sélection d'un serveur

Comme pour les autres cas d'utilisation, il faudra ajouter des fonctionnalités dans la bibliothèque d'accès aux données. Par contre, si nous voulons utiliser le DataBinding sur un champ concaténé (nom+prénom) il faudra procéder un peu différemment.

Création d'un TableAdapter à partir d'une nouvelle requête :

Dans la DLL, à partir du schéma relationnel (fichier DSRestaurant.xsd), ajouter un Tableadapter :

Configurez-le, en remplissant les différents écrans proposés. La requête qui permet de concaténer les deux champs (nom et prénom) est la suivante :

Teminer la configuration

On peut voir la nouvelle table (nommez-la SERVEURPARNOMPRENOM) dans le schéma :

Dans la classe facadeRestaurant, en mode design, un nouveau TableAdapter est disponible : SERVEURPARNOMPRENOMTableAdapter

Déposez ce nouveau composant dans la classe en mode design :

Nommez-le adapterserveurparnomprenom ; celui-ci est maintenant disponible dans le code cette classe.

Travail à faire
Dans la DLL écrire la méthode qui permet de charger la table SEVEURPARNOMPRENOM à l'aide du TableAdapter correspondant.
Dans l'interface ajouter les composants nécessaires et écrire le code qui va charger le ComboBox contenant les nom et prénom des serveurs.

4.b Attribution des tables au serveur sélectionné

L'affichage des tables à attribuer se fait dans un composant de type DataGridView
Si l'on coche des tables pour un serveur, elles ne seront plus disponibles pour un autre serveur :

Gestion de l'attribution des tables aux serveurs

Commentaires :
Les tables 2, 5 et 8 n'apparaissent pas pour l'attribution du second serveur.
Les tables attribuées à un serveur apparaissent en premier à l'affichage.

Avant de compléter ce formulaire (voir 4.c), nous allons mettre en place des données à utiliser

Pour obtenir les listes des tables attribuées à un serveur (le jour courant) et les listes non attribuées (le jour courant) ; nous utiliserons deux TableAdapter que nous allons configurer à partir de l'environnement prévu pour cela : le schéma DSRestaurant.xsd.
Le premier TableAdapter récupèrera les tables déjà attribuées à un serveur (le jour courant) et contiendra en plus un champ booléen à vrai. Le second récupèrera toutes les tables non attribuées, ainsi qu'un champ booléen à faux.
Pour que ces informations puissent être liées dans l'interface, il faudra fusionner les deux tables. Mais attention car pour fusionner deux tables il faut qu'elles aient la même structures (les mêmes champs construits à partir des instructions SELECT).
Dans notre cas, la structure commune des deux tables sera :
TABLE_SERVICE.id, TABLE_SERVICE.nbPlaces, attribuee (champ ajouté de type booléen)

4.b.1 Liste des tables attribuées à un serveur le jour courant

Déposer un TableAdapter et configurer-le de manière à obtenir :

Remarque : la requête SQL doit sélectionner les id et nbPlaces des tables attribuées à un serveur dont l'identifiant est passé en paramètre de la requête ; on vous fournit une partie de cette requête :
SELECT id, nbPlaces FROM TABLE_SERVICE
...
where a.idServeur = @idServ
and a.dateJour = convert(varchar,GETDATE(),103) ) order by a.idTableService

Pour insérer le champ attribuee, faire clic droit sur la table et indiquer ajouter/colonne ; préciser le nom et le type (booléen)

Travail à faire.
Configurer le TableAdapter en conséquence : requête, nom du TableAdapter.

4.b.2 Liste des tables non attribuées le jour courant

Il faut faire de même pour les tables non attribuées.

Remarque : la requête SQL doit sélectionner les tables de la relation Table_service qui ne se trouvent pas dans la relation Attribution le jour courant

Travail à faire.
Configurer le TableAdapter en conséquence : requête, nom du TableAdapter.

Il faut ensuite écrire la méthode qui va charger le DataSet avec ces deux tables ; déposer deux objets de type adapter :

On vous fournit une partie de cette méthode :
            public void chargeListeAttributionTablesServeur(DSRestaurant dsRestaurant, String idServeur)
         {
               try
               {
                      Guid GuidServeur = new Guid(idServeur);
                      dsRestaurant.ATTRIBUTIONTABLES.attribueeColumn.DefaultValue = "true";
                       adapterAttributionTables.Fill(dsRestaurant.ATTRIBUTIONTABLES, ? );
                       dsRestaurant.TABLESNONATTRIBUEES.attribueeColumn.DefaultValue = ?;
                       adapterTablesNonAttribuees.Fill(dsRestaurant.TABLESNONATTRIBUEES);
                       ?              // cette instruction permet de fusionner les deux tables
                 }
                 catch (Exception ex)
                 {
                        throw new Exception("Erreur au chargement");
                  }
            }

Travail à faire
Compléter la méthode en remplaçant les points d'interrogation. Compiler afin de vérifier la syntaxe

4.c Construction de l'interface d'attribution des tables

 

Vous allez compléter le formulaire d'attribution en ajoutant les différents composants.
Ajouter un DataGridView pour obtenir (en conception) :

Remarque : vous pouvez configurer plus finement le DataGridView (nom des colonnes ici) en faisant un clic droit sur le composant/Modifier les colonnes :

Travail à faire.
Terminer la configuration du DataGridView (ajout d'un composant de Binding, liaisons)
Dans l'événement clic du boutons Afficher, écrire l'appel de la méthode chargeListeAttributionTablesServeur qui charge le DataSet. Tester

4.d Enregistrement des attributions.

Il faut écrire dans la DLL une méthode qui enregistre les attributions en cours pour un serveur
On vous fournit une partie du code :
public void sauveListeAttributions(DSRestaurant dsRestaurant,String idServ)
{
            string aujourdhui = DateTime.Now.Date.ToString();
            string reqD = "delete from attribution where ? ";     // suppression de toutes les attributions
                                                                                         // du serveur pour ce jour

            try
            {
                     SqlConnection maConnexion = adapterAttribution.Connection;
                     maConnexion.Open();
                     SqlCommand cmdD = new SqlCommand(reqD, maConnexion);
                     cmdD.ExecuteNonQuery();
                     foreach (DataRow dr in dsRestaurant.ATTRIBUTIONTABLES)
                     {
                           if( ? )                                                                    // Si le champ attribuee est à vrai
                           {
                               string reqI = "insert into attribution values ( ? );";   // Insertion des nouvelles attributions
                               SqlCommand cmdI = new SqlCommand(reqI, maConnexion);
                               cmdI.ExecuteNonQuery();
                             }
                      }
               }
              catch (Exception ex)
              {
                      throw new Exception("Erreur lors de l'attribution");
               }
                finally
               {
                       adapterAttribution.Connection.Close();
                }
             }

 

Travail à faire.
Compléter le code de la méthode. Tester.

             5. Cas d'utilisation : visualisation des notes.

Le formulaire attendu est le suivant :


Visualisation des détails d'une note d'un serveur à une date donnée

Travail à faire
Dans le module d'accès aux données écrire les deux méthodes :
                             
public void chargeListeNotes(DSRestaurant dsRestaurant,string idServeur, string dateJour)
                             
public void chargeDetailsNote(DSRestaurant dsRestaurant,string idServeur, string dateJour,string idNote)

Dans la Winform, le ComboBox des nom et prénom des serveurs est chargé comme pour les formulaires précédents
Ecrire le code des gestionnaires des événements click.