作者:风叔


今天和实验室的Juan同学聊起了GUI编程的问题。这家伙最近搞了下C#,并写了个简单的处理GREET软件输入输出的小App。对于我们这种常年接触命令行的家伙,好容易见到个窗口真是分外眼红。接着我忽然就想到了Python做GUI编程的问题,以前一直想搞搞这方面的东西,被这么一提醒,就花了一晚上做了下尝试。

(1)介绍

Python的好处是方便易写,我个人认为,使用Python是拿运行效率换程序猿时间,如果运行效率比程序员时间更重要的话,自然可以考虑用C或者别的语言来编写程序(通常这用来写核心代码)。GUI是程序中属于不怎么对运行效率敏感的部分,因此用拓展性强的Python来写的确很合适。另外,由于目前做的东西的限制,我需要用到许多大量的第三方Python绘图库,而用Python写界面的话能直接把这些第三方库的功能嵌入软件中,这的确是非常有吸引力。

(按我朋友的话来讲,有种太监带着假JB的感觉)。 Python作为通用编程语言,再这方面自然比Matlab要强一些。我粗略查了一下,目前有许多第三方的库都能方便程序员构建窗口。

我大致进行了搜索,希望能找到第三方库拥有一下四个特质:

a.  功能强大且丰富的组件库;


b. 能够实现交互式界面设计(支持控件拖拽);

c. 与py2exe兼容,能直接编译成exe文件;


d. 完全免费(包括商业应用)。

最终找到了PySide和Qt Designer这个组合,经过试用,可以完美实现我的要求。

(2)安装

PySide是由诺基亚公司开发的,简单来说就是其Qt库的Python移植(原先是C++的)。诺基亚还有跨平台的开发工具Qt,完全都是免费使用,在这里不得不赞一下诺基亚。PySide的主要对手是PyQt。PyQt出现得比PySide早,但是PySide血统更纯正一些(毕竟是亲儿子)。但是具体应用两者差不多,因为目前Qt的IDE也没有把Python弄进去,要实现同样功能两者要进行的操作大同小异。PyQt个人应用免费,但是商业应用要收钱,相比下PySide的个人和商业双免费的策略更让人放心一些。

PySide的安装可以直接到官网上下载,这里我就不废话了,有几件事情需要注意:

首先是Pyside不支持Python 2.7 一下版本,为此我终于咬牙从2.5升级了,希望其他库不要出现问题,版本问题真是Python最让人恼火的地方;

其次是PySide似乎还对Python一个组件存在依赖性,但是Python 2.7并没有默认安装这个组件,如果爆出缺少pkg资源一类的错误,需要到官网进行下载,链接: https://pypi.python.org/pypi/setuptools/0.6c11#downloads

另外,为了方便应用,建议安装完成后把python目录下的script文件夹加到系统的Path中。

现在Qt Designer直接集成到了PySide中了,所以不用去Qt官网下载Qt软件了。500M的东西啊,我坑爹地下完了才发现根本不用下,几年前的教程害死人。Qt Designer在PySide包的文件夹中,具体可以到Python27Libsite-packages中去看,名字叫Designer.exe。

(3)PySide使用

PySide的使用非常方便,下面是一个简单是示例,用纯命令行建立一个标题为Test Form的窗口:

# TestPySide.py
import sys

from PySide.QtCore import *
from PySide.QtGui import *

# Define Form
class Form(QDialog):
     def __init__(self,parent=None):
         super(Form,self).__init__(parent)
         self.setWindowTitle('Test Form') 

# Main function
if __name__=='__main__':

     # Create Qt App
     app=QApplication(sys.argv)

     # Create window
     NewForm=Form()
     NewForm.show()

     # exit
     sys.exit(app.exec_())



 



程序先是导入Qt的库,接着继承QDialog类,建立了一个新类Form,标题为“Test Form”,接着在主程序中先是新建app,然后建立NewForm对象,并显示这个对象。



 



写完后直接运行结果如下:



 



[转载]Python <wbr>GUI <wbr>编程:PySide、Qt <wbr>Designer <wbr>和 <wbr>py




更具体的示例要自己去参考官方文档了。



 



(4)使用Qt Designer来实现交互式GUI设计



 



用纯代码的形式进行界面编写,有个好处是写出来的界面能很容易自适应窗口大小,不过写复杂界面的时候会比较麻烦。为此我们需要一个类似Visual Studio的交互式设计界面。



 



上面我也说Qt IDE本身并不支持Python,但是Qt IDE中有个Qt Designer的组件,能够进行Qt组件的交互式设计,并且输出成 .ui格式的文件(其实就是xml文件)。PySide的PySide-uic程序(就在Script文件夹下面)能够将这个ui文件转化成为py文件,之后就可以直接用PySide进行调用。



 



经过30秒的 精雕细琢,建立了这个日历+按钮界面:



 



[转载]Python <wbr>GUI <wbr>编程:PySide、Qt <wbr>Designer <wbr>和 <wbr>py



 



完成后保存成为TestGUI.ui文件。可以先打开看一眼:



 



