Interagir entre deux Class

Résolu/Fermé
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 - 20 févr. 2019 à 17:29
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 - 24 févr. 2019 à 10:59
Bonjour à tous
Grâce à un exemple de Class fourni par Whismeril j'ai finalisé une Class C20Locomotive qui est instanciée n fois pour devenir soit des boutons soit des pupitres de conduite


Fort de cet exemple j'ai créé crée une Class Arduino qui ouvre la communication Ethernet avec un microcontrôleur Arduino Mega. Cette classe est également instanciée n fois dans la même fenêtre que C20Locomotive. A l'initialisation une requête Ping vérifie la disponibilité de la station. Si Ping Ok on envois DateNow et on attends la réception de l'heure en retour si Ok la liaison est opérationnelle.


Voici le code de cette Class C30Arduino
using System;
using System.Windows;
using System.ComponentModel;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Data.OleDb;

namespace RobTrainV3
{
    public class C30Arduino : INotifyPropertyChanged
    {

        private System.Net.Sockets.TcpClient clientSocket17x = new System.Net.Sockets.TcpClient();
        private byte[] myReadBuffer = new byte[1024];
        private byte[] inStream = new byte[10000];

        public C30Arduino(int LeNumARD, bool LeValide, string LeNomARD)
        {
            int xnumARD = LeNumARD;
            numARD = LeNumARD.ToString();
            valide = LeValide;
            nomARD = LeNomARD;


                  Ping myPing = new Ping();
                //string AdresseIP = "192.168.1." + numARD.ToString();
            string AdresseIP = "192.168.1." + numARD;
            PingReply reply = myPing.Send(AdresseIP, 100);
                if (reply != null)
                {
                    if (reply.Status == IPStatus.Success)
                    {
                    TxtLine1 = "Ping " + numARD.ToString() + " OK";
                    TryConnectClient(LeNumARD);
                    Send(Convert.ToString(DateTime.Now));
                    int i = 0;
                    while (i < 200000)
                    {
                       int ret = Receive(i);
                        if( ret == 1 ) { break;  }
                        i = i + 1;
                    }
                }
                    else
                    {   TxtLine1 = "Ping " + numARD.ToString() + " Error";  }
                }
        }

        public C30Arduino(string _emettre)
        {
            Send("toto");
        }

        //*********************************************************************************************************************************************/
        private string emettre;
        /// <summary>
        ///Contient le numéro DCC de la locomotive
        /// </summary>
        public string Emettre
        {
            get { return emettre; }

            set { if (emettre != value) { Send("toto"); emettre = value; GenerePropertyChanged("Emettre"); } }
        }




        //*********************************************************************************************************************************************/
        private string numARD;
        /// <summary>
        ///Contient le numéro DCC de la locomotive
        /// </summary>
        public string NumARD
        {
            get { return numARD.ToString(); }
            set {   if (numARD != value) { numARD = value; GenerePropertyChanged("NumARD"); }   }
        }

        private string nomARD;
        /// <summary>
        ///Contient le numéro DCC de la locomotive
        /// </summary>
        public string NomARD
        {
            get { return nomARD; }
            set { if (nomARD != value) { nomARD = value; GenerePropertyChanged("NomARD"); } }
        }

        private string txtLine1;
        /// <summary>
        ///Contient le texte de la ligne 1
        /// </summary>
        public string TxtLine1
        {
            get { return txtLine1; }
            set { if (txtLine1 != value) { txtLine1 = value; GenerePropertyChanged("TxtLine1"); } }
        }

        private string txtLine2;
        /// <summary>
        ///Contient le texte de la ligne 1
        /// </summary>
        public string TxtLine2
        {
            get { return txtLine2; }
            set { if (txtLine2 != value) { txtLine2 = value; GenerePropertyChanged("TxtLine2"); } }
        }

