使用xrc的一点心得

使用xrc的一点心得

首先向大家说明,我学python才一个多月,写了一个程序主要是为了练习python和wxPython,如果有什么不对的地方还请大家指出。我也没怎么查是否有人写过类似的说明,就当给大家一个参考。我原来写过c++/gtk/gtkmm的程序,觉得wxPython的图形程序写起来还是很容易的,不过偶得一个同事说我的python程序写 的比较像C++程序,现在正在深入的研究python中。

我要讲的是如何在程序中使用wxPython库,其中包括各种widget的获得,动态的切换widget,也有很多资料的,我就把我使用过程中觉得需要注意的总结一下,其中也说明了整个gui的构建框架

我把程序分成了三个文件
一个 main.py
一个 ui.py
一个 common.py

Main.py 是这样写的

import ui

if __name__ == '__main__':
        application = ui.Application(0)
        application.MainLoop()
else:
        print "Can't import Buildipsl main module,it shoud be run alone!"

也就是在主程序中运行起来图形界面,进入事件循环,比较简单。

Ui.py程序比较长,大概有1000行,就不贴在这了,我主要说明的就是这个部分。

Common.py是一些常用的公共函数,定义的一些存放数据的底层的类,ui界面的数据交互最终都是通过和底层类对象数据交换来实现的,这样层次比较分明,也容易独立修改。

下面看一下ui.py文件中的Application类,也就是main.py中调用的那个类

class Application(wx.App):
        def OnInit(self):
                self.conf = common.HandleConfig('fic.conf')
                self.connection_pool = common.ConnectionPool()
                self.resource = xrc.XmlResource('../resource/IPSLBuild.xrc')
                self.frame = MainFrame(None, self.resource,self.conf,self.connection_pool)
                self.frame.Show()
                return True

这个类在构造函数中实列化了一个’配置文件’类对象,一个’连接缓冲池’类对象,并且初始化了一个xrc资源文件类对象,传入MainFrame这个主窗体类。


MainFrame类 ,主窗体的实现类:
先看一下前几行
class MainFrame(wx.Frame):
        def __init__(self, parent, resource,setting,pool):
                self.res = resource
                self.setting = setting
                self.connection_pool=pool
                self.PostCreate(self.res.LoadFrame(parent, 'MainWindow'))

self.PostCreate(self.res.LoadFrame(parent, 'MainWindow'))
这一行利用xrc资源对象,把在xml文件中定义的窗体load进来,设置本窗体类。

主窗体load进来了,接着就load窗体上的widget
一般的widget都可以使用像下面这样的函数:
self.text_ctrl_build_dir = xrc.XRCCTRL(self,'text_ctrl_build_dir')
也就是使用xrc,XRCCTRL,第一个参数是parent,这里是self,也就是我们load进来的主窗体,后面加的是包含在主窗体里的widget的XML id 值,这样widget就进来了,由前面的self.text_ctrl_build_dir来引用着。使用和用代码构建出来的widget没什么区别。
但是,这里有例外,xrc.XRCCTRL只能使用在由wxWindow继承出来的widget上,如果widget不是由wxWindow 继承而来的,就获得不到对象引用,比如menu 和 toolbar。Menuitem和toolbar button要使用下面的方式:

Menubar:
self.menubar = self.res.LoadMenuBar('MainWindow_MenuBar')
这里的menubar在XML文件中是个顶层的widget,也就是说这个menubar不是包含在主窗体里的,结构上是和主窗体平级的

一个小提示:
XML文件中顶层的widget都是用LoadXXXX  来获得,非顶层的才使用XRCCTRL,并且XRCCTRL中第一个参数要是以LoadXXXX获得的widget做为直接或间接的父widget.
比如一个panel,如果在XML文件的最顶层,就要使用LoadPanel,如果在一个Frame中,那就要使用XRCCTRL(Frame的引用,’panel xml id’)

