Binder dataGridView

thomic - 22 janv. 2015 à 21:49
thomasmic Messages postés 8 Date d'inscription samedi 24 janvier 2015 Statut Membre Dernière intervention 3 mars 2020 - 25 janv. 2015 à 21:24
Bonjour,

Je vous explique la configuration du programme.

J'ai tout d'abord une classe Produit avec divers propriétés, dont la propriété nom.

Ensuite j'ai une classe Consommation qui contient comme propriété Un produit et une quantité.

et une dernière classe Commande qui comprend une liste de consommations.

Est-il possible de binder un dataGridView à mon objet Commande et afficher le nom du produit et la quantité ?

Merci pour votre aide.

14 réponses

Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
Modifié par Whismeril le 22/01/2015 à 22:17
Bonjour,

deux options me viennent à l'esprit:
-1, dans consommation sur mets une propriété qui remonte le nom du produit, et quand tu bindes ta liste de consol, c'est cette propriété que tu affiches.
-2, tu fais une belle requête linq qui affiche ce que tu veux et c'est son résultat que tu bindes, un truc commes ça:

var monBinding = (from conso in Commande.Liste
                                                 select new 
                                                 {  
                                                       Nom = conso.Produit.Nom,
                                                       Quantite = conso.Quantite,
                                                       Numero = Commande.Numero,
                                                       //etc..
                                                  }).toList();


La modération m'amène à intervenir dans de nombreux posts, mais le seul langage que je maitrise est le C#, un peu de VB aussi. Pour vos codes pensez à la coloration. Réponse trouvée ->Question Résolue
1
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
24 janv. 2015 à 07:09
Bonjour, ok.

Plusieurs hypothèses:

-Dans la requête linq retire le ToList(), cette méthode force l'exécution instantanée de la requête. Peut être qu'en laissant "libre" l'exécution différée ça marchera.
-Il faut signaler au binding qu'une consommation a été ajoutée, pour cela comment par implémenter INotifyPropertyChanged dans la classe Commande, abonne le formulaire du datagridview a cet événement et dans un premier temps refait la requête et le binding.
-Avec la requête différée est INotifyPropertyChanged tu utilises le binding via un binding source et dans ses propriétés (je ne peux pas vérifier ou ce matin), tu lui indique qu'il réagit à propertychanged.
1
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
24 janv. 2015 à 07:39
D'ailleurs, le fait que aies une méthode Add perso, me fais revoir mon avis sur l'intérêt de la requête LinQ.

Dans ta classe Commade, tu ajoutes une propriété, en lecture seule pour l'extérieur:

public string NomProduit {get; private set;}


et tu modifies Add comme ça:
ublic void Add(Produit produit)
        {
            int index = this.IndexOf(produit);

            if (index == -1)
            {
                consommation = new Consommation(produit, 1);
                this.ListeConsommations.Add(consommation);
                NomProduit = produit.Nom;
            }
            else
            {
                this.ListeConsommations[index].quantite++;
            }
        }



Pour le binding, tu utilises la méthode du BindingSource. Et plus de requête.
1
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
24 janv. 2015 à 15:30
: le datagridview se met bien à jour lorsqu'un nouvel élément est ajouté à la liste. mais pas lorsque la quantité change. Pourtant le onPropertyChanged est sur mes méthodes Add et Remove et donc l'évènement devrait être levé dans les deux cas.

C'est le point que je n'avais pas le temps de retrouver ce matin.
Dans les propriétés du datagridview, tu vas dans {Binding} / Avancé, la case de saisie parait vide mais si tu place la souris tout à droite un bouton apparait. Clique dessus.
Tu devrais voir une fenêtre de ce type.


Paramètre en OnPropertyChanged pour chacun de tes champs.



Deuxième problème : j'aimerais bien trier tout abord mes colonnes. Par défaut il prend l'ordre dans lequel les accesseurs sont définis dans ma classe. Est-ce qu'il y a une autre manière de faire.

Retourne voir mon tuto, dans la partie de personnalisation des colonnes, la deuxième image, il y a la liste des colonnes à gauche, avec les flèches tu peux changer l'ordre.

j'aimerai bien qu'il ne m'affiche pas mon objet
heu pas compris, quel objet?

ajouter une colonne à mon datagridview avec un bouton.
, je ne sais pas comme ça, jamais essayé et je dois repartir.
Le bouton servirais à lancer une méthode?