        private string txtLine3;
        /// <summary>
        ///Contient le texte de la ligne 3
        /// </summary>
        public string TxtLine3
        {
            get { return txtLine3; }
            set { if (txtLine3 != value) { txtLine3 = value; GenerePropertyChanged("TxtLine3"); } }
        }
        //==============================================================================================================================================/
        private bool valide;
        /// <summary>
        /// Retourne ou définit si l'Arduino existe
        /// </summary>
        public bool Valide
        {
            get { return valide; }
            set {   if (valide != value) { valide = value; GenerePropertyChanged("Valide"); } }
        }
        /*============================================================================================================================*/
        private void TryConnectClient(int _numARD)
        {
            try
            {
                string AdresseIP = "192.168.1." + Convert.ToString(numARD);
                clientSocket17x.ReceiveBufferSize = 10000; clientSocket17x.Connect(AdresseIP, 8000 + _numARD);
            }
            catch (Exception exception)
            { MessageBox.Show("Rob Try ConnectClient" + exception.Message); }
        }
        /*============================================================================================================================*/
        private void Send(string send)
        {
            NetworkStream serverStream = <souligne>clientSocket17x.GetStream()</souligne>;
            <souligne>TxtLine2 </souligne>= send;
            byte[] outStream = System.Text.Encoding.ASCII.GetBytes(send + "\n");
            serverStream.Write(outStream, 0, outStream.Length);
            serverStream.Flush();
        }
        /*============================================================================================================================*/
        private int Receive(int _i)
        {
            NetworkStream serverStream = clientSocket17x.GetStream();
            if (serverStream.DataAvailable)
            {
                serverStream.Read(inStream, 0, (int)clientSocket17x.ReceiveBufferSize);
                serverStream.Flush();
                string returndata = System.Text.Encoding.ASCII.GetString(inStream);
                TxtLine3 = returndata;
                //MessageBox.Show("Receive :   i=   " + _i);
                //return (GM.ConversionEnString(inStream));
                return 1;
            }
            else { return -1; }
        }


        //==============================================================================================================================================/
        //==============================================================================================================================================/
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void GenerePropertyChanged(string Propriete)
        {
            if (this.PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(Propriete));
            //MessageBox.Show("GenerePropertyChanged :  " + Propriete);
        }

        #endregion
    }
}


La prochaine étape est de faire interagir les pupitres de conduite Locomotive avec le module Arduino 171 DCCppMain qui gère les signaux à fournir sur les rails. En clair chaque pupitre produit un codage, ex: t 1 5 65 , qui doit être transmis à cet Arduino sachant que dans la Class il y a une méthode Send et une autre Receive

Si je les déclare en Public j'ai des erreurs (j'ai mis une image pour pouvoir afficher le soulignement des erreurs ?)


De plus je ne vois pas comment adresser ces méthodes je pense que je m'égare à nouveau

Merci de me prêter une boussole
A+ Roland


Configuration: Windows / Chrome 72.0.3626.109

6 réponses

Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 931
20 févr. 2019 à 17:44
Bonjour

Sans les messages d’erreur, je ne fais que supposer.
Tu ne les a pas seulement déclaré public, mais static aussi.
Ce mot clé n’a pas la même signification qu’en VBA.
Si le message est un truc du style
Un champ d’instance ne peut pas être appelé dans une méthode statique
alors cela vient de là.
1
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
20 févr. 2019 à 20:49
Effectivement voici le message d'erreur


sans static il n'y a pas de message d'erreur

mais dans les deux je n'arrive pas à comprendre comment je peux, sur un changement de valeur d'un TextBlok
dans C20Locomotive, sollicité la méthode Send dans C30Arduino ?

dans le behind j'ai bien un événement lorsque la vitesse évolue

        private void SliVitesse_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            C20Locomotive loco = (C20Locomotive)((Slider)sender).DataContext;
            loco.Vitesse = loco.Vitesse;
        }


Peux-t-on dans C30Arduino surveiller un événement externe ?
D'habitude je suis plus imaginatif et je finis par trouver une solution mais là je sèche.

D'autant plus que c'est un besoin que j'aurai par ailleurs car d'autre tâche auront à dialoguer avec d'autres Arduino par exemple pour commander des aiguillages, des feux de signalisation ,des passages à niveaux .....
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 931
20 févr. 2019 à 23:20
Une chose après l'autre.

Un élément static (propriété, méthode ou évènement) est "hors d'atteinte" d'une élément d'une instance.
Parce ce que ce qui est static n'est pas instancié.
Si tu as besoin de données venant d'une instance, il faut les passer en paramètre.
Un exemple, on imagine des objets à gérer avec un ID unique.

public class MonObjet
{
      private static nombreObjet;

     public MonObjet()
     {
           ID = ++MonObjet.nombreObjet;//On ne peut pas utiliser directement nombreObjet

     }

     public int ID {get; private set}
}



nombreObjet est "en dehors" de la classe, chaque instance peut accéder à sa valeur.
Quand je l'incrémente, j'obtiens à la fois le nombre d'instances crées et un nouvel ID.
nombreObjet existe même s'in n'y aucune instance.

Imaginons maintenant, que les objets soient sérialisé dans un fichier csv.
Quand on lance le programme, il faut donc désérialiser. On va donc à partir du fichier générer une collection de MonObjet.
On peut écrire une classe dédiée.
On peut aussi, utiliser une méthode statique, puisqu'elle existe même s'il n'y a pas d'instance.

public static List<MonObjet> Deserialise(string Fichier)
{
      return (from ligne in File.ReadAllLines(Fichier)
                   from donnee in ligne.Split(';')
                   select new MonObjet
                   {
                            ID = Convert.ToInt(donnee[0]),
                           //etc
                  }).ToList();

}


