English 中文(简体)
如何正确初始化Windows窗体应用程序
原标题:How to properly initialize a Windows Forms application

我编写了这个示例程序,简化了一个非常复杂的应用程序。启动时,相同的二进制文件[.exe]在某些计算机上引发空指针异常。因此,我想知道如何正确构建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构造正确,否则不应调用Form1_SizeChanged。有人能介绍一下Windows窗体事件体系结构在这种情况下是如何工作的吗?


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构造正确,不是吗?

问题回答

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

Persumably, Form1_SizeChanged is being called before MyInitialize and hence before logger is initialised. Change that Method to

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

我猜您在SizeChanged事件中得到了null错误/

InitializeComponent()例程将Form1_SizeChanged事件设置为映射到Form的SizeChange事件。您的this.Size代码将触发所述事件。由于logger.log直到MyInitialize例程才被初始化,因此您可能会看到零星的结果。

基本上,Windows会将Form1_SizeChanged事件排队,您的应用程序会异步响应它,因此有时它可能会在Logger()初始化后响应它,有时它会在Loggers初始化前响应。

@Karephul:您看到这种行为的原因是消息循环在应用程序级别,而不是单个类级别。GUI窗口是一个类似于其他类的类,只要消息循环能够将事件发送到该类,它就会从消息循环中接收事件,而不必等待构造函数完成。

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

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

请注意,表单代码对可以从构造函数调用的内容反复无常——调用所有类型的虚拟方法,这些方法在构造函数完成之前绝对不应该被调用。

修复方法是初始化在Load事件中必须具有完全构造对象的任何对象。Load事件只被调用一次,并且在实例完全构造之后被调用。对您来说,这意味着从开发环境管理的内容中删除Form1_SizeChanged事件,并添加一个Form1_Load事件,其中包括

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

是的,这很痛苦。是的,你是对的,Forms是错的。至少周围的工作不太繁重。

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





相关问题
Bring window to foreground after Mutex fails

I was wondering if someone can tell me what would be the best way to bring my application to the foreground if a mutex was not able to be created for a new instance. E.g.: Application X is running ...

How to start WinForm app minimized to tray?

I ve successfully created an app that minimizes to the tray using a NotifyIcon. When the form is manually closed it is successfully hidden from the desktop, taskbar, and alt-tab. The problem occurs ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

Handle DataTable.DataRow cell change event

I have a DataTable that has several DataColumns and DataRow. Now i would like to handle an event when cell of this DataRow is changed. How to do this in c#?

Apparent Memory Leak in DataGridView

How do you force a DataGridView to release its reference to a bound DataSet? We have a rather large dataset being displayed in a DataGridView and noticed that resources were not being freed after the ...

ALT Key Shortcuts Hidden

I am using VS2008 and creating forms. By default, the underscore of the character in a textbox when using an ampersand is not shown when I run the application. ex. "&Goto Here" is not ...

WPF-XAML window in Winforms Application

I have a Winforms application coded in VS C# 2008 and want to insert a WPF window into the window pane of Winforms application. Could you explain me how this is done.

热门标签