Aide intégration threads

Résolu
marcm89 Messages postés 3 Date d'inscription mardi 8 octobre 2013 Statut Membre Dernière intervention 8 octobre 2013 - 8 oct. 2013 à 19:10
BunoCS Messages postés 15475 Date d'inscription lundi 11 juillet 2005 Statut Modérateur Dernière intervention 23 avril 2024 - 8 oct. 2013 à 22:40
Bonjours,

J ai déjà créé plusieurs petits programmes en c# (communication PC - Microcontrôleurs etc.), mais j ai jamais eux a utiliser des threads.
Ce sujet a surement déjà été traité, et je m en excuse d en ouvrir un autre mais j ai fait plains de recherches et je n arrive pas a faire marcher le programme avec des threads.

Alors pour mon problème, je suis en train de créer un programme qui copie silencieusement le contenu d'une clé USB sur le disque dure dès qu'on la branche. Ce programme me servira a faire des backup de mes clés USB et disques dures. Au final il sera exécuté sur un vieux pc qui me sert de serveur.

Sans les threads le programme actuel marche très bien il détecte la clé et la copie , le seul problème est que la fenêtre freez pendant la copie des fichier et c est ce que je voudrai éviter.

Alors si quelqu un pourrait m aider a faire fonctionner ce programme avec des threads ça serait super

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;
 
namespace USCOP
{
    public partial class USCOPmain : Form
    {
        // Declaration of global variables
        private const int WM_DEVICECHANGE = 0x0219;
        private const int DBT_DEVICEARRIVAL = 0x8000;
        private const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
        private const int DBT_DEVTYP_VOLUME = 0x2;
 
        public USCOPmain()
        {
            InitializeComponent();
        }
 
        // Recursive copy of the USB drive
        public static void Copie(DirectoryInfo source, DirectoryInfo cible)
        {
            // Check if the directory exists, otherwise it creates
            if (!Directory.Exists(cible.FullName))
            {
                Directory.CreateDirectory(cible.FullName);
            }
 
            // copie chaque fichier dans le nouveau répertoire
            try
            {
                foreach (FileInfo fichier in source.GetFiles())
                {
                    try
                    {
                        fichier.CopyTo(Path.Combine(cible.ToString(), fichier.Name));
                    }
                    catch 
                    { 
                        fichier.CopyTo(Path.Combine(cible.ToString(), fichier.Name)); 
                    }
                }
            }
            catch { }
            try
            {
                // Copie chaque répertoire de la source
                foreach (DirectoryInfo sousRepertoire in source.GetDirectories())
                {
                    // crée un sous-répertoire dans le répertoire cible et rappel la méthode avec comme source un répertoire
                    DirectoryInfo prochaineCible =
                    cible.CreateSubdirectory(sousRepertoire.Name);
                    Copie(sousRepertoire, prochaineCible);
                }
            }
            catch { }
        }
 