Et ailleurs
string filename = "mesObjets.csv";//la donnée instanciée filename est passée en paramètre
List<MonObjet> mesObjets = MonObjet. Deserialise(filename);



J'ai tapé de tête, il peut y avoir de petites erreurs.





1
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 931
20 févr. 2019 à 23:54
Pour l'interaction, tu pourrais avoir une propriété de Locomotive de type C30Arduino et quand la locomotive doit envoyer quelque chose elle se sert de sont composant.

Cependant, un objet modélise un vrai objet, concept, etc... Il y a plein de chose qui pourraient justifier des objets dans les composants d'une locomotive (des roues, un moteur, etc....) mais la communication Tcp bof....

Pour moi, le mieux est de créer un évènement dans la classe Locomotive.
Cet évènement doit avoir en paramètre tout ce qui est nécessaire à la communication:
  • l'adresse IP, et le message
  • le numéro/nom de la locomotive et le message
  • l'instance de la locomotive


Au moment où tu crées une loco, tu abonnes cet évènement à une méthode (toujours la même).
Dans cette méthode, tu identifies grâce au paramètre qui va bien quelle instance de C30Arduino utiliser, tu lui donnes le message et tu l'envoie.

Tu peux rendre cet évènement static, comme ça tu t'y abonnes dès l'initialisation de ton programme, que tu aies des locomotives ou pas. Et quand il y en aura il sera prêt.

Je t'invite à lire ce tuto https://tlevesque.developpez.com/tutoriels/csharp/delegues-et-evenements/ sur les délégués et les évènements.
1
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
21 févr. 2019 à 00:19
Merci de ta réponse
Je vais la décortiquer

Mais je te soumets ceci qui fonctionne, pour le moment, très bien qu'en penses-tu ?
        private void SliVitesse_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
C20Locomotive loco = (C20Locomotive)((Slider)sender).DataContext;
loco.Vitesse = loco.Vitesse;

C30Arduino ard = arduinos.Find(x => x.NumARD == "172");
ard.Send(loco.Vitesse.ToString());
int ret = ard.Receive(i);
}


Les deux premières lignes ne servent qu'à mettre la valeur du Slider dans le Bargraph sur le pupitre

la troisième ligne me permet de récupérer l'Arduino qui m'intéresse dans ard
dés lors je peux atteindre la méthode Send et lui passer la valeur de vitesse
et puis lancer la réception pour contrôle

les premiers test sont encourageants
0
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
21 févr. 2019 à 00:20
petite précision le code est dans le behind
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 931
21 févr. 2019 à 08:13
Les deux premières lignes ne servent qu'à mettre la valeur du Slider dans le Bargraph sur le pupitre

Tu n’en a pas besoin, il te suffit de binder le slider et le baregraphe sur la propriété Vitesse.

Pour la suite, c’est une option, mais que se passe t il si le message est incomplet? Si j’ai bien compris, tu sélectionnes ta loco, tu règles la vitesse et tu appuies sur avant ou arrière. Donc à ce moment là tu envoies un messages partiel.
0
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
21 févr. 2019 à 08:33
Dans la pratique c'est l'inverse on choisit un sens de marche et ensuite on agit sur la vitesse. Chaque variation du Slider est transmise. D'ailleurs le Slider est inhibé si aucun sens de marche n'est choisi.

Merci de ton aide je vais attaquer le tuto sur les délégués et événements cela s'impose
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 931
22 févr. 2019 à 18:00
Bonsoir

Je ne comprends pas pourquoi la window est une propriété static de ta class GD.
Je ne voie pas non plus où cette windows obtient l'instance de binding.
1
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
22 févr. 2019 à 18:55
Je ne comprends pas pourquoi la window est une propriété static de ta class GD.

Parce que j'ai ce message d'erreur


mais effectivement si je déclare la window dans MainWindow je n'ai pas d'errreur mais cela ne règle pas mon soucis

Je ne voie pas non plus où cette windows obtient l'instance de binding

pour être honnête je ne suis même pas sûr de bien comprendre ta question
En tout cas dans XAML il manquait (par rapport à MainWindow) 2 Binding dans Title et Grid
J'ai corrigé comme suit mais sans progrès

