[C#][Winform] Mettre à jour un contrôle à partir d'une classe fils

Hello les gens,

J’ai un gros problème ou pas. J’ai une winform et je veux pouvoir externaliser mes traitements sur la partie présentation sur une classe autre que la Winform. Donc j’ai créé une classe qui hérite de ma Winform.

Ce qui donne ceci:

[code]public class FrmMainApps : System.Windows.Forms.Form
{

}

public class MaClasse : FrmMainApps
{

}[/code]

Dans MaClasse, j’arrive donc à récupérer les contrôles qui sont déclarés en public/protected. Le problème, c’est que j’ai beau leur attribuer une valeur, cette dernière n’est pas répercuté sur la Winform. Ai-je zappé quelque chose ?
Du coup, je me suis contenté de faire du passage de paramètres dans ma méthode. Et j’aimerai bien pouvoir m’en passer. la doc MSDN ne me donne pas grand chose (ou alors j’ai saisie les mauvais mots clés). Et google n’est pas mon ami pour l’instant.

Voilà, voilà.
Et puis est ce vraiment possible de faire ce que je demande, ou il faut forcément passer par le passage de paramètres ?

Je suis pas certain de vraiment bien comprendre ce que tu veux faire, mais si je comprends bien, tu as créé un classe qui hérite d’une Form et tu as créé une instance de cette classe. Maintenant, tu modifies un paramètre de cette instance et tu voudrais que ça modifie les mêmes paramètres sur l’instance de la classe de base. Est-ce que c’est bien ça?

Si c’est le cas, je pense que tu as dû mal comprendre l’utilité de l’héritage et des instances de classe. Ce que tu veux faire plutôt c’est d’avoir une classe à laquelle tu vas passer l’instance de ta Form et qui va ensuite utiliser les paramètres de cette instance pour les modifier.

Enfin, dis moi déjà si j’ai bien compris ton problème et je t’expliquerais plus à fond, ça ne sert à rien que je parte dans des explications complexes si je suis à côté de la plaque :).

Le pattern Model View Presenter serait une solution adaptée non?

C’est bien ça mais sur des control.

Bon, hum… alors grosse explication. Mais tu devrais regarder sur le net si tu ne trouves pas un cours de programmation orientée objet, parce qu’avant de coder en C# ce serait pas mal d’avoir les bases de la POO, sinon tu vas vraiment galérer.

En gros, une classe on peut voir ça comme un plan, qui va expliquer comment il faut faire un objet. Elle va décrire tout ce qu’il doit contenir et comment ça va être représenté.
Imaginons une classe Voiture : dans cette classe tu vas dire qu’elle doit avoir des roues, un moteur, etc.
Maintenant quand tu fais

Voiture maVoiture = new Voiture();

tu crées une nouvelle instance de ta voiture, c’est à dire un objet voiture créé d’après les plans définis.

Maintenant imaginons une classe qui dérive de Voiture, disons SUV. Cette classe va hériter de toutes les caractéristiques de la classe Voiture (donc elle va avoir un moteur, 4 roues, etc.) mais en plus elle va pouvoir contenir des modifications et de nouveaux paramètres ou méthodes. Par exemple, la classe SUV va avoir une nouvelle méthode PolluerBeaucoup() :slight_smile: .

Maintenant quand tu fais

SUV leSUVDuVoisinIdiot = new SUV();

tu crées une instance de la classe SUV. Les modifications que tu vas faire sur cette instance ne seront pas répercutées sur l’instance maVoiture parce que ce sont deux objets différents. Si tu changes ta voiture de couleur, ça ne va pas changer la couleur de la voiture du voisin, même s’il a la même marque et le même type. C’est exactement pareil ici.

Donc pour ce que tu veux faire un des moyen serait, comme Totoradio l’a suggéré, de regarder le pattern Model View Presenter.

Bref, essaye de comprendre déjà un peu tout ça et n’hésite pas si tu as encore des questions.

Arg, non c’est pas à ça que je pensais. D’ailleurs, j’ai bien compris le concept de la POO. Ce que je cherche à faire c’est de pouvoir récupérer mon instance de mon confrol Winform (de System.Windows.Forms.Textbox/Label/etc…) et de le modifier à loirsir non pas dans le code behind de ma Form mais dans une classe à part. Ouais, parce en fait, on est 2 à vouloir travailler sur la même Winform et avec SourceSafe, faut qu’on trouve un moyen pour qu’on puisse s’occuper uniquement de notre partie sans impacté/ralentir le travail du binome.