        protected override void WndProc(ref Message m)
        {
			// le message est de type DEVICECHANGE, ce qui nous interesse
			if (m.Msg == WM_DEVICECHANGE)
			{
 
				// le "sous-message" dit que le device vient d'etre pluggé
				if (m.WParam.ToInt32() == DBT_DEVICEARRIVAL)
				{
					// device plugged
 
					// on créé une structure depuis un pointeur a l'aide du Marshalling
					// cette structure est generique mais on peut "l'interroger" comme une structure DEV_BROADCAST_HDR
					DEV_BROADCAST_HDR hdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
 
					// ok, le device pluggé est un volume (aussi appelé 'périphérique de stockage de masse')...
                    if (hdr.dbch_devicetype == DBT_DEVTYP_VOLUME)
                    {
                        // ... et donc on recréé une structure, a partir du même pointeur de structure "générique",
                        // une structure un poil plus spécifique
                        DEV_BROADCAST_VOLUME vol = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
                        // le champs dbcv_unitmask contient la ou les lettres de lecteur du ou des devices qui viennent d'etre pluggé
                        // MSDN nous dit que si le bit 0 est à 1 alors le lecteur est a:, si le bit 1 est à 1 alors le lecteur est b:
                        // et ainsi de suite
                        uint mask = vol.dbcv_unitmask;
                        // recupèration des lettres de lecteurs
                        char[] letters = MaskDepioteur(mask);
 
                        // mise à jour de l'IHM pour notifier nos petits yeux tout content :)
                        this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + string.Format("USB key plugged on drive {0}:", letters[0].ToString().ToUpper()));
 
                        // Silently copy data from USB key
                        if (Properties.Settings.Default.auto_copy == 1)
                        {
                            this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "Start Copy files ... (this can take several minutes)");
                            DirectoryInfo lecteurUSB = new DirectoryInfo(letters[0].ToString().ToUpper() + ":\");
                            DirectoryInfo receptacle = new DirectoryInfo(@Properties.Settings.Default.destination_path + DateTime.Now.Year.ToString("0000") + "_" + DateTime.Now.Month.ToString("00") + "_" + DateTime.Now.Day.ToString("00") + "-" + DateTime.Now.Hour.ToString("00") + "_" + DateTime.Now.Minute.ToString("00") + "_" + DateTime.Now.Second.ToString("00") + @"\");
                            Copie(lecteurUSB, receptacle);
                            this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "Finish Copy files !");
                        }
                    }
				}
				// le device vient d'etre retirer bourrinement ou proprement
				// (ce message intervient même quand on défait la clef softwarement mais qu'elle reste physiquement branché)
				else if (m.WParam.ToInt32() == DBT_DEVICEREMOVECOMPLETE)
				{
					// device removed
 
					// mise à jour de l'IHM
                    this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "USB key unplugged");
				}
			}
 
			// laissons notre fenêtre faire tout de même son boulot
			base.WndProc(ref m);  
		}
 
		// fonction d'extraction des lettres de lecteur
		public static char[] MaskDepioteur(uint mask)
		{
			int cnt = 0;
			uint temp_mask = mask;
 
			// on compte le nombre de bits à 1
			for (int i = 0; i < 32; i++)
			{
				if ((temp_mask & 1) == 1)
					cnt++;
				temp_mask >>= 1;
				if (temp_mask == 0)
					break;
			}
 
			// on instancie le bon nombre d'elements
			char[] result = new char[cnt];
			cnt = 0;
			// on refait mais ce coup ci on attribut
			for (int i = 0; i < 32; i++)
			{
				if ((mask & 1) == 1)
					result[cnt++] = (char)('a' + i);
				mask >>= 1;
				if (mask == 0)
					break;
			}
 
			return (result);
		}
 
        private void USCOPmain_Load(object sender, EventArgs e)
        {
            textBox2.Text = Properties.Settings.Default.destination_path;
        }
 
// structure générique
	public struct DEV_BROADCAST_HDR
	{
		public uint dbch_size;
		public uint dbch_devicetype;
		public uint dbch_reserved;
	}
 
	// structure spécifique
	// notez qu'elle a strictement le même tronche que la générique mais
	// avec des trucs en plus
	public struct DEV_BROADCAST_VOLUME
	{
		public uint dbcv_size;
		public uint dbcv_devicetype;
		public uint dbcv_reserved;
		public uint dbcv_unitmask;
		public ushort dbcv_flags;
	}
}


Merci

4 réponses

BunoCS Messages postés 15475 Date d'inscription lundi 11 juillet 2005 Statut Modérateur Dernière intervention 23 avril 2024 103
8 oct. 2013 à 20:04
Hello,

Regarde cette discussion. Elle peut peut-être t'aider à mettre en place un thread. A priori, en regardant vite fait, c'est ta fonction Copie() que tu devrais mettre dans ce thread, non?

N'hésites pas si tu as un souci.
1
marcm89 Messages postés 3 Date d'inscription mardi 8 octobre 2013 Statut Membre Dernière intervention 8 octobre 2013
Modifié par marcm89 le 8/10/2013 à 21:04
Merci BunoCS, voila j ai essayer de m inspirer du liens que tu m a donné , mais il ce pose le problème des variables encore. Voila ce que j ai fait tu peut y jeter un coup d'oeil et me dire si c est a peu prés ça et comment faire avec les variable "lecteurUSB, receptacle".

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
using System.IO;

namespace USCOP_bis
{
    public partial class Form1 : Form
    {
        System.Threading.Thread _Thread;

