一、概念
XRC(XML Resource)的设计来源于wxWidgets,它的想法很简单,就是将界面设计的工作从程序中独立出来。具体的做法是,创建单独的XML文件,负责界面设计,程序运行的时候载入,生成界面。这样做的好处是显而易见的。首先,将繁琐的外观设计代码从程序中去掉,程序更清晰易读。其次,XRC文件独立于程序,程序运行时才调用,因此可以随意更换外观。这种思想并不是wxWidgets的原创,MFC中的RC已经有了,类似的还有HTML和CSS的关系。wxPython从wxWidgets继承而来,当然也保留了XRC。
这里有几点要补充的。一是wxPython的XRC文件中用到的类名称仍然是wxWidgets中的类名称,换句话说,wxPython和wxWidgets可以共用XRC文件,第二点要补充的是XRC文件可以编译成二进制文件XRS,或者编译成C++代码。
二、例子
先来看一个例子。
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, title='My Frame')
panel = wx.Panel(self)
label1 = wx.StaticText(panel, wx.ID_ANY, 'First name:')
label2 = wx.StaticText(panel, wx.ID_ANY, 'Last name:')
self.text1 = wx.TextCtrl(panel, wx.ID_ANY)
self.text2 = wx.TextCtrl(panel, wx.ID_ANY)
button = wx.Button(panel, wx.ID_ANY, 'Submit')
sizer = wx.FlexGridSizer(rows=2, cols=2, vgap=5, hgap=5)
self.Bind(wx.EVT_BUTTON, self.OnSubmit, button)
sizer.Add(label1)
sizer.Add(self.text1)
sizer.Add(label2)
sizer.Add(self.text2)
sizer.Add((0,0)) #filler for the grid cell
sizer.Add(button)
panel.SetSizer(sizer)
sizer.Fit(self)
def OnSubmit(self, evt):
wx.MessageBox('Your name is %s %s!' %
(self.text1.GetValue(), self.text2.GetValue()), 'Feedback')
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame()
self.SetTopWindow(frame)
frame.Show()
return True
if __name__ == '__main__':
app = MyApp(False)
app.MainLoop()
这是一个简单的wxPython程序。可以看到上面的代码中,除了Bind和OnSubmit,其他的代码都是和界面设计有关的,这些代码或者类似的代码出现于几乎所有的GUI程序中。下面是用XRC重新实现的代码。
import wx
from wx import xrc
class MyApp(wx.App):
def OnInit(self):
self.res = xrc.XmlResource('gui.xrc')
self.init_frame()
return True
def init_frame(self):
self.frame = self.res.LoadFrame(None, 'mainFrame')
self.panel = xrc.XRCCTRL(self.frame, 'panel')
self.text1 = xrc.XRCCTRL(self.panel, 'text1')
self.text2 = xrc.XRCCTRL(self.panel, 'text2')
self.frame.Bind(wx.EVT_BUTTON, self.OnSubmit, id=xrc.XRCID('button'))
self.frame.Show()
def OnSubmit(self, evt):
wx.MessageBox('Your name is %s %s!' %
(self.text1.GetValue(), self.text2.GetValue()), 'Feedback')
if __name__ == '__main__':
app = MyApp(False)
app.MainLoop()
看起来是不是比上面的清晰多了,当然,程序要跑起来,还需要下面的部分。下面的代码属于XRC文件。
下面的代码属于XRC文件。
<?xml version="1.0" encoding="utf-8"?>
<resource>
<object name="mainFrame">
<title>My Frame</title>
<object name="panel">
<object
<cols>2</cols>
<rows>3</rows>
<vgap>5</vgap>
<hgap>5</hgap>
<object
<object name="label1">
<label>First name:</label>
</object>
</object>
<object
<object name="text1"/>
</object>
<object
<object name="label2">
<label>Last name:</label>
</object>
</object>
<object
<object name="text2"/>
</object>
<object
<size>0,0</size>
</object>
<object
<object name="button">
<label>Submit</label>
</object>
</object>
</object>
</object>
</object>
</resource>
这段代码看起来很复杂,但是如果熟悉XML的话,应该很容易看明白它的结构。最关键的是,我们不用亲手写这些代码,很多工具,像XRCed,wxGlade都可以自动生成这些代码,你所要做的只是点几下鼠标。
三、创建XRC文件
虽然我们不用亲自写XRC文件,弄清楚它的原理还是必要的。在wxPython中,Button的构造函数是这样的。
wx.Button( parent, id, label='', pos=wx.DefaultPosition, size=wx.DefaultSize,
size=wx.DefaultSize, style=0, validator=wx.DefaultValidator, name='button')
实际使用的时候,通常没有这么多参数,
button = wx.Button(panel, wx.ID_ANY, 'Submit')
但是在XRC文件中,要创建一个Button,通常用下面的方式,
<object name="button">
<label>Submit</label>
</object>
从上面这几行代码中,我们可以得到如下信息:
1. XRC用<object> </object>表示要创建的对象。
2. XRC用所用的是C++的类名wxButton,而不是wxPython的类名wx.Button。
3. XRC用name表示对象的名称,对应于python代码中的id。
4. XRC用层次关系表示对象之间的父子关系。
下面这段代码显示了XRC文件的层次关系。
<?xml version="1.0" encoding="utf-8"?>
<resource>
<object name="mainFrame">
<title>Test Frame</title>
<object name="panel">
<object name="button">
<label>Submit</label>
</object>
</object>
</object>
</resource>
注意,所有的结构都包含在<resource> </resource>中,表示这个文件是XML Resource文件。
到目前为止,我们对XRC文件的结构已经有了初步的认识,接下来要了解的是在Python程序中如何用到它们。
四、使用XRC文件
如何使用XRC文件是wxPython+XRC框架的关键一步。在前面的代码中,我们注意到这样两行,
import wx
from wx import xrc
这里要强调的是xrc模块必须单独被导入,所以,第二行是必须的。
class MyApp(wx.App):
def OnInit(self):
self.res = xrc.XmlResource('gui.xrc')
self.init_frame()
return True
在上面这几行代码中,在上面的代码中,Self.res存储xrc文件的内容。xrc.XmlResource是导入XRC文件,创建xrc对象的关键。
self.init_frame()是用户自己定义的函数。你不一定要定义self.init_frame()函数,这样做的好处只是把初始化框架的工作单独放在一起,看起来清晰。你也可以在OnInit函数中完成所有Frame的初始化工作。下面我们来看init_frame()函数中做了什么,
self.frame = self.res.LoadFrame(None, 'mainFrame')
LoadFrame()函数返回mainFrame的引用,将顶层Frame对象载入到python程序中,第一个参数是parent,第二个参数是ID,即XRC文件中为Frame取的名字。这个函数还初始化所有的children Frame,这些工作并没有显示的表现出来,由XRC自动完成。
当你想从XRC文件中提取信息时,有两种方式可以选择,一种是XRCID(XRC_name),这个函数可以获得对象的ID;另一种是XRCCTRL(XRC_name),可以获得对象的引用。
self.panel = xrc.XRCCTRL(self.frame, 'panel')
self.text1 = xrc.XRCCTRL(self.panel, 'text1')
self.text2 = xrc.XRCCTRL(self.panel, 'text2')
对XRCCTRL需要多说两句,XRCCTRL函数返回对象的引用。但是并没有create新对象,仅仅是获取他们的引用,创建对象的工作已经在上面的LoadFrame()过程中完成了。另一个需要强调的是,调用XRCCTRL时,不一定提供直接的parent,可以使用更上一层的parent,XRCCTRL会用FindWindowById递归的往下找。也就是说,如果你迷路了,报上爷爷的名字,警察叔叔也能把你送到家。
到目前为止,构造界面的工作算是完成了。如果你熟悉GUI编程的话,马上会想到,接下来的工作应该是将Gui对象,事件和事件处理函数联系起来。这就是接下来要讲的事件绑定。
五、事件绑定
先看一段代码,
self.frame.Bind( wx.EVT_BUTTON, self.OnSubmit, id=xrc.XRCID('button') )
这是一个简单的绑定的例子,将按钮事件,事件函数和按钮ID绑定起来。前两个参数不用多解释,注意第三个参数中的XRCID函数,这个函数通过对象的名称ID返回对象的数字ID。这个数字ID是你在创建对象的时候wxPython用wx.NewID()生成的。
讲到这里有必要了解一下Bind()函数,下面是Bind()函数的定义,
Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)
最后一个参数我们忽略不计。第四个参数就是我们刚才的例子中的第三个参数,你也许会奇怪,为什么跳过了默认的第三个参数直接读取第四个参数,程序还能正常?在这种情形下,它的确就是正常。看了Bind()函数的定义之后,你也许还能想出另一种调用方法,像下面这样,
self.button = xrc.XRCCTRL(self.panel, 'button')
self.frame.Bind(wx.EVT_BUTTON, self.OnSubmit, self.button)
这当然也是正确的。但我们通常都用前一种方法。为什么呢?后一种不是更直观吗?真正的原因是,XRCCTRL只能返回wxWindow的继承类的引用,也就是有GetID方法的类。但很多情况下,我们要绑定的对象并不是wxWindow的继承类,像我们经常用到的Bind是将event handler和menu绑定起来,wxMenuItem类不是由wxWindow继承而来,因此,用XRCCTRL得不到它的引用,这种情况下,必须用XRCID来获得对象的ID,并将ID传给Bind作为参数。所以,为了简单,为了统一,我们就选择ID来作为Bind的参数。
关于利用wxPython+XRC的框架进行GUI编程,到这里就讲完了,这些东西是我从网上收集来的,半翻译半笔记最后写成现在这样。主要参考了下面这篇文章
http://wiki.wxpython.org/XRCTutorial
希望能对也为这个内容困惑的朋友有帮助。