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即时网络爬虫项目启动说明详解
Feb 23 Python
对numpy中轴与维度的理解
Apr 18 Python
django自带的server 让外网主机访问方法
May 14 Python
python构建基础的爬虫教学
Dec 23 Python
微信小程序python用户认证的实现
Jul 29 Python
Python+Tensorflow+CNN实现车牌识别的示例代码
Oct 11 Python
浅析Django中关于session的使用
Dec 30 Python
Python模块future用法原理详解
Jan 20 Python
使用Python爬虫库requests发送表单数据和JSON数据
Jan 25 Python
django为Form生成的label标签添加class方式
May 20 Python
python操作链表的示例代码
Sep 27 Python
Anaconda详细安装步骤图文教程
Nov 12 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 array_slice函数的使用以及参数详解
2008/08/30 PHP
php 生成短网址原理及代码
2014/01/23 PHP
PHP函数extension_loaded()用法实例
2015/01/19 PHP
php实现在限定区域里自动调整字体大小的类实例
2015/04/02 PHP
PHP批量修改文件名称的方法分析
2017/02/27 PHP
PHP各种常见经典算法总结【排序、查找、翻转等】
2019/08/05 PHP
js AspxButton的客户端操作
2009/06/26 Javascript
js弹窗返回值详解(window.open方式)
2014/01/11 Javascript
javascript调试之DOM断点调试法使用技巧分享
2014/04/15 Javascript
js数组操作常用方法
2014/05/08 Javascript
js选择并转移导航菜单示例代码
2014/08/19 Javascript
jQuery.Callbacks()回调函数队列用法详解
2016/06/14 Javascript
Javascript中函数名.length属性用法分析(对比arguments.length)
2016/09/16 Javascript
JavaScript 实现的checkbox经典实例分享
2016/10/16 Javascript
使用jquery给新生的th绑定hover事件的实例
2017/02/10 Javascript
jquery实现数字输入框
2017/02/22 Javascript
Bootstrap模态对话框中显示动态内容的方法
2018/08/10 Javascript
python数据预处理之将类别数据转换为数值的方法
2017/07/05 Python
Python实现一个简单的验证码程序
2017/11/03 Python
python中copy()与deepcopy()的区别小结
2018/08/03 Python
CentOS下Python3的安装及创建虚拟环境的方法
2018/11/28 Python
python 去除二维数组/二维列表中的重复行方法
2019/01/23 Python
Django 开发环境配置过程详解
2019/07/18 Python
numpy ndarray 取出满足特定条件的某些行实例
2019/12/05 Python
移动端html5 meta标签的神奇功效
2016/01/06 HTML / CSS
应届生财务会计求职信
2013/11/05 职场文书
国贸专业个人求职信范文
2014/01/08 职场文书
大学班长的职责
2014/01/27 职场文书
本科毕业生求职自荐信
2014/04/09 职场文书
大班亲子运动会方案
2014/06/10 职场文书
师德师风的心得体会
2014/09/02 职场文书
学习朴航瑛老师爱岗敬业先进事迹思想汇报
2014/09/17 职场文书
师范生免费教育协议书范本
2014/10/09 职场文书
2014年园林绿化工作总结
2014/12/11 职场文书
旷工检讨书1000字
2015/01/01 职场文书
javascript条件式访问属性和箭头函数介绍
2021/11/17 Javascript