<Window x:Class="RobTrainV0.W10GareSecrete1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RobTrainV0"
        mc:Ignorable="d"
        Title="W10GareSecrete1" DataContext="{Binding}" Height="800" Width="1470" Background="MintCream"  Topmost="True">
    <Grid DataContext="{Binding .}" >
        <Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="10,10,0,0"/>        

        <!--la listbox est bindée sur la propriété 'AigsChoisies'-->
        <ListBox x:Name="listLocomotive" DataContext="{Binding .}" ItemsSource="{Binding AigsChoisies}"  VerticalAlignment="Top" HorizontalAlignment="Left" Height="800" Width="434" Margin="0,188,0,0" BorderThickness="0,0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!--son template est composé d'un stackpanel, qui empile les controles, verticalement par défaut (on peut demander horizontalement)-->
                    <StackPanel VerticalAlignment="Top" HorizontalAlignment="Left"  Height="36" Width="78" Margin="0,0,0,0" >
                        <!--le nom est affiché en rouge, l'image en fond et un slider est bindé sur la propriété 'Pourcentage'-->
                        <StackPanel.Background>
                            <ImageBrush ImageSource="E:/5-Train/300-appWPF/RobTrainV0/RobTrainV0/Resources/Ai000DrDeviee.png"/>
                        </StackPanel.Background>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        
    </Grid>
</Window>


voici le code behind de la fenêtre il y a des lignes inutiles car normalement il n'y aura pas deux collections d'aiguillages, c'était juste pour les testes
namespace RobTrainV0
{
    /// <summary>
    /// Logique d'interaction pour W10GareSecrete1.xaml
    /// </summary>
    public partial class W10GareSecrete1 : Window
    {
        List<C41Aiguillage> aigs = new List<C41Aiguillage> { };

        //Ce type de collection signale au binding quand un item est ajouté ou supprimé
        public ObservableCollection<C41Aiguillage> AigDisponibles { get; set; }
        public ObservableCollection<C41Aiguillage> AigChoisies { get; set; }

        public W10GareSecrete1()
        {
            InitializeComponent();

            /*********************************************************************************************************************************************/
            // Création des objets Aiguillages 
            int nbAig =10;
            int iAig = 0;
            while (iAig < nbAig)
            {
                aigs.Add(new C41Aiguillage(123 + iAig, "toto", true)); iAig = iAig + 1;
            }
            AigChoisies = new ObservableCollection<C41Aiguillage>(aigs.Where(l => l.NumAIG>126));//on crée la collection de locos choisies
            AigDisponibles = new ObservableCollection<C41Aiguillage>(aigs.Where(l => !(l.NumAIG>126)));//on crée la collection de locos disponibles
        }
    }
}


En tout cas merci de tes conseils. J'espère ne pas abuser de ta générosité, je cherche également beaucoup sur internet mais comme tu me l'avais écrit il y a beaucoup à découvrir...
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 931
22 févr. 2019 à 23:20
mais effectivement si je déclare la window dans MainWindow je n'ai pas d'errreur mais cela ne règle pas mon soucis
ben oui, une windows n'a pas vocation a être une variable statique.

pour être honnête je ne suis même pas sûr de bien comprendre ta question

tu y a répondu en montrant le code du constructeur de ta windows.

Il manque
this.DataContext = this;

Mais comme je l'avais mis en commentaire précédemment c'est pas le top.

Là, tu génères tes collections dans le code behind de la fenêtre.
Mais le rôle d'une fenêtre est d'afficher ou de permettre la saisie manuelle des données.
Ça n'est pas à elle de gérer la base de données.

Il faudrait écrire une classe qui lit Access et crée les collections.
Tu passes ensuite l'instance de cette classe en paramètres aux autres fenêtres et c'est elle qui devient le datacontext de chaque fenêtre.
0
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
23 févr. 2019 à 00:14
Merci beaucoup, mais il faut que je me rende à l'évidence il me manque beaucoup trop de bases pour avancer dans mon projet.
Je veux malgré persister dans l'apprentissage de WPF et je vais donc entreprendre une démarche plus progressive d'acquisition de ce langage avec des tutos et des exemples.
Il faut qu'en j'en assimile davantage les concepts sachant qu'en plus mon besoin n'est pas réellement un traitement séquentiel de données c'est plus une application d'automatisme avec de nombreuses interactions entre les différents organes.
Dans l'absolu je ne suis pas sûr que ce langage soit le mieux adapté j'ai un passé d'automaticien et nous traitions les applications industrielles avec des produits et des logiciels dédiés.

Je vais donc libérer un peu de ta bande passante mais nul doute que je chercherai encore ton soutien A+ Merci
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 931
23 févr. 2019 à 09:36
Il faut qu'en j'en assimile davantage les concepts sachant qu'en plus mon besoin n'est pas réellement un traitement séquentiel de données c'est plus une application d'automatisme avec de nombreuses interactions entre les différents organes.

L’objet est parfaitement adapté, il faut « juste » bien les concevoir.