1

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
25 janv. 2015 à 18:07
Bonsoir.

Premier point, la convention de nommage dit que les nom de propriété et de paramètre de méthode commencent par une majuscule, et les noms de variables internes par une minuscule.
Dans ton code tu fais l'inverse.

Ensuite pour pour ton problème. Je ne pensais pas que tu allais binder sur le type commande mais sur le type consommation, j'aurais du être plus précis.
Voila tes classes telles que que je les vois (voir IndexOf je me suis amusé):
    public class Commande
    {
        private BindingList<Consommation> listeConsommations;
        private Consommation consommation;

        public Commande()
        {
            listeConsommations = new BindingList<Consommation>();
        }

        public BindingList<Consommation> ListeConsommations
        {
            get { return listeConsommations; }
        }

        private int IndexOf(Produit Produit)
        {
            //Je me suis permis de remplacer ta boucle par une requete Linq, ça n'est pas plus simple, juste pour l'exemple
            if (listeConsommations.Any(c => c.NomProduit == Produit.Nom))
                return listeConsommations.Select((c, i) => new { Nom = c.NomProduit, Index = i }).Single(x => x.Nom == Produit.Nom).Index;
            else return -1;
        }

        public void Add(Produit Produit)
        {
            int index = this.IndexOf(Produit);

            if (index == -1)
            {
                consommation = new Consommation(Produit, 1);
                this.ListeConsommations.Add(consommation);
            }
            else
            {
                this.ListeConsommations[index].Quantite++;
            }
            
        }

        public void Remove(Produit Produit)
        {
            int index = this.IndexOf(Produit);

            if (index != -1)
            {
                if (this.ListeConsommations[index].Quantite == 1)
                {
                    this.ListeConsommations.Remove(this.ListeConsommations[index]);
                }
                else
                {
                    this.ListeConsommations[index].Quantite--;
                }

            }
        }

    }

    public class Consommation:INotifyPropertyChanged
    {
        public Consommation(Produit P, int Quantite)
        {
            ProduitCommande = P;
            quantite = Quantite;
            NomProduit = P.Nom;
            RisePropertyChanged("NomProduit");
        }

        private int quantite;

        public int Quantite
        {
            get { return quantite; }
            set 
            {
                if (quantite != value)
                {
                    quantite = value;
                    RisePropertyChanged("Quantite");
                }
            }
        }

        private Produit produit;

        public Produit ProduitCommande
        {
            get { return produit; }
            set 
            {
                if (produit != value)
                {
                    produit = value;
                    RisePropertyChanged("ProduitCommande");
                }
            }
        }

        public string NomProduit { get; private set; }


        #region INotifyPropertyChanged Membres

        public event PropertyChangedEventHandler PropertyChanged;

        private void RisePropertyChanged(string NomProp)
        {
            if (this.PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs(NomProp));
        }

        #endregion
    }

    public class Produit
    {
        public string Nom { get; set; }
    }


Les paramètres de l'assistant
je vais chercher comme source l'objet Consommation



Je supprime la colonne produit et renomme les 2 autres



L'affectation du binding
            Produit the = new Produit { Nom = "Thé" };
            Produit cafe = new Produit { Nom = "Café" };
            Produit biere = new Produit { Nom = "Bière" };

            mesProduits.Add(the);
            mesProduits.Add(cafe);
            mesProduits.Add(biere);

            //charge une commande en cours
            for (int i = 0; i < 3; i++)
            {
                maCommande.Add(the);
                maCommande.Add(cafe);
                maCommande.Add(biere);
            }

            consommationBindingSource.DataSource = maCommande.ListeConsommations;


Et la méthode qui retire les consommations (appelée depuis un bouton) pour mon test
        int indexProduit = 0;
        Commande maCommande = new Commande();
        List<Produit> mesProduits = new List<Produit>();

        private void testThomic()
        {
            maCommande.Remove(mesProduits[indexProduit++]);
            if (indexProduit == 3) indexProduit = 0;

        }



Chez moi ça marche avec Add et Remove
1
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
25 janv. 2015 à 18:29
Au fait je n'ai pas eu besoin d'aller dans les propriétés avancées de binding du datagridview, le bindingsource est à l'écoute de PropertyChanged.
0
Bonjour,

Merci de te pencher sur mon problème.

N'est ce pas propre au WPF ce que tu m'affiche là? :D