Bien évidemment, si ma classe hérite de ma Form, je suis censé accéder à ces contrôles. Chose qui marche parfaitement. Le soucis que j’ai, c’est de répercuter mes modifs de controls (contenu, présentation, autres) sur la winform. Et là, ça ne le fait pas du tout.

Form.Invalidate() ?

Sinon, passez sur SVN/TFS/P4, parce que sourcesafe, c’est un peu de la daube en barre.

Enfin, sinon, je penses qu’il te faut un event et que la form aille chercher ses données, ca pourrait marcher. Enfin, il va falloir decider d’une archi qui le permette quoi.

Sous réserve d’avoir bien compris ton problème, les events pour ça c’est effectivement le pied. Comme le dit AnA-I, il faut créer ensemble avec ton binôme une archi permettant de supporter les events, de se mettre d’accord sur les events utilisés et après roulez jeunesse!

Vous faites ainsi chacun vos traitement dans votre coin dans des classes à part et balancez des events dès que qqch à besoin d’être refresh. L’avantage c’est que ta form garde le contrôle sur ce qu’elle affiche et elle est bien la seule et unique à toucher à ses controls, le traitement étant bien séparé de l’affichage. Si jamais tu veux changer ton IG, tu n’as qu’une couche à toucher tant que tes nouveaux controls écoutent les même events!

Vous pourriez en dire plus sur ces events ? Ca n’a rien à voir avec les event handler d’un bouton/textbox ?

Pour faire simple il faut utiliser les évènements quand une classe à besoin de faire savoir à d’autres classes qu’elle a changé d’état.

Un exemple, j’ai une classe Voiture et j’ai une classe PorteDeGarage. J’adore la haute technologie et je souhaite que ma porte de garage s’ouvre lorsque je démarre ma voiture. En objet ma classe PorteDeGarage va s’enregistrer au prêt de ma classe Voiture pour que cette dernière la prévienne lorsqu’elle démarre :

La classe Voiture :

[code]class Voiture {
[…]
private List voitureListeners;

public void addVoitureListener(VoitureListener listener) {
voitureListeners.add(listener);
}

public void demarrer() {
[…]
for(VoitureListener listener: voitureListeners) {
listeners.voitureStarted(new VoitureEvent());
}
}
}[/code]

Et la classe porte de garage :

class PorteDeGarage implements VoitureListener { public void voitureStarted(VoitureEvent e) { this.souvrir(); } }

Sans oublié le listener :

public interface VoitureListener { public void voitureStarted(VoitureEvent e); }

La syntaxe est en Java mais le concept s’applique à tout langage objet.

http://msdn.microsoft.com/fr-fr/library/8627sbea(VS.80).aspx

MetalDestroyer, tu veux pas coller du code là ? Parce que ce que tu cherche a faire marche sans rien rajouter de particulier, donc je subodore un probleme de logique, et les events ne t’aideront pas plus que ca. Par ailleurs, plutot que d’hériter des classes, tu peut pas passer par une classe partielle ? A moins que tu ne soit en .net 1.0, ca correspondrait a tes besoins, sans refaire une classe juste pour y ranger du code.

Ok, ok. Alors j’ai un projet Windows qui contient une Winform et un fichier de type classe. Ah oui, je travail en .net 1.1, pas moyen de faire plusieurs solution Visual Studio pour un même gros projet. Donc du coup, pas de .net 2.0 pour cette appli windows.

MonProjet

  • FrmMainApps.cs (qui est mon formulaire windows)
  • MaClasseA.cs
  • MaClasseB.cs

Sachant que MaClasseA s’occupe d’un traitement spécifique. Et B idem.

Le code de mon formulaire :

[code]public class FrmMainApps : System.Windows.Forms.Form
{
// Attributs public
public System.Windows.Forms.ProgressBar progressBar;
public System.Windows.Forms.Button btnImport;
public System.Windows.Forms.Textbox txbLog;

	private void InitializeComponent()
	{
		this.progressBar = new System.Windows.Forms.ProgressBar();
		this.btnImport = new System.Windows.Forms.Button();
		this.txbLog = new System.Windows.Forms.Textbox();
		...
	}

	[STAThread]
	static void Main() 
	{
		Application.Run(new FrmMainApps());
	}

	private void btnImport_Click(object sender, System.EventArgs e)
	{
		try
		{
			this.threadImport = new Thread(new ThreadStart(ImporterDonnees));
			this.threadImport.Priority = ThreadPriority.Normal;
			this.threadImporte.Start();
		}
		catch(Exception ex)
		{
			...
		}
	}

	private void ImporterDonnees()
	{
		try
		{
			MaClasseA objImport = new MaClasseA();
			objImport.Process();   // La méthode Process doit pouvoir modifier le contenu de mes contrôles progressBar, textbox, ...
		}
		catch(Exception ex)
		{
			...
		}
		finally
		{
			threadImport.Abort();
		}
	}
}[/code]

