C# 实例化WinForm用户控件时不总是调用基类

C# 实例化WinForm用户控件时不总是调用基类,c#,user-controls,base-class,onload-event,intermittent,C#,User Controls,Base Class,Onload Event,Intermittent,我有一个有解决方法的时髦问题,但我希望代码尽可能相似。问题集中在我的用户控件基类中的一个特定变量上,该变量可能为null,也可能不为null,并且永远不应为null 基本上,我有许多用户控件,这些控件带有一个基类,该基类获取我的主窗体窗口的一个实例,因此用户控件可以访问主窗体属性,并可以调用主窗体上的方法。以下是一个片段(this.frmParent是公共成员): 然后,每个用户控件共享此基类: public partial class ucLiberty : ucBase 然后在主窗体中,我

我有一个有解决方法的时髦问题,但我希望代码尽可能相似。问题集中在我的用户控件基类中的一个特定变量上,该变量可能为null,也可能不为null,并且永远不应为null

基本上,我有许多用户控件,这些控件带有一个基类,该基类获取我的主窗体窗口的一个实例,因此用户控件可以访问主窗体属性,并可以调用主窗体上的方法。以下是一个片段(this.frmParent是公共成员):

然后,每个用户控件共享此基类:

public partial class ucLiberty : ucBase
然后在主窗体中,我将如下调用用户控件:

                ucLiberty Liberty = new ucLiberty();
                IQDevicePath = Liberty.GetIQDrivePath();
this.frmParent = new frmMain();
出于某种原因,当我实例化用户控件时(在本例中,它是主窗体),基类中的frmParent变量可能会也可能不会填充非null值

我注意到用户控件中的加载事件没有触发。我找到了一个名为CreateControl()的方法,该方法应该强制创建控件,然后我的加载事件开始触发,但是当我在调试器中跟踪执行时,我找到了它试图填充frmParent的基类,FindForm()并不总是返回非null值

我有其他的用户控件,但我没有这个问题,它们之间的区别是,有些用户控件有子控件,有些没有子控件。没有子控件的控件有此问题

我的解决方法是监视哪个用户控件FindForm()失败,并在该用户控件的加载事件中,通过调用主窗体的构造函数来分配值,如下所示:

                ucLiberty Liberty = new ucLiberty();
                IQDevicePath = Liberty.GetIQDrivePath();
this.frmParent = new frmMain();
但是,我仍然需要调用CreateControl()才能触发加载事件,我不喜欢要求未来的维护人员必须明确了解不同的行为规则。换句话说,我希望我的用户控件都以相同的方式工作,以保持维护简单

我已经将代码拆开,无法理解为什么有时用户控件的加载事件可能会触发,也可能不会触发,以及为什么用户控件基类中对FindForm()的调用失败


有人对如何解决这些问题有什么想法吗?谢谢

通过让用户控件知道它所在的表单,您正在犯一个相当严重的OOP错误。它应该是一个独立的类,不关心它的容器,您可以使用事件让容器知道在您的类中发生的容器可能感兴趣的任何事情。Winforms中任何标准控件都严格遵循的设计原则。例如,文本框从不关心它被放置在什么样的表单上

这是理论,实践往往不是那么干净。您遇到的问题是OnLoad方法(也称为Load事件)是由于不同的原因触发的。它在创建本机Windows句柄时运行。这通常发生在表单的窗口创建时,由Show()方法调用触发。它位于表单的initializeComponent()方法之后

如果用户控件的构造函数中有任何代码要求Handle属性具有值,则Winforms会强制并为控件创建Windows句柄,并触发Load事件。在表单的InitializeComponent()方法有机会调用它的Controls.Add()方法之前,太快了。父属性尚未引用表单。FindForm()上的Kaboom


使用调试器很容易诊断。在用户控件的OnLoad方法上设置断点。堆栈跟踪将直接指向触发句柄创建的语句。

您有多少个主窗体实例?如果你只有一个,而且永远只有一个,你可以让它成为一个单身汉

public class frmMain : Form
{
     private static frmMain s_Singleton;

     public static frmMain Singleton
     {
          get
          {
              if (s_Singleton == null) s_Singleton = new frmMain();
              return s_Singleton;
          }

     }
}
因此,与其直接调用构造函数,不如调用frmMain.Singleton作为引用(即使在(尤其是在!)Program.cs中,表单最有可能是最初构造的)。此外,您还可以通过在用户控件中调用
frmMain.Singleton
获得对主窗体的全局可访问引用

至于ucBase_加载没有加载的原因,我未经教育的猜测是,您也在具体的用户控件中处理事件,并且它以某种方式阻止了基本处理程序的启动。如果是这种情况,请在具体用户控件的事件处理程序中添加
base.OnLoad()

至于FindForm不工作的原因,可能是因为在用户控件的构造函数完成之前调用了该方法。这似乎不太可能,但如果没有看到您的代码,就很难确定。这可能是因为控件的父级etc是在构造函数中设置的。但由于您是在Load事件中处理它的,所以看起来不太可能。您可以通过将逻辑移动到OnParentChanged事件来验证此理论


顺便说一句,您的解决方法似乎非常脏,它没有给您主窗体的引用,它创建了一个新的主窗体实例(从未显示过)。

除了VS放在那里的InitializeComponent()之外,用户控件构造函数中没有任何内容。用户控件中没有控件,只有代码。在实例化控件时不会触发load事件,在调用其第一个方法时不会调用它,而是在调用第二个方法时调用它,并且FindForm()返回null。