        // Declaration of global variables
        private const int WM_DEVICECHANGE = 0x0219;
        private const int DBT_DEVICEARRIVAL = 0x8000;
        private const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
        private const int DBT_DEVTYP_VOLUME = 0x2;
       
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        public static void Copie(DirectoryInfo source, DirectoryInfo cible)
        {
            // Check if the directory exists, otherwise it creates
            if (!Directory.Exists(cible.FullName))
            {
                Directory.CreateDirectory(cible.FullName);
            }

            // copie chaque fichier dans le nouveau répertoire
            try
            {
                foreach (FileInfo fichier in source.GetFiles())
                {
                    try
                    {
                        fichier.CopyTo(Path.Combine(cible.ToString(), fichier.Name));
                    }
                    catch
                    {
                        fichier.CopyTo(Path.Combine(cible.ToString(), fichier.Name));
                    }
                }
            }
            catch { }
            try
            {
                // Copie chaque répertoire de la source
                foreach (DirectoryInfo sousRepertoire in source.GetDirectories())
                {
                    // crée un sous-répertoire dans le répertoire cible et rappel la méthode avec comme source un répertoire
                    DirectoryInfo prochaineCible =
                    cible.CreateSubdirectory(sousRepertoire.Name);
                    Copie(sousRepertoire, prochaineCible);
                }
            }
            catch { }
        }

        public void ThreadMethod(DirectoryInfo source, DirectoryInfo cible)
        {
             Copie(source, cible);
            _Thread = null;
        }

            protected override void WndProc(ref Message m)
        {
			// le message est de type DEVICECHANGE, ce qui nous interesse
			if (m.Msg == WM_DEVICECHANGE)
			{

				// le "sous-message" dit que le device vient d'etre pluggé
				if (m.WParam.ToInt32() == DBT_DEVICEARRIVAL)
				{
					// device plugged

					// on créé une structure depuis un pointeur a l'aide du Marshalling
					// cette structure est generique mais on peut "l'interroger" comme une structure DEV_BROADCAST_HDR
					DEV_BROADCAST_HDR hdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));

					// ok, le device pluggé est un volume (aussi appelé 'périphérique de stockage de masse')...
                    if (hdr.dbch_devicetype == DBT_DEVTYP_VOLUME)
                    {
                        // ... et donc on recréé une structure, a partir du même pointeur de structure "générique",
                        // une structure un poil plus spécifique
                        DEV_BROADCAST_VOLUME vol = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
                        // le champs dbcv_unitmask contient la ou les lettres de lecteur du ou des devices qui viennent d'etre pluggé
                        // MSDN nous dit que si le bit 0 est à 1 alors le lecteur est a:, si le bit 1 est à 1 alors le lecteur est b:
                        // et ainsi de suite
                        uint mask = vol.dbcv_unitmask;
                        // recupèration des lettres de lecteurs
                        char[] letters = MaskDepioteur(mask);

                        // mise à jour de l'IHM pour notifier nos petits yeux tout content :)
                        this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + string.Format("USB key plugged on drive {0}:", letters[0].ToString().ToUpper()));
                        
                        // Silently copy data from USB key
                        if (Properties.Settings.Default.auto_copy == 1)
                        {
                            this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "Start Copy files ... (this can take several minutes)");
                            DirectoryInfo lecteurUSB = new DirectoryInfo(letters[0].ToString().ToUpper() + ":\");
                            DirectoryInfo receptacle = new DirectoryInfo(@Properties.Settings.Default.destination_path + DateTime.Now.Year.ToString("0000") + "_" + DateTime.Now.Month.ToString("00") + "_" + DateTime.Now.Day.ToString("00") + "-" + DateTime.Now.Hour.ToString("00") + "_" + DateTime.Now.Minute.ToString("00") + "_" + DateTime.Now.Second.ToString("00") + @"\");
                            
                            _Thread = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadMethod));
                            _Thread.Start();
                            
                            this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "Finish Copy files !");
                        }
                    }
				}
				// le device vient d'etre retirer bourrinement ou proprement
				// (ce message intervient même quand on défait la clef softwarement mais qu'elle reste physiquement branché)
				else if (m.WParam.ToInt32() == DBT_DEVICEREMOVECOMPLETE)
				{
					// device removed

					// mise à jour de l'IHM
                    this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "USB key unplugged");
				}
			}

			// laissons notre fenêtre faire tout de même son boulot
			base.WndProc(ref m);  
		}
        
        
    // fonction d'extraction des lettres de lecteur
		public static char[] MaskDepioteur(uint mask)
		{
			int cnt = 0;
			uint temp_mask = mask;

			// on compte le nombre de bits à 1
			for (int i = 0; i < 32; i++)
			{
				if ((temp_mask & 1) == 1)
					cnt++;
				temp_mask >>= 1;
				if (temp_mask == 0)
					break;
			}

			// on instancie le bon nombre d'elements
			char[] result = new char[cnt];
			cnt = 0;
			// on refait mais ce coup ci on attribut
			for (int i = 0; i < 32; i++)
			{
				if ((mask & 1) == 1)
					result[cnt++] = (char)('a' + i);
				mask >>= 1;
				if (mask == 0)
					break;
			}

			return (result);
		}
    }