Ce que j’avais fait avant de fournir en paramètre les contrôles à ma méthode Process (pas moyen de perdre du temps en ce moment, la deadline est assez proche et il reste encore énormément de boulots à côté de cet Import).

Code de MaClasseA:

public class MaClasseA : FrmMainApps { public void Process() { try { txbLog.Text = "Hello world"; // Sachant que txbLog = FrmMainApps.txbLog ... } catch(Exception e) { ... } } }

Donc en sortant de ma méthode Process, ma Textbox doit m’afficher “Hello World”. Or, rien n’est mis à jour.

Hum, c’est juste exactement ce que j’avais écrit dans mes explications et que tu disais avoir compris…
quand tu fais MaClasseA objImport = new MaClasseA(); objImport.Process();
tu crées un nouvel objet qui certes hérites (pour de mauvaises raisons à mon avis) de ton objet qui l’appelle, mais c’est un nouvel objet. Modifier ses paramètres ne vont pas modifier les paramètres de la fenêtre de base.

D’ailleurs le commentaire où tu dis “// Sachant que txbLog = FrmMainApps.txbLog” est faux… txtbLog c’est MaClasseA.txbLog… ou plus exactement objImport.txtbLog

Ce que tu voudrais faire c’est plutôt quelque chose du genre :

[code]public class MaClasseA
{
FrmMainApps maClassePrincipale;

	MaClasseA(FrmMainApps maClassePrincipale)
	{
		  this.maClassePrincipale = maClassePrincipale;
	}

	public void Process()
	{
		try
		{
			maClassePrincipale .txbLog.Text = "Hello world";
		}
		catch(Exception e)
		{
			...
		}
	}
	}[/code]

Autre remarque, les attributs publiques c’est LE MAL ! Utilise les properties en .Net (tu mets tes attributs private et tu fais :

private string monAttribut; public string MonAttribut { get {return monAttribut;} set {monAttribut = value;} }

Enfin, dernière remarque, l’exemple que je t’ai donné plus haut n’est pas génial non plus en fait… Tu aurais vraiment meilleur temps d’utiliser les évènements pour faire le genre de choses que tu veux faire.

Je suis globalement d’accord avec Gimly. Ceci étant dit, je vois pas pourquoi tu créée pas directement une instance de MaClasseA dans le Main() :

[STAThread] static void Main() { Application.Run(new MaClasseA()); }

Attention aussi au multi threading qui nécessite souvent un Control.Invoke(…), et plus particulièrement dans ton cas.

Là où je rejoins d’autant plus Gimly c’est que la solution que tu a choisie l’a été pour de mauvaises raisons. Et du coup je rejoins maintenant le début du thread et l’histoire des events. En gros (en pseudo code pas propre parce que j’ai pas que ca a foutre :)) :

[code]public class FrmMainApps : System.Windows.Forms.Form
{
protected MaClasseQuiProcess mcqp = new MaClasseQuiProcess()
private void InitializeComponent()
{
this.progressBar = new System.Windows.Forms.ProgressBar();
this.btnImport = new System.Windows.Forms.Button();
this.txbLog = new System.Windows.Forms.Textbox();

// ici ou ailleurs hein, tant que c’est pluggé avant que les données soient traitées
this.mcqp.UnTrucOccured += new UnTrucEventHandler(this.UnTrucHappened);
}

	private void UnTrucHappened(string duTexte)
	{
		if(this.monControl.InvokeRequired) 
				 // monControl.Invoke(...);
				 // note qu'y a des moyens de tout faire dans le même, mais ca sera pas forcément propre dans ton cas.
				// Faudra donc passer par une autre fonction intermédiaire. 
				//En jouant avec Invoke tu comprendra ce que je veut dire
		else
			  monControl.Text = duTexte;
	 }

}[/code]

[code]public delegate void UnTrucEventHandler();

public class MaClasseQuiProcess
{
public event UnTrucEventHandler UnTrucOccured;
public void Process()
{
// là tu fais des trucs, et quand tu a besoin de mettre à jour quelque chose :

				OnUnTruc();

		}