<?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>310</width>
     <height>284</height>
    </rect>
   </property>
   <property name="sizePolicy">
    <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
     <horstretch>0</horstretch>
     <verstretch>0</verstretch>
    </sizepolicy>
   </property>
   <property name="minimumSize">
    <size>
     <width>310</width>
     <height>284</height>
    </size>
   </property>
   <property name="maximumSize">
    <size>
     <width>310</width>
     <height>284</height>
    </size>
   </property>
   <property name="windowTitle">
    <string>Useless Windows</string>
   </property>
   <widget class="QWidget" name="centralwidget">
    <widget class="QPushButton" name="pushButton">
     <property name="geometry">
      <rect>
       <x>80</x>
       <y>240</y>
       <width>141</width>
       <height>23</height>
      </rect>
     </property>
     <property name="text">
      <string>Useless Button</string>
     </property>
    </widget>
    <widget class="QCalendarWidget" name="calendarWidget">
     <property name="geometry">
      <rect>
       <x>30</x>
       <y>40</y>
       <width>248</width>
       <height>169</height>
      </rect>
     </property>
    </widget>
   </widget>
 </widget>
 <resources/>
 <connections/>
 </ui>



 



看着略复杂。接着我们写个bat文件把这个ui文件转换成为py文件:



 



pyside-uic TestGUI.ui -o TestGUI.py



 



顺利的话就可以在同一个目录下拿到TestGUI.py文件,接着打开看一眼对比下:



 



# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'TestGUI.ui'
#
# Created: Thu Jun 20 18:55:06 2013
#      by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!

from PySide import QtCore, QtGui

class Ui_MainWindow(object):
     def setupUi(self, MainWindow):
         MainWindow.setObjectName("MainWindow")
         MainWindow.resize(310, 284)
         sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setVerticalStretch(0)
         sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
         MainWindow.setSizePolicy(sizePolicy)
         MainWindow.setMinimumSize(QtCore.QSize(310, 284))
         MainWindow.setMaximumSize(QtCore.QSize(310, 284))
         self.centralwidget = QtGui.QWidget(MainWindow)
         self.centralwidget.setObjectName("centralwidget")
         self.pushButton = QtGui.QPushButton(self.centralwidget)
         self.pushButton.setGeometry(QtCore.QRect(80, 240, 141, 23))
         self.pushButton.setObjectName("pushButton")
         self.calendarWidget = QtGui.QCalendarWidget(self.centralwidget)
         self.calendarWidget.setGeometry(QtCore.QRect(30, 40, 248, 169))
         self.calendarWidget.setObjectName("calendarWidget")
         MainWindow.setCentralWidget(self.centralwidget)

         self.retranslateUi(MainWindow)
         QtCore.QMetaObject.connectSlotsByName(MainWindow)

     def retranslateUi(self, MainWindow):
         MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "Useless Windows", None, QtGui.QApplication.UnicodeUTF8))
         self.pushButton.setText(QtGui.QApplication.translate("MainWindow", "Useless Button", None, QtGui.QApplication.UnicodeUTF8))



 



可以看到,实际上是生成了一个类,这个类包含了窗口的所有信息。



 



然后就写一个和(3)类似的程序,来调用这个类,并生成新窗口:



 



# TestProgramWithGUI.py
# This is a program for testing Qt GUI and PySide

import sys

# Import Qt GUI component
from PySide.QtGui import *

# Import GUI File
from TestGUI import Ui_MainWindow

# Self Function
def PrintHello():
     print("Hello")

# Make main window class
class MainWindow(QMainWindow,Ui_MainWindow):
     def __init__(self, parent=None):
         super(MainWindow,self).__init__(parent)
         self.setupUi(self)
         # Connect button click event to PrintHello function
         self.pushButton.clicked.connect(PrintHello)

# End of main window class


# Main Function
if __name__=='__main__':
     Program = QApplication(sys.argv)
     Window=MainWindow()
     Window.show()
     Program.exec_()



 



我还给按钮链接了自定义的PrintHello()函数,这样每次按按钮就会在Console输出Hello。为什么称这个为Useless Button呢,因为后来我在py2exe打包的过程中去掉了Console,所以Hello就输出到空气里了。



 



写完运行,大功告成:



 



[转载]Python <wbr>GUI <wbr>编程:PySide、Qt <wbr>Designer <wbr>和 <wbr>py



 



(5)打包成exe文件



 



接下来就是用py2exe打包成不需要依赖python的exe文件了。前两年的教程还经常写要添加这个添加那个,现在新的PySide已经非常修正了大部分bug了,只要正常编写Setup文件就可以。



 



我写的文件如下:



 



from distutils.core import setup
import py2exe

# Set options
 options ={ 'py2exe':
                 {
                     'dll_excludes':['w9xpopen.exe'] #This file is for win9x platform
                 }
         }

# Setup
 setup ( options  = options,
         windows = [{
                         'script': 'TestProgramWithGUI.py'
                   }]
       )


 



由于是窗体文件,setup里用的是windows选项,这就屏蔽了Console。经过满屏的通知后,得到Dist文件夹,除了exe文件,还打包一堆第三方的包,一个窗口的文件竟然都有20M,文件夹内容和exe运行结果如下:



 



[转载]Python <wbr>GUI <wbr>编程:PySide、Qt <wbr>Designer <wbr>和 <wbr>py