实例化 WinForm 用户控件时并不总是调用基类
我有一个奇怪的问题,有一个解决方法,但我想保持代码尽可能相似。问题集中在我的用户控件的基类中的一个特定变量上,该变量可能为空,也可能不为空,并且永远不应该为空。
基本上,我有许多具有单个基类的用户控件,该基类获取主窗体窗口的实例,以便用户控件可以访问主窗体属性并可以调用主窗体上的方法。这是一个片段(this.frmParent 是一个公共成员):
private void ucBase_Load( object sender, EventArgs e )
{
// Establish the link to the main form.
this.frmParent = FindForm() as frmMain;
}
然后每个用户控件共享这个基类:
public partial class ucLiberty : ucBase
然后在主窗体中,我将像这样调用用户控件:
ucLiberty Liberty = new ucLiberty();
IQDevicePath = Liberty.GetIQDrivePath();
由于某种原因,当我实例化用户控件时(在本例中,它位于主窗体中),基类中的 frmParent 变量可能会也可能不会填充非空值。
我注意到用户控件中的加载事件没有触发。我找到了一个名为 CreateControl() 的方法,该方法应该强制创建控件,然后我的加载事件开始触发,但是当我在调试器中跟踪执行情况时,我到达了它试图填充 frmParent 的基类, FindForm() 并不总是返回非空值。
我有其他用户控件,但没有这个问题,它们之间的区别在于,有些用户控件有子控件,有些没有子控件。没有子控件的有这个问题。
我的解决方法是监视哪个用户控件 FindForm() 失败,并在该用户控件的加载事件中,通过调用主窗体的构造函数来分配值,如下所示:
this.frmParent = new frmMain();
但是,我仍然必须调用 CreateControl( )才能触发加载事件,而且我不喜欢要求未来的维护者必须对不同的行为命令有明确的了解的想法。换句话说,我希望我的用户控件都以相同的方式工作,以保持维护简单。
我已经把我的代码拆散了,无法弄清楚为什么有时用户控件的加载事件可能会或可能不会触发,以及为什么在用户控件基类中对 FindForm() 的调用失败。
有人对如何解决这些问题有任何想法吗?谢谢。
I have a funky problem that has a workaround, but I want to keep code as similar as possible. The issue centers on a particular variable in the base class for my user controls that may or may not be null, and it should never be null.
Basically I have a number of user controls with a single base class which grabs an instance of my main form window so the user control has access to main form properties and can call methods on the main form. Here is a snippet (this.frmParent is a public member):
private void ucBase_Load( object sender, EventArgs e )
{
// Establish the link to the main form.
this.frmParent = FindForm() as frmMain;
}
Then each user control shares this base class:
public partial class ucLiberty : ucBase
Then in the main form, I'll call the user control like this:
ucLiberty Liberty = new ucLiberty();
IQDevicePath = Liberty.GetIQDrivePath();
For some reason, when I instantiate the user control (in this case it's in the main form), the frmParent variable in the base class may or may not be populated with a non null value.
I noticed that the load event in the user control was not firing. I found a method called CreateControl() which is supposed to force the creation of the control, and then my load event started fireing, however when I traced execution in the debugger and I got to the base class where it was trying to populate frmParent, FindForm() would not always return a non null value.
I have other user controls where I don't have this issue with, and the difference between them is that some user controls have child controls and some do not have child controls. The one's without child controls have this problem.
My workaround is to monitor which user control FindForm() fails in, and in that user control's load event, assign the value with a call to the main form's constructor, something like this:
this.frmParent = new frmMain();
However, I still have to have the call to CreateControl() for the load event to fire, and I don't like the idea of requiring future maintainers to have to have explicit knowledge of different behavioral imperatives. In other words, I'd like my user controls to all work the same way to keep maintenance simple.
I have torn my code apart and cannot figure out why sometimes the user control's load event may or may not fire, and why the call to FindForm() in the user control base class fails.
Does anyone have any ideas about how to solve these issues? Thanks.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
通过让用户控件知道它所在的窗体,您犯了相当严重的 OOP 罪过。它应该是一个独立的类,不关心它的容器,您使用事件让容器知道您的类中发生的任何容器可能感兴趣的事情。 Winforms 中的任何标准控件都严格遵循这一原则。例如,文本框从不关心它被放置在哪种表单上。
这就是理论,实践往往并不那么干净。您遇到的问题是 OnLoad 方法(又名 Load 事件)由于不同的原因而触发。它在创建本机 Windows 句柄时运行。 通常在创建表单窗口时发生,由 Show() 方法调用触发。它位于表单的 IntializeComponent() 方法之后。
如果用户控件的构造函数中有任何代码要求 Handle 属性具有值,则 Winforms 会强制为您的控件创建 Windows 句柄并触发 Load 事件。太早了,窗体的 InitializeComponent() 方法有机会调用其 Controls.Add() 方法。 Parent 属性尚未引用表单。 FindForm() 上的 Kaboom。
使用调试器很容易诊断。在用户控件的 OnLoad 方法上设置断点。堆栈跟踪将带您直接到达触发句柄创建的语句。
You are committing a fairly serious OOP sin by making the user control aware of the form it is placed on. It is supposed to be a independent class that doesn't care about its container, you use events to let the container be aware of anything that happened in your class that the container might be interested in. A design principle that's followed to the letter by any of the standard controls in Winforms. A TextBox never cares what kind of form it is dropped on, for example.
That's the theory, practice isn't often that clean. The problem you are running into is that the OnLoad method (aka Load event) fires for a different reason. It runs when the native Windows handle gets created. Which normally happens when the form's window gets created, triggered by the Show() method call. Which is after the form's IntializeComponent() method.
If you have any code in your user control's constructor that requires that the Handle property has a value then Winforms obliges and creates the Windows handle for your control and fires the Load event. Too soon, before the form's InitializeComponent() method had a chance to call its Controls.Add() method. The Parent property doesn't yet refer to the form. Kaboom on FindForm().
It is easy to diagnose with the debugger. Set a breakpoint on the user control's OnLoad method. The stack trace will take you right to the statement that triggered the handle creation.
您有多少个主表单实例?如果您只有一个 - 并且永远只会有一个,您可以将其作为单例进行访问。
因此,不要直接调用构造函数,而是调用 frmMain.Singleton 来获取引用(即使在(尤其是!)您的 Program.cs 中最有可能最初构造表单的地方)。此外,您可以通过在用户控件中调用
frmMain.Singleton
来获取对主窗体的全局可访问引用至于您的ucBase_Load未加载的原因,我的无知猜测是您还处理了具体用户控件中的事件,并且它以某种方式阻止基本处理程序触发。如果是这种情况,请在具体用户控件的事件处理程序中添加
base.OnLoad()
。至于为什么FindForm不起作用,可能是因为该方法在用户控件的构造函数完成之前被调用所致。这似乎不太可能,但在没有看到您的代码的情况下很难确定。这可能是问题的原因是控件的父级等是在构造函数中设置的。但由于您是在 Load 事件中处理它,所以这似乎不太可能。您可以通过将逻辑移至 OnParentChanged 事件来验证此理论。
顺便说一句,您的工作似乎非常肮脏,它不会为您提供对主表单的引用,它会创建一个新的主表单实例(从未显示) 。
How many main form instances do you have? If you only have one - and ever only will have one, you can make it accessible as a singleton.
So instead of ever calling the constructor directly, call frmMain.Singleton for a reference (even in (especially in!) your Program.cs where the form is most likely initially constructed). Furthermore, you have a globally accessible reference to your main form, available by calling
frmMain.Singleton
in your user controlsAs to the reason why your ucBase_Load does not load, my uneducated guess is that you also handle the event in the concrete user control and that it somehow stops the base handler from firing. If that is the case, add
base.OnLoad()
in your concrete user control's event handler.And as to why FindForm does not work, it could be caused because the method is called before the user control's constructor is finished. This seems unlikely but it's hard to say for sure without seeing your code. The reason this might be the issue is that the control's parent etc is set up in the constructor. But since you're handling it in the Load-event, it seems unlikely. You could verify this theory by moving the logic to the OnParentChanged event.
By the way, your work around seems very dirty, it does not give you a reference to your main form, it creates a new main form instance (which is never shown).