Windows中使用wxPython和py2exe开发Python的GUI程序的实例教程


Posted in Python onJuly 11, 2016

Python是支持可视化编程,即编写gui程序,你可以用它来编写自己喜欢的桌面程序。使用wxPython来做界面非常的简单,只是不能像C#一样拖动控件,需要自行写代码布局。在完成编写之后,由于直接的py文件不能再没有安装python的电脑上运行,能否有一个打包成在任意电脑都能运行的工具,网上找找发现了py2exe正好可以完成这个功能。wxPython和py2exe都是开源免费软件。

环境配置
wxPython: sourceforge项目页https://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/
下载后双击安装即可,安装程序会自动安装到对应python\Scripts下。
py2exe:官方下载主页https://www.wxpython.org/download.php
同样双击即可安装,注意下载要对应使用的Python版本。
下面分别示例说明wxPython和py2exe的简单使用。

基本示例
文件名:wxTest.py:

# -*- coding: cp936 -*-
'''MainWindow类完成最简单的编辑功能,添加一个主菜单,两个子菜单(about和exit)'''
import wx
 
class MainWindow(wx.Frame):
 '''定义一个窗口类'''
 def __init__(self, parent, title):
  wx.Frame.__init__(self, parent, title=title, size=(300, 300))
  self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
 
  self.setupMenuBar()
  self.Show(True)
 
 def setupMenuBar(self):
  self.CreateStatusBar()
 
  menubar = wx.MenuBar()
  menufile = wx.Menu()
 
  mnuabout = menufile.Append(wx.ID_ABOUT, '&About', 'about this shit')
  mnuexit = menufile.Append(wx.ID_EXIT, 'E&xit', 'end program')
 
  menubar.Append(menufile, '&File')
 
  #事件绑定
  self.Bind(wx.EVT_MENU, self.onAbout, mnuabout)
  self.Bind(wx.EVT_MENU, self.onExit, mnuexit)
   
  self.SetMenuBar(menubar)
 
 def onAbout(self, evt):
   '''点击about的事件响应'''
   dlg = wx.MessageDialog(self, 'This app is a simple text editor', 'About my app', wx.OK)
   dlg.ShowModal()
   dlg.Destroy()
 
 def onExit(self, evt):
   '''点击退出'''
   self.Close(True)
app = wx.App(False)
frame = MainWindow(None, 'Small Editor')
app.MainLoop() #循环监听事件

编辑好改文件后,使用py2exe将Python脚本编译成Windows可执行文件,这样就不需要Python解释器了。要使用py2exe,首先要编写一个编译脚本,然后通过Python运行编译脚本即可将其他的脚本编译成可执行文件。以下实例是将要编译成可执行文件的脚本,文件名:setup.py

import distutils
import py2exe
distutils.core.setup(windows=['wxTest.py'])

在setup.py中除了导入必需的模块以外,只有一条语句:

distutils.core.setup(windows=['wxTest.py'])

方括号中就是要编译的脚本名,前边的windows 表示将其编译成GUI程序。如果要编译命令行界面的可执行文件,只要将windows改为console,如果需要将脚本编译成Windows服务,则可以使用service选项。
都编辑好之后,将wxTest.py和setup.py放在同一个路径下,cmd进入该路径,输入:

setup.py py2exe
如果在运行时报以下错误:
error: MSVCP90.dll: No such file or directory
是因为没有找到MSVCP90.dll,在windows目录下搜索MSVCP90.dll这个文件,然后拷到python安装目录的DLLs下就可以了。
当打包PyQt项目时,可能会报以下错误
ImportError: No module named sip
这时只需要在打包时加上--includes sip就行啦,如:
setup.py py2exe --includes sip

运行结束之后,会在路径下生成dist和 build两个目录。其中dist目录中就是编译生成的文件。如果要在其他未安装Python的机器上运行编译好的程序,只要将dist目录复制到其他机器上即可。双击运行wxTest.exe,如图:

Windows中使用wxPython和py2exe开发Python的GUI程序的实例教程

