2011-02-10 140 views
1

我已经编写了这个示例程序 - 简化了非常复杂的应用程序。相同的二进制[.exe]在启动时在某些机器上引发空指针异常。所以,我想知道如何正确构造Windows窗体。如何正确初始化Windows窗体应用程序

在我们的应用程序中,Form1_SizeChanged方法是this.ResumeLayout(false)方法的结果,这是InitializeComponents()中的最后一条语句。我不知道模拟这个,所以我只是为了这个测试程序而改变了自己的尺寸。

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"); 
    } 
} 

按照我的理解,Form1_SizeChanged不应该,除非Form1是正确构造调用。有人可以提出一些关于Windows Forms事件架构如何在这种情况下工作的信息?


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: 

通知来自堆栈跟踪:Form1_sizeChanged从InitializeComponents()调用..我认为不应该发生。 Form1_sizeChanged是Form1类的实例方法,不应在Form1构造之前调用。如果.NET环境想要处理这个事件,它应该等到Form1被正确构建,不是吗?

+0

抛出此异常在哪里?什么机器?你有没有堆栈跟踪? – Oded 2011-02-10 20:49:15

+0

@Oded:堆栈跟踪添加到问题中。 – 2011-02-10 21:21:56

回答

2

在构造函数的第二行中,设置Size。这将在记录器创建之前调用Form1_SizeChanged()。在调用MyInitialize之后移动Size的设置。

+0

我可以看到问题出在哪里,但我想知道为什么它是一个问题? Windows窗体“事件”应该排队[如@nagative()建议],并应在对象“Form1”构建正确之后运行,而不是在此之前运行。另外,请记住,只有一个主线程是Windows窗体应用程序。 @ALL我已经做了相应的更改,检查null,尽可能地将记录器初始化设置为“up”,但现在我没有看到问题。但我仍然想知道,到底是什么!为什么它是一个问题! – karephul 2011-02-10 21:21:23

1

我猜想,你在SizeChanged事件让你零误差/

你的InitializeComponent()程序设置了Form1_SizeChanged事件映射到窗体的SizeChanged事件。您的this.Size代码将触发该事件。由于logger.log在您的MyInitialize例程之前未被初始化,您可能会看到零星的结果。

基本上Windows会排队Form1_SizeChanged事件,并且你的应用程序会异步响应它,所以有时它可能会在Logger()初始化后响应它,有时它会在Logger初始化之前做出响应。

@Karephul:你看到这种行为的原因是消息循环处于应用程序级别,而不是单个类级别。一个GUI窗口就像其他任何类一样,一旦消息循环可以将它们发送给类并且不一定要等待构造函数完成,它将从消息循环中接收事件。

1

可能的是,Form1_SizeChanged在MyInitialize之前被调用,因此在记录器被初始化之前被调用。 将该方法更改为

if (logger != null) 
     logger.log("Form1_SizeChanged -- Enter"); 
0

您在问题中自己回答了问题。这一切都与ResumeLayout()调用有关。

自动生成的“InitializeComponent()”代码将关闭布局,组装组件,然后重新打开布局。我想这是一种优化,因为重新计算每个子组件添加的布局可能会变得缓慢。当布局重新启用时,很多组件的调整大小事件可能会被调用,包括您的。

请注意,表单代码在构造函数中可以调用的东西快速松散 - 调用各种虚拟方法,直到构造函数完成后才调用绝对不应该调用的虚方法。

此修复程序是初始化Load事件中必须具有完全构造的对象的任何东西。 Load事件只被调用一次并在实例完全构建后被调用。对你来说,这意味着除去什么开发环境管理和添加包含

private void Form1_Load(object sender, EventArgs e) 
{ 
    this.Load += Form1_SizeChanged; 
} 

是的,这是一个痛苦Form1_Load事件的Form1_SizeChanged事件。是的,你是对的,表格是错的。至少周围的工作并不太繁重。

哦,我没有完成。我听说Move事件可能发生在Load事件之前,但在构造函数完成之后发生,所以如果由于某种原因需要所有Move事件,则可能需要在Move事件处理程序中执行防御性编码,如sgmoore所示。