Menubar获得了,就把menubar设置在主窗体上
self.SetMenuBar(self.menubar)

获得子菜单用
self.menu_add =  self.menubar.FindItemById(xrc.XRCID('menu_item_node_add'))

toolbar可以在主窗体上获得
self.toolbar = xrc.XRCCTRL(self,'MainWdinow_ToolBar')

在使用上,我获得toolbar button主要是要改变其可用和不可用的状态:
self.toolbar.EnableTool(xrc.XRCID("tool_button_node_add"),True)
想要toolbar button不能使用就用False
回调函数我下面再说.

到现在获得widget大家应该都没什么疑惑了吧,再说回调函数
回调函数一般是这样
self.config_panel.Bind(wx.EVT_BUTTON,self.OnConfSearch,id=xrc.XRCID('button_search_node_conf'))

self.Bind(wx.EVT_MENU, self.OnAdd,id=xrc.XRCID('menu_item_node_add'))
self.Bind(wx.EVT_TOOL, self.OnAdd,id=xrc.XRCID('tool_button_node_add'))

第一个是正常的Buttton
第二个是menuitem
第三个是toolbar button
其实都是一样的
父widget.Bind(事件类型,函数名,xml id)
再次说一下上面的menuitem 和toolbar button,其实如果不要像我这样要设置他们的可用和不可用的状态,不用获得他们的对象引用,可以直接绑定的,因为他们用的是xml id来查找的,没有bind在他们对象的引用上.

下面说一个我使用中的需求:
我窗体左边有一个treeview,需要在点左边不同的treeview item的时候,右边可以根据不同的情况变化.
我不想给右边通过代码来实现,因为可能有如下问题
1.        右边可能是个比较复杂的panel,代码量比较大,而且手写代码不容易后来的修改
2.        每次切换都要destory和Create一堆东西,资源的占用比较大
我喜欢完全的界面代码分离,好处多多,所以还是想用xrc的方式来做.
下面是实现步骤

右面我们要操作的实际上是一个splitter window的panel,叫splitter_right_panel

定义一个BoxSizer,用来向里面添东西
self.splitter_right_box_for_guest_panel=wx.BoxSizer(wx.VERTICAL)

初始化的时候先放上一个空panel
self.splitter_right_box_for_guest_panel.Add(wx.Panel(self),1, wx.EXPAND, 0)

设置布局
self.splitter_right_panel.SetAutoLayout(True)

设置容器
self.splitter_right_panel.SetSizer(self.splitter_right_box_for_guest_panel)

调整widget间的布局关系
self.splitter_right_box_for_guest_panel.Fit(self.splitter_right_panel)