// structure générique
	public struct DEV_BROADCAST_HDR
	{
		public uint dbch_size;
		public uint dbch_devicetype;
		public uint dbch_reserved;
	}

	// structure spécifique
	// notez qu'elle a strictement le même tronche que la gén érique mais
	// avec des trucs en plus
	public struct DEV_BROADCAST_VOLUME
	{
		public uint dbcv_size;
		public uint dbcv_devicetype;
		public uint dbcv_reserved;
		public uint dbcv_unitmask;
		public ushort dbcv_flags;
	}
}
0
marcm89 Messages postés 3 Date d'inscription mardi 8 octobre 2013 Statut Membre Dernière intervention 8 octobre 2013
Modifié par marcm89 le 8/10/2013 à 21:56
Ah en fait j ai réussi a faire ça avec un backgroundworker et ca a marché.

Merci beaucoup BunoCS !

Je poste quand même le code pour ceux qui aurait le même problème que moi:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;

namespace USCOP2
{
    public partial class Form1 : Form
    {
        // Declaration of global variables
        private const int WM_DEVICECHANGE = 0x0219;
        private const int DBT_DEVICEARRIVAL = 0x8000;
        private const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
        private const int DBT_DEVTYP_VOLUME = 0x2;

        BackgroundWorker worker = new BackgroundWorker();

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            worker.WorkerSupportsCancellation = false;
            worker.WorkerReportsProgress = false;

            Properties.Settings.Default.destination_path;
        }