使用wxPython建立一个计算文件md5的GUI工具
小工具最终是下面这个样子,将文件拖到上面会自动计算其md5与size

Windows中使用wxPython和py2exe开发Python的GUI程序的实例教程

下面是全部的代码

#coding:gbk
import wx
import optparse
import time,hashlib
import threading
import os

def checkMD5(pefile):
  try:
    f = open(pefile,'rb')
    data = f.read()
    m = hashlib.md5()
    m.update(data)
    f.close()
    return m.hexdigest()
  except:
    return 'error'
  
def getFileSize(filename):
  try:
    size = int(os.path.getsize(filename))
    return size
  except:
    return 'error'
   
#线程函数
class FuncThread(threading.Thread):
  def __init__(self, func, *params, **paramMap):
    threading.Thread.__init__(self)
    self.func = func
    self.params = params
    self.paramMap = paramMap
    self.rst = None
    self.finished = False

  def run(self):
    self.rst = self.func(*self.params, **self.paramMap)
    self.finished = True

  def getResult(self):
    return self.rst

  def isFinished(self):
    return self.finished

def doInThread(func, *params, **paramMap):
  t_setDaemon = None
  if 't_setDaemon' in paramMap:
    t_setDaemon = paramMap['t_setDaemon']
    del paramMap['t_setDaemon']
  ft = FuncThread(func, *params, **paramMap)
  if t_setDaemon != None:
    ft.setDaemon(t_setDaemon)
  ft.start()
  return ft

class FileDropTarget(wx.FileDropTarget):
  def __init__(self, filetext,md5tx,filesizetx):
    wx.FileDropTarget.__init__(self)
    self.filepath = filetext
    self.md5tx = md5tx
    self.filesizetx = filesizetx
   
  def OnDropFiles(self, x, y, fileNames):
    filename = fileNames[0].encode('gbk')
    print filename
    print type(filename)
    self.filepath.SetValue(filename)
    md5 = doInThread(checkMD5,filename)
    filesize = doInThread(getFileSize,filename)
    while True:
      if not md5.isFinished():
        time.sleep(0.5)
      else:
        self.md5tx.SetValue(md5.getResult())
        break
        
    while True:
      if not filesize.isFinished():
        time.sleep(0.5)
      else:
        self.filesizetx.SetValue(str(filesize.getResult()))
        break

class Frame(wx.Frame): #Frame 进行初始化
  def __init__(self,title):
    wx.Frame.__init__(self,None,title=title,size = (400,300))
    boxSizer = wx.BoxSizer(wx.VERTICAL)
    
    self.panel = wx.Panel(self)
    
    # boxSizer.Add(self.panel,1,wx.EXPAND|wx.ALL) #wx.ALL 周围的距离,EXPAND扩充到全部
    
    filepath = wx.StaticText(self.panel,-1,"FileDir(请将文件拖到本对话框中)")
    filetext = wx.TextCtrl(self.panel,-1,"",size=(350,20))
    
    md5st = wx.StaticText(self.panel,-1,"MD5")
    md5tx = wx.TextCtrl(self.panel,-1,size=(250,20))
    
    filesizest = wx.StaticText(self.panel,-1,'FileSize')
    filesizetx = wx.TextCtrl(self.panel,-1,size=(250,20))
    
    # hashst = wx.StaticText(self.panel,-1,'Hash')
    # hashtx = wx.TextCtrl(self.panel,-1,size=(250,20))
    
    boxSizer.Add(filepath,0,wx.EXPAND|wx.LEFT|wx.TOP,border=10)
    boxSizer.Add(filetext,0,wx.LEFT|wx.TOP,border=10)
    boxSizer.Add((-1,20))
    boxSizer.Add(md5st,0,wx.LEFT|wx.TOP,border=10)
    boxSizer.Add(md5tx,0,wx.LEFT|wx.TOP,border=10)
    boxSizer.Add((-1,10))
    boxSizer.Add(filesizest,0,wx.LEFT|wx.TOP,border=10)
    boxSizer.Add(filesizetx,0,wx.LEFT|wx.TOP,border=10)
    # boxSizer.Add((-1,10))
    # boxSizer.Add(hashst,0,wx.LEFT|wx.TOP,border=10)
    # boxSizer.Add(hashtx,0,wx.LEFT|wx.TOP,border=10)
    
    dropTarget = FileDropTarget(filetext,md5tx,filesizetx)
    self.panel.SetDropTarget( dropTarget )
    
    self.panel.SetSizer(boxSizer)    
 