Je vais te faire un exemple le plus simple possible avec un carnet de contacts.
0
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
23 févr. 2019 à 11:38
Je te remercie de ta patience, mais comme exemple pourrais-tu faire très simplement
un MainWindow avec un TextBox1 bindé sur une Casse C1 et qui lorsqu'il évolue met à jour un autre TextBox2 bindé sur une Classe C2
dans le MainWindow un Bouton qui ouvre une Window avec un TextbBox ou Listbox bindé sur une Classe C3 et que lorsque celui-ci évolue le TextBox2 de C2 soit actualisé

mais bien sûr ce n'est qu'un souhait mais qui illustre bien mon besoin et devrait me permettre d'avancer
0

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

Posez votre question
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 931
23 févr. 2019 à 12:10
J'ai un carnet de contact stocké dans un fichier xml, parce que
  • c'est facile à fournir dans un forum
  • contrairement à un csv c'est souple au niveau des champs

<?xml version="1.0" encoding="utf-8"?>
<CarnetDeContacts>
  <Contact Nom="Di" Prenom="Alain" Naissance="2000-01-02T00:00:00">
    <Notes>Ce contact est bidon</Notes>
    <Telephones>
      <Telephone Numero="01 23 45 67 89" Type="Domicile" />
      <Telephone Numero="06 23 45 67 89" Type="Portable Perso" />
      <Telephone Numero="07 23 45 67 89" Type="Portable Pro" />
    </Telephones>
    <Ardesses>
      <Adresse Adresse="1 allée du week-end 11 111 Semaine Les Bains" Type="Domicile" />
      <Adresse Adresse="7 avenue du mardi 11 111 Semaine Les Bains" Type="Bureau" />
    </Ardesses>
  </Contact>
  <Contact Nom="Zètofrais" Prenom="Mélanie" Naissance="2010-03-04T00:00:00">
    <Notes>Ce contact est goutu</Notes>
    <Telephones>
      <Telephone Numero="07 89 67 45 23" Type="Portable Pro" />
    </Telephones>
    <Ardesses>
      <Adresse Adresse="51 rue du pastaga, 51 515 Cigales Plage" Type="Bureau" />
    </Ardesses>
  </Contact>
</CarnetDeContacts>

Question souplesse, mon premier contact a 3 numéros et 2 adresses, alors que le second n'en a qu'1 de chaque.