car si c'est le cas je n'ai malheureusement pas encore sauté le pas :(
0
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
23 janv. 2015 à 11:50
0
Je pense qu'on est sur la bonne voie.

Il affiche bien mes colonnes, mais lorsque j'ajoute des éléments à ma commande, les éléments ne s'affichent pas.

par contre au chargement, avant de faire le binding, lorsqu'il existe un élément dans la commande, celà s'affiche bien dans le datagridview.

Il y a-t-il quelque chose à faire pour que ça fonctionne?
0
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
23 janv. 2015 à 16:28
Il faudrait que tu mettes le code que tu as écrit parler dans le vide ne sert a rien....
0
oui effectivement :)

au chargement de la form


private void Consommation_Load(object sender, EventArgs e)
{
this.KeyPreview = true;
chargementProduits();
afficherSofts();

var monBinding = (from conso in commande.listeConsommations
select new
{
Nom = conso.produitCommande.nom,
Quantite = conso.quantite
}).ToList();

dataGridView1.DataSource = monBinding;

btSoft_Click(sender, e);//on simule le clic sur le bouton bière

}


et là lorsque j'ajoute un élément à la commande

commande.Add(produit);


avec la fonction

public void Add(Produit produit)
{
int index = this.IndexOf(produit);

if (index == -1)
{
consommation = new Consommation(produit, 1);
this.ListeConsommations.Add(consommation);
}
else
{
this.ListeConsommations[index].quantite++;
}
}
0
ça se termine :)

mais encore plusieurs problèmes.

voici ce que j'ai fait.

j'ai implémenté l'interface INotifyPropertyChanged à ma classe Commande.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.ComponentModel;

namespace GestionBar
{
class Commande : INotifyPropertyChanged
{
private BindingList<Consommation> ListeConsommations;
private Consommation consommation;

public Commande()
{
ListeConsommations = new BindingList<Consommation>();
}

public BindingList<Consommation> listeConsommations
{
get { return ListeConsommations; }
}

private int IndexOf(Produit produit)
{
int index = -1;
for (int i = 0; i < ListeConsommations.Count; i++)
{
if (ListeConsommations[i].produitCommande == produit)
{
index = i;
break;
}
}
return index;
}

public void Add(Produit produit)
{
int index = this.IndexOf(produit);

if (index == -1)
{
consommation = new Consommation(produit, 1);
this.ListeConsommations.Add(consommation);
}
else
{
this.ListeConsommations[index].quantite++;
}
this.OnPropertyChanged("listeConsommations");
}

public void Remove(Produit produit)
{
int index = this.IndexOf(produit);

if (index != -1)
{
if (this.ListeConsommations[index].quantite == 1)
{
this.ListeConsommations.Remove(this.ListeConsommations[index]);
}
else
{
this.ListeConsommations[index].quantite--;
}
this.OnPropertyChanged("listeConsommations");
}
}

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}


et voici le binding entre le datagridview et la liste de mon objet commande.


private void Consommation_Load(object sender, EventArgs e)
{
this.KeyPreview = true;
chargementProduits();
afficherSofts();

commandeBindingSource.DataSource = commande.listeConsommations;
dataGridView1.DataSource = commandeBindingSource;
}


J'ai travaillé avec une BindingList au lieu d'une List sinon le binding ne fonctionnait pas.

Ensuite j'ai ajouté la propriété NomProduit, ce fait effectivement que je n'ai plus besoin de Linq.

et j'appelle le OnPropertyChanged à mon Add et Remove de ma classe.

Premier problème : le datagridview se met bien à jour lorsqu'un nouvel élément est ajouté à la liste. mais pas lorsque la quantité change. Pourtant le onPropertyChanged est sur mes méthodes Add et Remove et donc l'évènement devrait être levé dans les deux cas.
Ce qui est bizarre, en mode Debug, le dataGridView est bien mis à jour dans tous les cas.

Deuxième problème : j'aimerais bien trier tout abord mes colonnes. Par défaut il prend l'ordre dans lequel les accesseurs sont définis dans ma classe. Est-ce qu'il y a une autre manière de faire.

Ensuite j'aimerai bien qu'il ne m'affiche pas mon objet. est-ce que jouer sur la visibilité de la colonne est suffisant ou est-ce qu'il y a une autre manière de faire? ( j'ai besoin de l'accesseur Get pour d'autres opérations ).

J'aimerai aussi ajouter une colonne à mon datagridview avec un bouton. est-ce que celà doit être fait dans ma classe ou pas obligatoirement. ( je sais pas si le binding permet de rajouter manuellement d'autres colonnes )

Merci pour ton aide précieuse :)
0
thomasmic Messages postés 8 Date d'inscription samedi 24 janvier 2015 Statut Membre Dernière intervention 3 mars 2020
24 janv. 2015 à 17:09
Voilà j'ai rajouter un propertyChanged sur la propriété quantité de mon objet.
Et maintenant la mise du datagridview se fait correctement lorsqu'il n'y a que la quantité qui a changé ( et donc pas d'ajout d'élément dans la liste ).

