Comment initialiser correctement une application Windows Forms
J'ai écrit cet exemple de programme - simplification d'une très complexe de l'application. Le même binaire [.exe] renvoie une exception de pointeur null sur certaines machines au démarrage. Donc, je veux savoir comment bien construire un Windows Forms forme.
Dans notre application, la Form1_SizeChanged
méthode est le résultat de this.ResumeLayout(false)
méthode qui est la dernière instruction dans InitializeComponents()
. Je ne sais pas à simuler, donc j'ai juste changé la taille moi-même pour ce programme de test.
public partial class Form1 : Form
{
public class Logger {
public Logger() { }
public void log(string str) {
Console.WriteLine("logging - " + str);
}
}
Logger logger = null;
public Form1()
{
InitializeComponent();
this.Size = new Size(200, 300);
MyInitialize();
}
private void MyInitialize() {
//Just that it takes some time.
Console.WriteLine("MyInitialize -- Enter");
for (int count = 0; count <5 ; count++){
Console.WriteLine("Sleeping -- " + count);
Thread.Sleep(1000);
}
logger = new Logger();
}
private void sleepingPill(int millisec) {
Thread.Sleep(millisec);
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
logger.log("Form1_SizeChanged -- Enter");
}
}
Selon ma compréhension, Form1_SizeChanged
ne devrait pas être appelée à moins que Form1
est bien construit. Quelqu'un peut jeter un peu de lumière sur la façon dont les Windows Forms architecture d'événements de travailler dans ce scénario?
Original Stack Trace: from our complex application
System.NullReferenceException was unhandled
Message=Object reference not set to an instance of an object.
Source=ABCD
StackTrace:
at ABCD.Form1.AppendToLog(String s)
at ABCD.Form1.Form1_SizeChanged(Object sender, EventArgs e)
at System.Windows.Forms.Control.OnSizeChanged(EventArgs e)
at System.Windows.Forms.Control.UpdateBounds(Int32 x, Int32 y, Int32 width, Int32 height, Int32 clientWidth, Int32 clientHeight)
at System.Windows.Forms.Control.UpdateBounds(Int32 x, Int32 y, Int32 width, Int32 height)
at System.Windows.Forms.Control.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.Form.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.Control.ScaleControl(SizeF factor, BoundsSpecified specified)
at System.Windows.Forms.ScrollableControl.ScaleControl(SizeF factor, BoundsSpecified specified)
at System.Windows.Forms.Form.ScaleControl(SizeF factor, BoundsSpecified specified)
at System.Windows.Forms.Control.ScaleControl(SizeF includedFactor, SizeF excludedFactor, Control requestingControl)
at System.Windows.Forms.ContainerControl.Scale(SizeF includedFactor, SizeF excludedFactor, Control requestingControl)
at System.Windows.Forms.ContainerControl.PerformAutoScale(Boolean includedBounds, Boolean excludedBounds)
at System.Windows.Forms.ContainerControl.PerformNeededAutoScaleOnLayout()
at System.Windows.Forms.ContainerControl.OnLayoutResuming(Boolean performLayout)
at System.Windows.Forms.Control.ResumeLayout(Boolean performLayout)
at ABCD.Form1.InitializeComponent()
at ABCD.Form1..ctor()
at ABCD.Program.Main()
InnerException:
Avis de la trace de la pile: Form1_sizeChanged est appelé à partir de InitializeComponents() .. ce qui je pense ne devrait pas arriver. Form1_sizeChanged est une méthode d'instance de la classe Form1 et ne doit pas être appelée avant la Form1 est construit. Si l' .NET de l'environnement à traiter ce cas, il doit attendre jusqu'à Form1 est bien construit, n'est-ce pas?
- Où est cette exception? Quelles machines? Avez-vous une trace de la pile?
- La trace de pile ajoutées à la question.
Vous devez vous connecter pour publier un commentaire.
En ligne deux du constructeur, vous définissez la Taille. Ce sera successivement appel Form1_SizeChanged() avant de l'enregistreur de données est créée. Déplacer le réglage de la Taille après l'appel à MyInitialize.
Persumably, Form1_SizeChanged est appelé avant MyInitialize et donc avant le logger est initialisé.
Changement de la Méthode de
Je suppose que vous êtes l'obtention de votre erreur nulle sur la SizeChanged événement/
Votre InitializeComponent() de la routine met en place le Form1_SizeChanged événement pour être mappé à la Forme du SizeChanged événement. Votre ce.Taille code des incendies de l'événement. Depuis l'enregistreur.le journal n'est pas de l'initialisation jusqu'à ce que votre MyInitialize routine, vous pouvez voir des résultats sporadiques.
Fondamentalement Windows file d'attente jusqu'à la Form1_SizeChanged de l'événement et de votre app, vous permettra de répondre de manière asynchrone, donc il peut parfois y répondre après que l'Enregistreur() est initialisé et parfois il faudra répondre avant de l'Enregistreur de données est initialisé.
@Karephul: La raison pour laquelle vous voyez ce comportement est que la boucle de message est au niveau de l'application, et pas au niveau de la classe. Une interface est une classe comme rien d'autre et recevoir des événements de la boucle de message dès que la boucle de message pouvez les envoyer à la classe et n'est pas nécessairement attendre que le constructeur d'être terminé.
Vous avez répondu vous-même à votre question. Il a tout à voir avec la ResumeLayout() de l'appel.
Auto-généré "InitializeComponent()" code désactive la mise en page, assemble les composants, et puis tourne à disposition sur le retour. Ceci, je suppose, est une optimisation de recalculer la disposition sur chaque sous-composant plus pourrait devenir lent. Lors de la mise en page se tourna sur le dos, les événements de redimensionnement de beaucoup de composants seront probablement appelés, y compris la vôtre.
Note que les formulaires code joue vite et lâche avec ce qui peut être appelé à partir du constructeur - l'appel de toutes sortes de méthodes virtuelles qui absolument ne devrait pas être appelé jusqu'à ce que après le constructeur est fini.
La solution est d'initialiser tout ce qui doit disposer d'un objet construit dans l'événement Load. L'événement Load seulement est appelé une fois et est appelé après que l'instance est entièrement construit. Pour vous, cela signifie le retrait de la Form1_SizeChanged événement de ce que l'environnement de développement gère et ajouter un événementielle Form1_Load qui comprend
Oui c'est une douleur. Oui, vous avez raison et des Formes de mal. Au moins le travail autour n'est pas trop onéreux.
Oh, je ne suis pas fait. J'ai entendu dire que de Déplacer les événements peuvent se produire avant que l'événement de Chargement mais après le constructeur est fini donc, si vous devez Déplacer tous les événements pour une raison quelconque, vous pouvez avoir besoin de faire de la défensive de codage dans le mouvement de gestionnaire d'événements comme sgmoore a montré.