class App(wx.App): ##继承wx.App
  def OnInit(self): ##还没有调起来的时候读取初始化
    self.frame = Frame('MD5&size信息')    
    self.frame.Centre()
    self.frame.Show(True)    
    return True

def killSelf(evt = None):
  os.system('taskkill /F /T /PID %d >NUL 2>NUL' % win32process.GetCurrentProcessId())

if __name__ == '__main__':
  parser = optparse.OptionParser()
  parser.add_option('-x', '--no-update', dest = 'test', action = 'store_true', help = 'start without update')
  parser.add_option('-t', '--no-update-test', dest = 'test2', action = 'store_true', help = 'start without update debug')
  options, args = parser.parse_args()
  if options.test:
    print("-x param")
  if options.test2:
    print("-t param")
  App(redirect = False).MainLoop()

一点点的解释:

class App与App().MainLoop()是固定写法,在class App下有一个def OnInit方法来初始化主的Frame,将其居中并且Show()出来,没什么好说的,主要看一下Frame的定义

这个小工具使用的是boxSizer来布局,为了简单我只使用了一个boxSizer,将里面的所有控件采用VERTICAL(垂直)的方式来布局,如果想要将MD5与后面的文本框放在同一行,那么就需要添加一个水平的boxSizer,然后那将这个水平的boxSizer再放入主的boxSizer

boxSizer = wx.BoxSizer(wx.VERTICAL) #初始化一个垂直的boxSizer,也是整个框架的主Sizer

self.panel = wx.Panel(self) #初始化一个panel,这个panel是放了放之后的控件的

filepath = wx.StaticText(self.panel,-1,"FileDir(请将文件拖到本对话框中)") 
filetext = wx.TextCtrl(self.panel,-1,"",size=(350,20)) 
md5st = wx.StaticText(self.panel,-1,"MD5") 
md5tx = wx.TextCtrl(self.panel,-1,size=(250,20)) 
filesizest = wx.StaticText(self.panel,-1,'FileSize') 
filesizetx = wx.TextCtrl(self.panel,-1,size=(250,20))

上面是初始化相应的静态文本与文本框,方法中的第一个参数是其所在的父类窗口,这里也就是self.panel,其实也可以不用panel,而是将其直接放入到boxSizer中
boxSizer.Add(filepath,0,wx.EXPAND|wx.LEFT|wx.TOP,border=10)

将filepath加入到主的boxSizer中,这里一开始我有一些困惑,一开始我一直以为先将所有的控件放入到panel中,然后再将panel放入到boxSizer中,但是这样是不对的,而应该是直接就入到boxSizer中,将该控件的父类设置为panel,之后就没有将panel放入boxSizer这一步操作,wx.LEFT|wx.TOP,border=10 这个参数表示的是该控件距离上来左各有10个像素的距离,再使用wx.EXPAND来使其充分的填充其所在的区域,我曾经想,可否设置成距离上10px,左20px,但是貌似不能这样设置,Add函数里只能有一个border参数,换句话说只能设置相同的数值,之后我再找找是否可以实现。

boxSizer.Add((-1,20)) #这个是添加一个空距离,距离上20px

dropTarget = FileDropTarget(filetext,md5tx,filesizetx) 
self.panel.SetDropTarget( dropTarget )

这个是放该窗口类添加一个拖拽方法,也是比较固定的写法

上面的class FileDropTarget中的__init__与OnDropFiles方法也是固定的方法,只是里面的处理函数不同。

wxPython中的一些style与flag等参数在布局中使用需要一些经验,还有它的很多控件和与之绑定的方法,要想熟练掌握还需要下一些工夫,下面两个网站算是介绍比较详细,要多多查阅