把两个要加的panel都变成右边splitter_right_panel的子widget
self.config_panel.Reparent(self.splitter_right_panel)
self.connection_panel.Reparent(self.splitter_right_panel
这一步比较重要,一定要让panel的父widget是他的直接panel,如果是主窗体,显示有问题.
还有这两个panel是xml文件中顶层的panel,要使用LoadPanel加载进来.

初始化基本上就完成了,在切换中的代码是写在treeview中的select changed回调函数中.

self.main_frame.splitter_right_box_for_guest_panel.Detach(0)
self.main_frame.config_panel.Hide()
self.main_frame.connection_panel.Hide()

if ……:
        self.main_frame.config_panel.Show()
        self.main_frame.splitter_right_box_for_guest_panel.Add(self.main_frame.config_panel,1,wx.EXPAND,0)
else:
        self.main_frame.connection_panel.Show()
self.main_frame.splitter_right_box_for_guest_panel.Add(self.main_frame.connection_panel,1,wx.EXPAND,0)


1.        任何时候都先把BoxSizer中的panel分离
2.        两个面板都hide
3.        根据判断加载不同的panel,show出来
这里用到的是Detach(),不要用Destory,panel对象是在xrc 资源文件中的,destory有问题,而且我们就是不想让它被destory,这样最开始得到的panel内部的widget的对象的引用一直都是可用的.

基本的原理我就说完了,写了两个common文件的类在这,是实现ssh连接的,我测试是可用的,还没写好, python异常我还没看,不好意思
使用的是paramiko模块,不过我用起来发现有问题,put和get函数有问题,没办法我自己写的put和get.
远程执行命令的时候不等执行完就退出了,命令在后台执行,这个不是我想要的,不过肯定有方法,只是我还不知道.我还没写完,让大家见笑了,呵呵

class Connection:
        def __init__(self,ip,account,pwd):
                self.trans = paramiko.Transport((ip, 22))
                self.trans.connect(username=account, password=pwd)
        def put(self,localfile,remotefile):
                sftp=paramiko.SFTPClient.from_transport(self.trans)
                data = open(localfile, 'r').read()
                print 'remotefile is %s' % remotefile
                sftp.open(remotefile, 'w').write(data)
        def get(self,remotefile,localfile):
                sftp=paramiko.SFTPClient.from_transport(self.trans)
                data = sftp.open(remotefile, 'r').read()
                open(localfile, 'w').write(data)
        def execmd(self,cmd):
                chan = self.trans.open_session()
                chan.exec_command(cmd)
        def active(self):
                return self.trans.is_active()
        def __del__(self):
                self.trans.close()

class ConnectionPool:
        def __init__(self):
                self.pool={}
                self.filelist=['bg','pnp','sub','fp','ep']
                self.genscript='fic.pexpect'
        def add_connection(self,name,ip,account,pwd):
                if self.pool.has_key(name):
                        return False
                try:
                        con=Connection(ip,account,pwd)
                except Exception:
                        return False
                self.pool[name]=con
                return True
        def del_connection(self,name):
                if not self.pool.has_key(name):
                        return False
                else:
                        del self.pool[name]
        def connected(self,name):
                if self.pool.has_key(name):
                        return True
                else:
                        return False
        def gen_config(self,name,path):
                if not self.pool.has_key(name):
                        return False
                try:
                        self.pool[name].execmd(r'rm -rf /tmp/%s' % self.genscript)
                        self.pool[name].put(get_path(self.genscript),'/tmp/%s' % self.genscript)
                        self.pool[name].execmd('python /tmp/%s' % self.genscript)
                        self.pool[name].execmd(r'rm -rf /tmp/%s' % self.genscript)
                        time.sleep(600)
                        for i in self.filelist:
                                print os.path.join(path,i)
                                self.pool[name].get("/tmp/fic/%s" % i,os.path.join(path,i))
                except:
                        return False
                return True
先顶后看!
大家觉得有点用就顶一下,谢谢
多谢楼上的兄弟
不错。
多谢limodou兄,我从你那也得到很多的帮助
不过大家好像对我写的不是很关心阿,那下次还是不写了
倒也不是,因为只有大家有相同的经验才便于交流。可能你的方式大家用得不多,所以也说不出什么。用得人多才讨论得起来。许多东西都是这样。你可以做为一个先行者,方便后学之人。
哦,呵呵,好的,可能大家如果写wxpython的程序的时候才能用的到吧,我觉得是我看到得应该注意得都写出来了,大家如果写得时候可以参考一下, 不过python版确实是没有shell 版热闹啊。
如果你订阅了中国python邮件列表,就知道是不是热闹了。目前每天的邮件大概在50封左右。
嗯,好得,我先再看几本书,深入一下,limodou兄有没有什么公共得项目做得,开源的,可以加我一个,想再练练手,或许也能有点贡献。
我个人在做ulipad,是基于wxPython。不过目前是一个人在做,不太会轻易加人,不过如果你有东西适合加到Ulipad中,我会很乐意了。不过加入开源项目并不一定是件容易的事,因为一般来说,没有对项目有一定的贡献是不会轻易让人加入的。所以需要提高参与度。