		public void OnUnTruc()
		{
				 if(UnTrucOccured != null)
						  UnTrucOccured();
		}

}[/code]

Ca te donne plein d’avantages : tu n’a plus qu’un form, tu garde la mise à jour du form dans le form en question, et le traitement des données ailleurs. Par ailleurs, si tu veux déclencher autant de process que tu veux, du coup tu le fait dans ta classe qui process. Enfin, si la logique globale des deux classes qui processent est la même mais le traitement différent, tu peux créer une classe de base qui sera héritée par les deux et dans laquelle tu mettra le stuff commun.

Les premières tentatives à base d’event et de delegate sont souvent un peu pénibles, mais une fois que t’a choppé le coup, c’est que du bonheur.

Justement, d’après la tooltip de Visual Studio, ma variable txbLog est reconnu comme appartenant à FrmMainApps et non à MaClasseA.

Bon, maintenant, tous cela me demande pas mal de modifications à faire (problème de timing) et du temps pour comprendre toussa notamment l’histoire des threads, des events, etc… Je m’attendais à 2 ou 3 lignes d’instructions toutes connes. Cela dit, c’est bon à savoir pour les prochains projets ou dès que j’aurai du temps pour améliorer mon code.

Et doit on encore rappeller que seul le thread de UI a la droit de modifier la UI? Pas de modification de UI depuis un autre thread damit, les resultats sont pas garantis du tout! Et c’est malpoli!

Matter du cote de BackgroundWorker en 2.0+ ou de Form.Invoke et autre…

Ca m’embete toujours un peu de dire des choses comme ca, mais t’a manifestement un probleme de compréhension de la programation objet :confused: C’est complétement logique qu’elle soit reconnue comme appartenant a FrmMainApps puisque c’est là qu’elle est déclarée. Mais le problème ne vient pas de là mais du fait que tu fais un new, alors que tu cherche a récupérer un objet qui existe déjà (comme ce qui a été dit plus haut)

Je rejoins Gimly et bishop. Relis bien ton code MetalDestroyer. Si tu ne comprends pas pourquoi ça ne peut PAS fonctionner (en oubliant les threads, try-catch et autres complexités, juste en mode console/debug tout bête), il vaut mieux que tu laisses ça de côté en arrêtant d’improviser et que tu te concentres uniquement sur ce que tu dois et sais faire. Si vous n’êtes que deux dessus et ne maîtrisez pas les SVN ou autres, en attendant de vous former, ben codez à l’ancienne et fusionnez votre travail en fin de journée à la main. Je vais essayer de simplifier ton code pour que tu y voies plus clair :

[quote=“MetalDestroyer version light, post:13, topic: 48614”]MonProjet (version light)

  • FrmMainApps.cs (qui est mon formulaire windows)
  • MaClasseA.cs

Sachant que MaClasseA s’occupe d’un traitement spécifique.

Le code (version light) :

[code]public class FrmMainApps : System.Windows.Forms.Form
{
public System.Windows.Forms.Button btnImport;
public System.Windows.Forms.Textbox txbLog;

	[STAThread]
	static void Main() 
	{
		Application.Run(new FrmMainApps());
	}

	private void btnImport_Click(object sender, System.EventArgs e)
	{
		MaClasseA objImport = new MaClasseA();
		objImport.Process();
		Debug.WriteLine(this.txbLog.Text);

// texte de l’objet this de ta classe FrmMainApps qui hérite de System.Windows.Forms.Form
// => celui qui est donc affiché
Debug.WriteLine(objImport.txbLog.Text);
// texte de l’autre objet objImport de ta classe MaClasseA qui hérite de la classe FrmMainApps et n’est donc qu’un vague cousin éloigné de l’objet this, uniquement visible localement dans le code du clic du bouton
// => celui qui est donc comme le H de Hawaii…
}
// Il n’y a AUCUNE RAISON pour que les 2 valeurs soient identiques !
// Tu ne dis nulle part que le membre txbLog de ton this doit prendre la valeur de ton 2e objet local objImport qui existe uniquement dans la fonction btnImport_Click et donc disparaît avec la fin de la fonction…
}

public class MaClasseA : FrmMainApps
{
public void Process()
{
txbLog.Text = “Hello world”;
}
}[/code][/quote]