Python 相关文章推荐
用Python程序抓取网页的HTML信息的一个小实例
May 02 Python
Python数据结构之翻转链表
Feb 25 Python
对django 模型 unique together的示例讲解
Aug 06 Python
Python: 传递列表副本方式
Dec 19 Python
Pytorch之contiguous的用法
Dec 31 Python
给 TensorFlow 变量进行赋值的方式
Feb 10 Python
Python sql注入 过滤字符串的非法字符实例
Apr 03 Python
Python爬虫基于lxml解决数据编码乱码问题
Jul 31 Python
浅析Python 序列化与反序列化
Aug 05 Python
Python try except finally资源回收的实现
Jan 25 Python
Python查找算法的实现 (线性、二分,分块、插值查找算法)
Apr 24 Python
python库Tsmoothie模块数据平滑化异常点抓取
Jun 10 Python
Python的requests网络编程包使用教程
Jul 11 #Python
Python的SQLalchemy模块连接与操作MySQL的基础示例
Jul 11 #Python
Python中的异常处理相关语句基础学习笔记
Jul 11 #Python
Python编写简单的HTML页面合并脚本
Jul 11 #Python
Python中super()函数简介及用法分享
Jul 11 #Python
Swift中的协议(protocol)学习教程
Jul 08 #Python
Python中多线程的创建及基本调用方法
Jul 08 #Python
You might like
PHP用GD库生成高质量的缩略图片
2011/03/09 PHP
优化WordPress的Google字体以加速国内服务器上的运行
2015/11/24 PHP
php实现简单的权限管理的示例代码
2017/08/25 PHP
PHP设计模式(一)工厂模式Factory实例详解【创建型】
2020/05/02 PHP
php中yii框架实例用法
2020/12/22 PHP
JavaScript 继承详解(三)
2009/07/13 Javascript
Jquery下判断Id是否存在的代码
2011/01/06 Javascript
javascript对数组的常用操作代码 数组方法总汇
2011/01/27 Javascript
解决extjs grid 不随窗口大小自适应的改变问题
2014/01/26 Javascript
jQuery实现返回顶部效果的方法
2015/05/29 Javascript
jQuery时间轴插件使用详解
2015/07/16 Javascript
Bootstrap每天必学之按钮
2015/11/26 Javascript
javascript实现计时器的简单方法
2016/02/21 Javascript
第七篇Bootstrap表单布局实例代码详解(三种表单布局)
2016/06/21 Javascript
jQuery之动画效果大全
2016/11/09 Javascript
Bootstrap基本布局实现方法详解
2016/11/25 Javascript
JS函数多个参数默认值指定方法分析
2016/11/28 Javascript
Bootstrap基本插件学习笔记之按钮(21)
2016/12/08 Javascript
微信小程序封装自定义弹窗的实现代码
2019/05/08 Javascript
jQuery实现验证用户登录
2019/12/10 jQuery
[01:07:02]DOTA2-DPC中国联赛 正赛 iG vs PSG.LGD BO3 第三场 2月26日
2021/03/11 DOTA
Python中浅拷贝copy与深拷贝deepcopy的简单理解
2018/10/26 Python
Python不支持 i ++ 语法的原因解析
2020/07/22 Python
python使用建议技巧分享(三)
2020/08/18 Python
Python gevent协程切换实现详解
2020/09/14 Python
Python使用xpath实现图片爬取
2020/09/16 Python
MAC Cosmetics巴西官方网站:M·A·C彩妆
2019/04/18 全球购物
美国球迷装备的第一来源:FOCO
2020/07/03 全球购物
电气工程和自动化自荐信范文
2013/12/25 职场文书
高中军训感想800字
2014/02/23 职场文书
2014年民主评议党员个人总结
2014/09/24 职场文书
背起爸爸上学观后感
2015/06/08 职场文书
春季运动会加油词
2015/07/18 职场文书
初二物理教学反思
2016/02/19 职场文书
经典人生语录分享:不畏将来,不念过去,笑对当下
2019/12/12 职场文书
python实现学员管理系统(面向对象版)
2022/06/05 Python