J'ai donc une classe qui va contenir ces informations
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace CarnetContacts
{
    public class Contact : INotifyPropertyChanged
    {
        private string nom;
        /// <summary>
        /// Nom du contact
        /// </summary>
        public string Nom
        {
            get { return nom; }
            set
            {
                if (nom != value)
                {
                    nom = value;
                    GenerePropertyChanged("Nom");
                    GenerePropertyChanged("Identite");
                }
            }
        }

        private string prenom;
        /// <summary>
        /// Prénom du contact
        /// </summary>
        public string Prenom
        {
            get { return prenom; }
            set
            {
                if (prenom != value)
                {
                    prenom = value;
                    GenerePropertyChanged("Prenom");
                    GenerePropertyChanged("Identite");
                }
            }
        }

        private DateTime naissance;
        /// <summary>
        /// Date de naissance du contact
        /// </summary>
        public DateTime Naissance
        {
            get { return naissance; }
            set
            {
                if (naissance != value)
                {
                    naissance = value;
                    GenerePropertyChanged("Naissance");
                }
            }
        }

        /// <summary>
        /// Collection de numéros de téléphone
        /// </summary>
        /// <remarks>le type KeyValuePair est le type de base d'un dictionnaire, il associe une clé à une valeur, là je m'en sert pour dire si le numéro est le domicile, le bureau etc...</remarks>
        public ObservableCollection<KeyValuePair<string, string>> Telephones { get; set; }

        /// <summary>
        /// Collection d'adresses
        /// </summary>
        public ObservableCollection<KeyValuePair<string, string>> Adresses { get; set; }


        private string notes;
        /// <summary>
        /// Informations diverses
        /// </summary>
        public string Notes
        {
            get { return notes; }
            set
            {
                if (notes != value)
                {
                    notes = value;
                    GenerePropertyChanged("Notes");
                }
            }
        }

        public string Identite
        {
            get { return Prenom + " " + Nom; }
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

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

        #endregion 
    }
}


J'ai une classe qui sait lire et écrire le fichier.
Elle aurait pu être static, en effet, y'a pas besoin de plusieurs instances, mais je ne veux pas que tout le monde trifouille le fichier.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;

namespace CarnetContacts
{
    public class FichierXML
    {
        private string chemin;

        public FichierXML(string Chemin)
        {
            chemin = Chemin;
        }

        public IEnumerable<Contact> LireFichier()
        {
            XDocument xdoc = XDocument.Load(chemin);

            return (from c in xdoc.Descendants("Contact")
                    select new Contact
                    {
                        Nom = c.Attribute("Nom").Value,
                        Prenom = c.Attribute("Prenom").Value,
                        Naissance = Convert.ToDateTime(c.Attribute("Naissance").Value),
                        Notes = c.Element("Notes").Value,
                        Telephones = new ObservableCollection<KeyValuePair<string, string>>(
                            from t in c.Descendants("Telephone")
                            select new KeyValuePair<string,string>(t.Attribute("Numero").Value, t.Attribute("Type").Value)),
                        Adresses = new ObservableCollection<KeyValuePair<string, string>>(
                            from a in c.Descendants("Adresse")
                            select new KeyValuePair<string, string>(a.Attribute("Adresse").Value, a.Attribute("Type").Value)),

                    }

                    );
        }

        public void EcrireFichier(IEnumerable<Contact> Contacts)
        {
            XDocument xdoc = new XDocument
                (
                    new XElement("CarnetDeContacts",
                    from c in Contacts
                    select new XElement("Contact", new XAttribute("Nom", c.Nom), new XAttribute("Prenom", c.Prenom), new XAttribute("Naissance", c.Naissance),
                                new XElement("Notes", c.Notes),
                                new XElement("Telephones",
                                    from t in c.Telephones
                                    select new XElement("Telephone", new XAttribute("Numero", t.Key), new XAttribute("Type", t.Value))),
                                new XElement("Ardesses",
                                    from a in c.Adresses
                                    select new XElement("Adresse", new XAttribute("Adresse", a.Key), new XAttribute("Type", a.Value)))
                                    ))

                );

            xdoc.Save(chemin);

        }
    }
}

J'ai utilisé LinqToXml, comme tu fais de l'Access ne te prends pas la tête sur ce code, il marche.
Cependant, je n'ai pas géré le cas (tout à fait possible en xml) ou un élément ou un attribut serait absent.

Dans MainWindow je veux afficher une liste non modifiable avec les infos principales.
Si je double clique, ça doit afficher une autre windows avec tous les éléments éditables (c'est pas tout à fait vrai, en effet KeyValuePair n'est pas adapté, mais je voulais faire un exemple simple et rapide, donc adresses et numéros ne sont pas éditables).
Enfin quand cette fenêtre se ferme ça enregistre les données dans le xml (ne se pose pas la question s'il y a eu modification ou pas et ne permet pas l'annulation -> on fait simple).

Je vais créer un "chef d'orchestre", c'est lui qui va dire à la classe FichierXml, où est le fichier, quand lire ou quand sauvegarder. C'est aussi lui qui contient les données dans la collection qui lui convient.

La deuxième fenêtre n'a pas à connaitre tous mes contacts, juste celui à afficher.

using System.Collections.ObjectModel;

namespace CarnetContacts
{
    public class Carnet
    {
        FichierXML xml;
        public Carnet()
        {
            xml = new FichierXML("mesContacts.xml");
            Contacts = new ObservableCollection<Contact>(xml.LireFichier());
        }

        public ObservableCollection<Contact> Contacts { get; set; }

        public void Enregistrer()
        {
            xml.EcrireFichier(Contacts);
        }
    }
}


et mes 2 fenêtres
<Window x:Class="CarnetContacts.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CarnetContacts"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="900" DataContext="{Binding}">
    <Grid DataContext="{Binding .}">
        <ListBox DataContext="{Binding .}" ItemsSource="{Binding Contacts}" MouseDoubleClick="ListBox_MouseDoubleClick">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Identite, UpdateSourceTrigger=PropertyChanged}" Width="150"/>
                        <TextBlock Text="{Binding Naissance, StringFormat='Anniversaire {0:dd/MM}'}" Width="150"/>
                        <ListBox DataContext="{Binding .}" ItemsSource="{Binding Telephones}">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="{Binding Value}" Width="100"/>
                                        <TextBlock Text="{Binding Key}" Width="100"/>
                                    </StackPanel>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                        <ListBox DataContext="{Binding .}" ItemsSource="{Binding Adresses}">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="{Binding Value}" Width="100"/>
                                        <TextBlock Text="{Binding Key}" Width="230"/>
                                    </StackPanel>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace CarnetContacts
{
    /// <summary>
    /// Logique d'interaction pour MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Carnet carnet;
        public MainWindow()
        {
            InitializeComponent();

            carnet = new Carnet();

            this.DataContext = carnet;
        }

        private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            Contact contact = (Contact)((ListBox)sender).SelectedItem;

            wdwContact wdw = new wdwContact(contact);

            wdw.Closed += Wdw_Closed;

            wdw.Show();
        }

        private void Wdw_Closed(object sender, EventArgs e)
        {
            carnet.Enregistrer();
        }
    }
}