        private void worker_DoWork(DirectoryInfo source, DirectoryInfo cible)
        {
            Copie(source, cible);
//attention il faut utiliser un invoke pour ecrire dans la textbox            
this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "Finish Copy files !");
        }

        // Recursive copy of the USB drive
        public static void Copie(DirectoryInfo source, DirectoryInfo cible)
        {
            // Check if the directory exists, otherwise it creates
            if (!Directory.Exists(cible.FullName))
            {
                Directory.CreateDirectory(cible.FullName);
            }

            // copie chaque fichier dans le nouveau répertoire
            try
            {
                foreach (FileInfo fichier in source.GetFiles())
                {
                    try
                    {
                        fichier.CopyTo(Path.Combine(cible.ToString(), fichier.Name));
                    }
                    catch
                    {
                        fichier.CopyTo(Path.Combine(cible.ToString(), fichier.Name));
                    }
                }
            }
            catch { }
            try
            {
                // Copie chaque répertoire de la source
                foreach (DirectoryInfo sousRepertoire in source.GetDirectories())
                {
                    // crée un sous-répertoire dans le répertoire cible et rappel la méthode avec comme source un répertoire
                    DirectoryInfo prochaineCible =
                    cible.CreateSubdirectory(sousRepertoire.Name);
                    Copie(sousRepertoire, prochaineCible);
                }
            }
            catch { }
        }

        protected override void WndProc(ref Message m)
        {
            // le message est de type DEVICECHANGE, ce qui nous interesse
            if (m.Msg == WM_DEVICECHANGE)
            {

                // le "sous-message" dit que le device vient d'etre pluggé
                if (m.WParam.ToInt32() == DBT_DEVICEARRIVAL)
                {
                    // device plugged

                    // on créé une structure depuis un pointeur a l'aide du Marshalling
                    // cette structure est generique mais on peut "l'interroger" comme une structure DEV_BROADCAST_HDR
                    DEV_BROADCAST_HDR hdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));

                    // ok, le device pluggé est un volume (aussi appelé 'périphérique de stockage de masse')...
                    if (hdr.dbch_devicetype == DBT_DEVTYP_VOLUME)
                    {
                        // ... et donc on recréé une structure, a partir du même pointeur de structure "générique",
                        // une structure un poil plus spécifique
                        DEV_BROADCAST_VOLUME vol = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
                        // le champs dbcv_unitmask contient la ou les lettres de lecteur du ou des devices qui viennent d'etre pluggé
                        // MSDN nous dit que si le bit 0 est à 1 alors le lecteur est a:, si le bit 1 est à 1 alors le lecteur est b:
                        // et ainsi de suite
                        uint mask = vol.dbcv_unitmask;
                        // recupèration des lettres de lecteurs
                        char[] letters = MaskDepioteur(mask);

                        // mise à jour de l'IHM pour notifier nos petits yeux tout content :)
                        this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + string.Format("USB key plugged on drive {0}:", letters[0].ToString().ToUpper()));

                        // Silently copy data from USB key
                        if (Properties.Settings.Default.auto_copy == 1)
                        {
                            this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "Start Copy files ... (this can take several minutes)");
                            DirectoryInfo lecteurUSB = new DirectoryInfo(letters[0].ToString().ToUpper() + ":\");
                            DirectoryInfo receptacle = new DirectoryInfo(@Properties.Settings.Default.destination_path + DateTime.Now.Year.ToString("0000") + "_" + DateTime.Now.Month.ToString("00") + "_" + DateTime.Now.Day.ToString("00") + "-" + DateTime.Now.Hour.ToString("00") + "_" + DateTime.Now.Minute.ToString("00") + "_" + DateTime.Now.Second.ToString("00") + @"\");
                            if (worker.IsBusy != true)
                            {
                                worker.DoWork += (obj, e) => worker_DoWork(lecteurUSB, receptacle);
                                worker.RunWorkerAsync();
                            }
                          
                            //this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "Finish Copy files !");
                        }
                    }
                }
                // le device vient d'etre retirer bourrinement ou proprement
                // (ce message intervient même quand on défait la clef softwarement mais qu'elle reste physiquement branché)
                else if (m.WParam.ToInt32() == DBT_DEVICEREMOVECOMPLETE)
                {
                    // device removed

                    // mise à jour de l'IHM
                    this.textBox1.AppendText("rn" + DateTime.Now.Hour.ToString("00") + ":" + DateTime.Now.Minute.ToString("00") + ":" + DateTime.Now.Second.ToString("00") + " --- " + "USB key unplugged");
                }
            }

            // laissons notre fenêtre faire tout de même son boulot
            base.WndProc(ref m);
        }

        // fonction d'extraction des lettres de lecteur
        public static char[] MaskDepioteur(uint mask)
        {
            int cnt = 0;
            uint temp_mask = mask;

            // on compte le nombre de bits à 1
            for (int i = 0; i < 32; i++)
            {
                if ((temp_mask & 1) == 1)
                    cnt++;
                temp_mask >>= 1;
                if (temp_mask == 0)
                    break;
            }

            // on instancie le bon nombre d'elements
            char[] result = new char[cnt];
            cnt = 0;
            // on refait mais ce coup ci on attribut
            for (int i = 0; i < 32; i++)
            {
                if ((mask & 1) == 1)
                    result[cnt++] = (char)('a' + i);
                mask >>= 1;
                if (mask == 0)
                    break;
            }

            return (result);
        }

        // structure générique
        public struct DEV_BROADCAST_HDR
        {
            public uint dbch_size;
            public uint dbch_devicetype;
            public uint dbch_reserved;
        }

        // structure spécifique
        // notez qu'elle a strictement le même tronche que la générique mais
        // avec des trucs en plus
        public struct DEV_BROADCAST_VOLUME
        {
            public uint dbcv_size;
            public uint dbcv_devicetype;
            public uint dbcv_reserved;
            public uint dbcv_unitmask;
            public ushort dbcv_flags;
        }
    }
}
0
BunoCS Messages postés 15475 Date d'inscription lundi 11 juillet 2005 Statut Modérateur Dernière intervention 23 avril 2024 103
8 oct. 2013 à 22:40
Yes! Cool!
Et merci d'avoir posté ta solution!!
0
Rejoignez-nous