Par contre, je me bat pour le faire à l'aide de l'assistant comme tu le fait dans le tuto ( donc sans code manuel ) mais là le datagridview ne se met pas à jour.

voici les étapes que j'ai suivi :

d'abord j'ai sélectionné le datasource sur le datagridview :



là rien ne s'affiche dans le datagridview, car logiquement il doit être lié à la liste de la classe commande.

donc quand je fais pour changer le datasource du datagridview, il me propose une nouvelle source, la liste


Je sélectionne la liste.

là il m'affiche bien mes colonnes correspondantes aux éléments de ma liste ( entêtes de colonne ).



et là... catastrophe, je reviens à l'état initial pour lequel j'avais posté mon problème.

Du coup pas moyen de personnaliser l'affichage via l'assistant. :(
0
thomasmic Messages postés 8 Date d'inscription samedi 24 janvier 2015 Statut Membre Dernière intervention 3 mars 2020
25 janv. 2015 à 19:37
Bonsoir,

En fait j'ai réussi à avoir le même résultats que toi mais sans passer par d'assistant.

Il ne me crée simplement pas automatiquement les colonnes via le binding. J'ai du les définir moi même via l'assistant sur le datagridview.

j'ai instancié un bindingsource

BindingSource commandeBindingSource = new BindingSource();


ensuite au load de la form, j'ai défini le datasource du bindingsource, et ensuite je lie ce bindingsource au datasource de mon datagridview.


commandeBindingSource.DataSource = commande.ListeConsommations;
dataGridView1.DataSource = commandeBindingSource;


et ça fonctionne parfaitement, et je peux gérer mes colonnes comme je le voulais.

Par contre j'ai essayé de trier la liste de consommation.

Comme apparemment la BindingList ne supporte pas le tri, j'ai trouvé cette solution :


List<Consommation> ListeTriee = ListeConsommations.OrderBy(x => x.nomProduit).ToList();
this.ListeConsommations = new BindingList<Consommation>(ListeTriee);


mais là catastrophe ça ne fonctionne plus.
Il m'affiche bien le premier élément dans le datagridview, la quantité s'incrémente bien si je clique sur ce même produit plusieurs fois, mais dès que je clique sur un deuxième produit, celui-ci ne s'affiche pas.
Pourtant en mode debug ma listeConsommation contient bien deux éléments.

N'y a-t-il pas quelque chose proche de la BindingList qui supporterai le tri tout en pouvant être lié?

Merci
0
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
25 janv. 2015 à 19:46
Je pense que c'est à cause du new, ça te crée une nouvelle référence donc le binding est perdu.

essaye
ListeConsommations = ListeConsommations.OrderBy(x => x.nomProduit).ToList();
0
thomasmic Messages postés 8 Date d'inscription samedi 24 janvier 2015 Statut Membre Dernière intervention 3 mars 2020
25 janv. 2015 à 19:54
Il ne veut pas, car le ToList() retourne un objet List et non un objet BindingList, et un cast ne fonctionne pas non plus.
0
Whismeril Messages postés 19025 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 19 avril 2024 656
25 janv. 2015 à 20:27
Ça doit marcher avec une liste normale
0
thomasmic Messages postés 8 Date d'inscription samedi 24 janvier 2015 Statut Membre Dernière intervention 3 mars 2020
25 janv. 2015 à 21:24
malheureusement non.

L'utilisation d'une liste normale fesait que le binding ne se fesait pas, du moins que les modifications ne se répercutaient pas directement sur le datagridview.

J'ai fait un tri manuel lorsqu'un nouvel item est ajouté à la liste.
0
Rejoignez-nous