<Window x:Class="CarnetContacts.wdwContact"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CarnetContacts"
        mc:Ignorable="d"
        Title="wdwContact" Height="300" Width="600" DataContext="{Binding}">
    <Grid DataContext="{Binding .}">
        <TextBox Text="{Binding Prenom, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="37,34,0,0" VerticalAlignment="Top" Width="120"/>
        <TextBox Text="{Binding Nom, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="162,34,0,0" VerticalAlignment="Top" Width="120"/>
        <TextBox Text="{Binding Naissance}" HorizontalAlignment="Left" Height="23" Margin="162,74,0,0" VerticalAlignment="Top" Width="120"/>
        <TextBox Text="{Binding Notes}" HorizontalAlignment="Left" Height="84" Margin="37,142,0,0" VerticalAlignment="Top" Width="223"/>
        <Label Content="Prénom" HorizontalAlignment="Left" Height="29" Margin="56,0,0,0" VerticalAlignment="Top" Width="72"/>
        <Label Content="Nom" HorizontalAlignment="Left" Height="29" Margin="188,0,0,0" VerticalAlignment="Top" Width="72"/>
        <Label Content="Date de naissance" HorizontalAlignment="Left" Height="23" Margin="37,74,0,0" VerticalAlignment="Top" Width="120"/>
        <Label Content="Notes" HorizontalAlignment="Left" Height="26" Margin="85,111,0,0" VerticalAlignment="Top" Width="110"/>
        <ListBox HorizontalAlignment="Left" Height="100" Margin="322,34,0,0" VerticalAlignment="Top" Width="260" DataContext="{Binding .}" ItemsSource="{Binding Telephones}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Value}"/>
                        <TextBlock Text="{Binding Key}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        
        <ListBox HorizontalAlignment="Left" Height="100" Margin="322,144,0,0" VerticalAlignment="Top" Width="260" DataContext="{Binding .}" ItemsSource="{Binding Adresses}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Value}"/>
                            <TextBlock Text="{Binding Key}"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

    </Grid>
</Window>


using System.Windows;

namespace CarnetContacts
{
    /// <summary>
    /// Logique d'interaction pour wdwContact.xaml
    /// </summary>
    public partial class wdwContact : Window
    {
        public wdwContact(Contact Contact)
        {
            InitializeComponent();

            this.DataContext = Contact;
        }
    }
}


En faisant ainsi,
  • les fenêtres ne savent pas d'où viennent les données ni comment on les enregistre.
  • la classe contact vie sa vie, peut importe que les données viennent d'Access ou d'un xml, et peut importe qui est affiché où.


Le jour où tu veux en faire un site ASP.Net, tu conserves tout le code "métier", tu n'as qu'à adapter l'interface.
Le jour où tu veux passer tes données sur Access. Tu crées une classe pour lire et écrire Access, tu modifies un peu la classe Carnet, mais pour l'interface Carnet s'utilise pareil, et pour Contact c'est complètement transparent.

PS: j'ai mis une option dans le binding d'Identite (mainWindow) et Nom et Prenom (wdwContact) dés qu'on tape un truc dans Nom ou Premon l'affichage d'identité et mis à jour. Pour Naissance, il faut "terminer" la saisie, c'est à dire quitter le TextBox.

PS2: J'ai choisi de faire gérer à l'interface de mainWindow la récupération du contact à transférer à la 2eme fenêtre
Contact contact = (Contact)((ListBox)sender).SelectedItem;
. Mais j'aurais pu avoir une propriété ContactSelectionne dans Carnet, bindé sur la listBox et c'est cette propriété qui aurait été donnée en paramètre à la seconde fenêtre. Je ne voulais pas t'en mettre trop d'un coup dans le xml.

PS3: Si j'avais mis la seconde fenêtre comme propriété (statique ou non) de Contact:
  • option 1, j'en fais autant que j'ai de contact -> pas modulaire non
  • option 2, je fais en sorte de lui passer le contact autrement que par le constructeur, modulaire ok. Mais quand je la ferme elle n'est plus instanciée, il faut donc la réinstancier à chaque fois (pas de gain par rapport à l'instancier dans mainWindows) et le jour où tu passes en ASP.Net, tu disposes d'une instance de windows qui n'existent pas en ASP. Bref, une window c'est de l'interface, ça ne va pas dans une classe métier.

1
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
24 févr. 2019 à 10:32
Merci Whismeril parfait cet exemple
il illustre bien les relations entre les diverses entités

et j'ai ma réponse avec
 UpdateSourceTrigger=PropertyChanged}" 


je commence à entrevoir une solution pour mes Window réseau j'ai transformé un StackPanel par un Canvas
dans ton exemple juste pour tester
                        </ListBox>
                        <ListBox DataContext="{Binding .}" ItemsSource="{Binding Adresses}" Canvas.Left="500">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <Canvas Width="300" Height="50" Background="YellowGreen">
                                        <TextBlock Text="{Binding Value}" Width="100" Canvas.Top="5" Canvas.Left="10"/>
                                        <TextBlock Text="{Binding Key}" Width="230" Canvas.Top="20" Canvas.Left="30"/>
                                    </Canvas>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>




