翻译自:
http://wiki.wxpython.org/TwoStageCreation
译者:winterTTr (转载请注明 )
wxWdiget支持两步构造的窗体对象。这种方式有时是很必要的,尤其是在需要额外的flag或者使用XRC这样的类工厂的时候。因为C++本身允许重载方法(也包括重载构造函数),所以很容易来预先创建一个窗口实例,然后再调用Create。但是在python中,被包含在__init__中的构造函数,也负责创建的过程。
在wxPython中,也是可能使用两步构造的,只是需要耍点小花招。所有的类都具有一个工程函数用来与创建对象实例,之后你可以调用Create 方法来做第二步构造(或者让XRC等等为你做点别的事情)。这些工厂函数名字就是“Pre+类名”,例如:wx.PreWindow , wx.PreFrame 等等。
下面这个例子就是继承自wx.Dialog,然后设置一些额外的参数。请注意:这里并没有调用wx.Dialog.__init__ 方法,而是调用了Create方法。
class MyDialog(wx.Dialog): def __init__(self, parent, ID, title): pre = wx.PreDialog() pre.SetExtraStyle(wx.FRAME_EX_CONTEXTHELP) pre.Create(parent, ID, title) self.PostCreate(pre)
如同上面提到过的,两步构造可以让XRC将其构造信息“融入”当前的wx.Dialog 或者 wx.Frame 类对象的构造中(译者:也就是说使用XRC的内容构造当前对象,而不是生成新的对象)。下面这个例子就是如何使用这个办法:
class MyFrame(wx.Frame): def __init__(self, parent): pre = wx.PreFrame() wx.xrc.XmlResource.Get().LoadOnFrame(pre, parent, "TheFrameXRCName") self.PostCreate(pre)
PostCreate 方法实现了一个“黑魔法”的效果,它将Pre所指向的Frame(或者Dialog)移植入当前的对象中。换句话说,当PostCreate返回的时候,self就如同正常调用了父类的__init__方法一样,Pre会被正确的转移过来。实质上讲,就是让self成为一个C++对象的代理,而这个对象之前的代理是Pre(并且会unhook之前的Pre)。这种办法最初在wxPython2.5中被提到过,可以参照这里 。
XRC提供的另一个特性,就是可以在XML中使用标准的类名。同时,也可以指定一个自定义的子类,用来实例化wx中的标准类。换句话说,加入你期望使用一个继承自wx.TextCtrl的自定义类,但同时,你也期望使用 XRCed 这样的设计工具(虽然XRCed并不知道你的自定义类)。处理这个问题的办法,就是在XRCed中继续使用wxTextCtrl,同时指定你自定义的子类的类名。那么,当XRC在运行时刻动态加载XML的内容时,它会寻找子类的类名并创建实例。当然,在别的方面仍旧像使用 wx.TextCtrl一样来对待你的自定义类对象。
为了能够使用XRC中的自定义子类这个特性,自定义的类必须能够支持两步构造方法。所有的过程和上面所讲解到的完全一样,只是有一点不同,就是Create(译者:也就是两步构造中的第二步)并不是在__init__里面调用的,所以,你在__init__中不能调用任何需要在这个widget对象被完全构造后才能调用的函数。一种解决办法,就是绑定(Bind)wx.EVT_WINDOW_CREATE事件,然后在处理函数中进行其他的初始化工作和函数调用。下面就是一个小例子:
class MySpecialTextCtrl(wx.TextCtrl): def __init__(self): pre = wx.PreTextCtrl() # the Create step is done later by XRC. self.PostCreate(pre) self.Bind(wx.EVT_WINDOW_CREATE, self.OnCreate) def OnCreate(self, event): self.Unbind(wx.EVT_WINDOW_CREATE) # Do all extra initialization here
下面的内容来自mail list中Robin的一些信息,其中包含了更多的细节,甚至是一些内部的处理逻辑。
[两步构造]是一种很典型的在C++中使用的构造方法。由于C++支持函数重载,所以很多UI类都是使用两步构造的方法。第一步不需要任何参数,第二步接收所有wxPython中我们所传入的参数。这种来自C++的默认构造方式中是非常有用的,这是因为,无参数版的构造函数--也就是我们通常所说的C++的默认构造函数--在很多语言的不指定参数的构造过程中会被自动调用,例如:当一个子类被实例化,它需要初始化基类的时候。因此,基本上讲,这种自动的调用能够完成所有的C++部分的类构造(当然也包括基类的构造),并将UI相关的对象构造延迟到第二步构造中。这也就意味着,所有的UI初始化操作都只在第二步构造中进行了仅仅一次,而不是在每个基类对象中都进行。
类的Create函数一般情况下在非默认构造函数(也就是有参数的构造函数)中被调用,因为两步构造的原因,只需要调用一次UI对象的初始化函数(Create),并不需要更多的工作,因为C++已经为我们做好了其余的内容。wxBitmapButton的函数调用过程就像下面这样
call wxBitmapButton(parent, id, bmp)
call wxButton default ctor
call wxControl default ctor
call wxWindow default ctor
and so on...
call wxBitmapButton::Create(parent, id, bmp)
XRC的作者意识到这种创建模型非常的便利,它允许我们将对象的创建过程和UI的初始化过程分离开来。尤其在使用XRC来创建继承自XRC所了解的基类的,但并不了解的子类对象过程中。这种模型本质上讲实现了一种非动态语言的动态使用。
["pre = wx.PreFrame()" 语句]创建了一个wxFrame的C++实例但并不调用实际的UI对象的创建函数,而Pre本身只是一个C++对象的代理。[在LoadOnFrame中]XRC调用了Frame的Create方法,并且XRC的内容中所定义的所有资源都以当前Frame作为父窗体(并且使用相同的创建模型--两步构造)被递归的创建出来。当一切完成之后,Pre已经是一个完全被创建好的wx.Frame对象了。而我们所作的下一步,就是将Pre对象移植入self对象中,这也就是PostCreate方法所作的事情。因为Pre本身就是wx.Frame对象的实例,但是你是使用与之不同的Main_Frame对象,因此很容易将他们融合在一起。
译者:最后一句的意思是,Pre是一个wx.Frame的对象,是一个C++对象的实例。我们利用python的继承wx.Frame所实现的,只是改变了这个C++对象的代理的一些行为模式,但并没有改变Pre所指向的C++本身的对象。所以,我们可以将这个C++的对象很轻松的和一个新的(继承自原来的wx.Frame的)代理对象融合在一起。
一开始,我推荐使用委托(delegation)和转发(forwarding)来将所有的请求和调用转发给XRC中的实例,但事实证明那样很不合适。因为是python,只要你想到,你就能做到。所以我们发现可以很容易的实现实例化Pre为一个wx.Frame对象并将其放置在self中,令self既是wx.Frame对象也是Main_Frame对象的实例。这就是PostCreate所作的事情。
在question from Will Sadkin 和response from Robin Dunn 的讨论中有很多教育意义的信息,但是似乎这两条讨论已经无法访问了。
在Window Layout / XmlResourceSubclass下面,也有XRC子类的一些例子可以参考。
以上~