Là j'ai de quoi œuvrer quelques jours et te laisser souffler un peu, enfin j'espère ...
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 931
Modifié le 24 févr. 2019 à 10:42
Bonjour

je commence à entrevoir une solution pour mes Window réseau j'ai transformé un StackPanel par un Canvas
dans ton exemple juste pour tester

comme je te l'ai dit, c'était un exemple simple et rapide, donc je n'ai pas peaufiné la présentation, le stackPanel a l'avantage de mettre ses enfants à la suite sans s'embêter c'était donc un gain de temps pour moi

et j'ai ma réponse avec
 UpdateSourceTrigger=PropertyChanged}" 

Je n'avais pas réactualisé la page quand j'ai posté l'exemple, je viens juste de découvrir ta question...
0
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
24 févr. 2019 à 10:59
Question devenue inutile car ton exemple est déjà très productif
0
robunccm Messages postés 52 Date d'inscription jeudi 7 février 2019 Statut Membre Dernière intervention 9 mars 2024 1
22 févr. 2019 à 14:35
Bonjour Whismeril

J'attaque un nouvel épisode de La Vie du Rail et commence à travailler sur la représentation du réseau ferré. Idéalement j'aimerasi implémenter plusieurs fenêtres chacune contenant une partie du réseau comme par exemple une gare.
J'imagine
créer une Class Aiguillage contenant Droit et contenant Gauche

Dans un premier temps j'ai transposé l'exemple des Locomotives aux Aiguillages
mais sans la partie boutons de choix car par définition tous les aiguillages créés sont Choisis
Ne prête pas attention à la Class aiguillage elle est volontairement rudimentaire pour les testes.
En pratique un aiguillage est assez complexe mais ce sera pour plus tard.

Mon soucis c'est qu'ils ne s'affichent pas dans ma nouvelle fenêtre alors qu'avec un programme identique ils s'affichent dans MainWindow



Voici le code XAML de la fenêtre W10GareSecrete1 que j'affiche avec dans le behind de MainWindow
            GD.W10GS1.Show();


et sa déclaration dans une Class
namespace RobTrainV0
{
    class C90GlobalData
    {
    }
    class GD
    {
        public static String NamePC = " NamePC?";
        public static String DbAccess = "DbAccess ?";
        public static String DataPictures = "DataPictures ?";

        public static W10GareSecrete1 W10GS1 = new W10GareSecrete1();
    }


on remarquera que le bouton "Button" hors listbox lui est bien créer

<Window x:Class="RobTrainV0.W10GareSecrete1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RobTrainV0"
        mc:Ignorable="d"
        Title="W10GareSecrete1" Height="800" Width="1470" Background="MintCream" Topmost="True">
    <Grid>
        <Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="10,10,0,0"/>        

        <!--la listbox est bindée sur la propriété 'AigsChoisies'-->
        <ListBox x:Name="listLocomotive" DataContext="{Binding .}" ItemsSource="{Binding AigsChoisies}"  VerticalAlignment="Top" HorizontalAlignment="Left" Height="800" Width="434" Margin="0,188,0,0" BorderThickness="0,0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!--son template est composé d'un stackpanel, qui empile les controles, verticalement par défaut (on peut demander horizontalement)-->
                    <StackPanel VerticalAlignment="Top" HorizontalAlignment="Left"  Height="36" Width="78" Margin="0,0,0,0" >
                        <!--le nom est affiché en rouge, l'image en fond et un slider est bindé sur la propriété 'Pourcentage'-->
                        <StackPanel.Background>
                            <ImageBrush ImageSource="E:/5-Train/300-appWPF/RobTrainV0/RobTrainV0/Resources/Ai000DrDeviee.png"/>
                        </StackPanel.Background>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        
    </Grid>
</Window>


j'essaie cette solution pour pouvoir tester Canvas dont j'ai cru comprendre qu'il permettait de positionner chaque Control

Mais peut être que je me fourvoie dans mes choix et qu'il existe une meilleure solution
En réalité je voudrait que chaque fenêtre se construise dynamiquement à partir d'une table Access qui décrirait tous les éléments du réseau de cette fenêtre, cela me permettrait de concevoir une autre application qui serais comme un éditeur de vue graphique disposant d'une bibliothèque de symboles (Aiguillages, ...) et que l'on positionnerais à son gré pour créer la vue.
Charge à cet éditeur de remplir la table Access qui serait ensuite utilisé par l'application de pilotage du réseau.

Quand je saurai faire tout cela je pense que je maîtriserai au moins 1% de WPF
0