因为毕设要用到PyQt,所以正在学习Qt以及Qt Designer的使用。
本篇笔记记录我使用当中发现的种种我认为值得记录的要点,可能比较凌乱,新人也可能看得糊涂,不过反正我自己看,先将就着吧,以后有时间再做整理。
毕设的要求是使用Python27和PyQt4,我需要先使用Qt做好界面,然后将.ui文件转换成对应的.py,然后再使用Python完成剩下的程序。
至于IDE,我用的是PyCharm。
由于PyCharm的运行需要java环境,可能你还需要安装jdk。。。
因为是老版本了,PyQt4有点麻烦,一开始我还以为PyQt4就是一个软件,可以直接设计UI呢,结果发现就是一个库而已,我要做的是UI,如果纯靠编程,那还不弄死我自己? 所以我需要一个IDE,网上一查,原来Qt里面含的Qt Creater就是一个很好的IDE,那还说什么?直接到官网下载最新的Qt5.6版本,但是最新的Qt Creater真是非常难以入门啊。。。。。安装以后各种问题,我都懒得说了。。。反正就是用不了。 最后弄来弄去,还是没有解决,最后心一横,换了一个老版本,Qt4.8,幸运的是,这个版本的Qt Creater里面还有一个独立的Qt Designer,使用它可以直接先设计好用户界面,并生成.ui文件,最后在用python编程,完成毕设。
首先安装python2.7和PyCharm,如果必要,安装JDK。
PyQt4可以直接从官网下载Binary Packages安装,里面自带安装好sip和Qt Designer,不用像有些老的帖子那样去弄这些东西了。。。。
需要注意的是,如果你的python是安装的32位的,那么PyQt4也需要下载32位的。
我使用的是最新的XUbuntu 16.04 LTS,XUbuntu就是Ubuntu,不同之处在于图形化界面是Xfce,而原版是Gnome,Xfce是一个相对轻量化的界面,因为我是用的虚拟机安装,所以Xfce要比Gnome流畅100倍不止。
Linux自带python,而且PyQt4安装也非常简单
sudo apt-get install python-qt4
但注意的是这里的PyQt4不包含designer,需要自行安装
sudo apt-get install qt4-designer
PyCharm的安装,首先你必须要安装好jdk,然后
Installation Instructions
Copy the pycharm-*.tar.gz to the desired installation location (make sure you have rw permissions for that directory)
Unpack the pycharm-*.tar.gz
using the following command:tar xfz pycharm-*.tar.gz
Remove the pycharm-*.tar.gz to save disk space (optional)
Run pycharm.sh from the bin subdirectory
至此,Linux下面的开发环境就算是搭建好了。
得到的py文件是没有主函数的,虽然Python本身也没有主函数一说。。。但是直接运行这个文件是没有任何效果的,需要额外的代码:
class MyWindow(QtGui.QMainWindow, NeuronenUI.Ui_MainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setupUi(self)
# 此处插入代码
# 建立connect
self.show()
# 此处可以定义各种slot
app = QtGui.QApplication(sys.argv)
window = MyWindow()
sys.exit(app.exec_())
由于要支持跨平台,一些基于平台的功能就不能直接写入类库,导致了使用 Qt 时一些功能需要变通的方法来实现。
比如:Qt 并不支持直接禁止用户调整窗口大小。变通的方法是将窗口的最小大小和最大大小设置为一样。
widget->setMinimumSize(10, 10);
widget->setMaximumSize(10, 10);
PS:暂时不知道如何保留最小化按钮,也就是说,要么最大最小都有,要么都没有
下面是为sizepolicy的各种属性介绍
Constants | Description |
---|---|
QSizePolicy::Fixed | widget 的实际尺寸只参考 sizeHint() 的返回值,不能伸展(grow)和收缩(shrink) |
QSizePolicy::Minimum | 可以伸展和收缩,不过sizeHint() 的返回值规定了 widget 能缩小到的最小尺寸 |
QSizePolicy::Maximum | 可以伸展和收缩,不过sizeHint() 的返回值规定了 widget 能伸展到的最大尺寸 |
QSizePolicy::Preferred | 可以伸展和收缩,但没有优势去获取更大的额外空间使自己的尺寸比 sizeHint() 的返回值更大 |
QSizePolicy::Expanding | 可以伸展和收缩,它会尽可能多地去获取额外的空间,也就是比 Preferred 更具优势 |
QSizePolicy::MinimumExpanding | 可以伸展和收缩,不过sizeHint() 的返回值规定了 widget 能缩小到的最小尺寸,同时它比 Preferred 更具优势去获取额外空间 |
QSizePolicy::Ignored | 忽略 sizeHint() 的作用 |
对于像我这样的新手,往往搞不定Layout,结果就是控件的位置各种凌乱,即使一开始正确,主窗口大小一变,要么就乱,要么控件无法随窗口大小改变。
弄了好几天,总结下来的经验就是:无论如何,不要把控件直接放到第一层的QWidget下面,要添加控件,外面都要套一层Container,嗯,注意带套~~~嗯。。。就这样。。
还有一点要注意的是,网上大多数教程都是以QWidget为基础,但是有的时候我们需要以QMainWindow为基础,这个时候就必须在QMainWindow下面额外放入以个QWidget,并且设置为CentralWidget,然后在此之下新建一个mainlayout
centralwidget = QWidget()
QMainWindow.setCentralWidget(centralwidget)
mainlayout = QVBoxLayout(centralwidget)
mainlayout.addWidget(...)
这样就相当于在正常的Widget下套上一层MainWindow的衣服,而Widget改怎么操作就怎么操作
简单的绘图,我们通常用QPainter,举个例子:
# -*- coding: utf-8 -*-
# 简单的QPainter程序
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setFixedSize(400, 400)
self.show()
def paintEvent(self, event):
# 在QMainWindow上绘图
qp = QPainter(self)
# 蓝色,粗细为2,实线
pen = QPen(Qt.blue, 2, Qt.SolidLine)
qp.setPen(pen)
# 画一个直径400的圆
qp.drawEllipse(0, 0, 400, 400)
app = QApplication(sys.argv)
win = MyWindow()
sys.exit(app.exec_())
这就是一个简单的Qt程序,效果就是建立一个400*400大小的窗口,并在中心化一个直径400的圆。
但这个程序其实很有问题,因为我们自己做开发的时候,不可能直接在主窗口上绘图,通常来讲,我们会给主窗口一个或则多个container,然后container里面再放入各种widget,而我们需要绘制的内容,往往是在这些widget当中的。比如我们将上面的code改改,添加一个Qwidget:
def __init__(self):
super(MyWindow, self).__init__()
self.setFixedSize(400, 400)
self.widget = QWidget(self)
self.widget.setFixedSize(300, 300)
self.widget.setStyleSheet("background-color:white")
self.show()
这里我就遇到一个问题,QPainter说明文档里面倒是提到了可以在别的QPaintDevice上作图,按照我本来的理解是,只要把paintEvent里面的QPainter参数变一下,应该就可以在对应的device上绘图。
def paintEvent(self, event):
# 在QWidget上绘图
qp = QPainter(self.widget)
# 蓝色,粗细为2,实线
pen = QPen(Qt.blue, 2, Qt.SolidLine)
qp.setPen(pen)
# 画一个直径400的圆
qp.drawEllipse(0, 0, 400, 400)
结果总是提示
QPainter::begin: Widget painting can only begin as a result of a paintEvent
那么如何能够将圆画如textEdit里面呢?
经过探索,以下几种方法可以解决这个问题:
# -*- coding: utf-8 -*-
# 简单的QPainter程序
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setFixedSize(400, 400)
# self.widget = QWidget(self)
self.widget = MyWidget(self)
self.widget.setFixedSize(300, 300)
self.widget.setStyleSheet("background-color:white")
self.show()
def paintEvent(self, event):
# 在QMainWindow上绘图
qp = QPainter(self)
# 蓝色,粗细为2,实线
pen = QPen(Qt.blue, 2, Qt.SolidLine)
qp.setPen(pen)
# 画一个直径400的圆
qp.drawEllipse(0, 0, 400, 400)
class MyWidget(QWidget):
def __init__(self,parent):
super(MyWidget, self).__init__(parent)
def paintEvent(self, event):
# 在QWidget上绘图
qp = QPainter(self)
# 黑色,粗细为2,实线
pen = QPen(Qt.black, 2, Qt.SolidLine)
qp.setPen(pen)
# 画一个直径400的圆
qp.drawEllipse(0, 0, 400, 400)
app = QApplication(sys.argv)
win = MyWindow()
sys.exit(app.exec_())
这样虽然解决了问题,但是仔细想想,如果我的GUI有100个Widget需要绘图,那我岂不是要定义100个QWidget的子类,重写100次paintEvent方法??
这样不仅写代码的人痛苦,读代码的人估计更痛苦。
不仅如此,更严重的问题是,我的PyQt4配置文件其实是通过pyuic从.ui文件转换而来的。得到的文件可以说是自成一体,如果我直接在此文件上更改,添加,甚至重写函数,那么如果万一我在designer上调整了ui,并重新更新这个文件,那么我在上面的任何修改都将付诸流水。
既然我不可能修改配置文件,那么我有没有可能从外部去修改或者重写Widget里的paintEvent方法呢?
答案是有!
自定义 函数 paintEvent,然后动态添加到已经存在的实例当中,这样就相当于重写paintEvent 方法,举个例子:
>>> def foo(self):# 这就是函数
... print "foo"
...
>>> class A:
... def act_a(self):# 而这里是方法
... print "actA"
>>> a = A() # 这是一个instance
>>> foo
<function foo at 0x00A98D70> #函数foo的相关信息
>>> a.act_a
<bound method A.bar of <__main__.A instance at 0x00A9BC88>> # 方法 bound
>>>
那么怎样将函数 foo 添加到 class A里面呢?
注意上面的关键字bound,在python当中,函数和方法还是不同的,class之外是函数,他没什么特别的,而在class之内的方法,就要分为bound和unbound两大类。
bound:即为”绑定”,意思就是该方法会自动绑定到所有的instance当中去,所有的instances都会具有这个方法。
>>> A.foo = foo # 就这一步,简不简单~~~~^o^
>>> a2 = A()
>>> a2.foo
<bound method A.foo of <__main__.A instance at 0x00A9BEB8>> >>> a2.foo() foo
unbound:”非绑定”,则该次添加的方法,只对当前实例有效。
>>> a = A()
>>> import types
>>> a.foo = types.MethodType( foo, a )
>>> a.foo
<bound method ?.foo of <__main__.A instance at 0x00A9BC88>> >>> a.foo() foo
网上搜到两篇文章,讲得非常好,可以看看: 英文版 中文版
再来看我自己的代码:
# -*- coding: utf-8 -*-
# 简单的QPainter程序
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.initUI()
self.show()
def initUI(self):
centralwidget = QWidget()
self.setCentralWidget(centralwidget)
# 添加一个有两个Tab的QTabWidget
self.widget = QTabWidget(centralwidget)
tab_1 = QWidget()
tab_2 = QWidget()
self.widget.addTab(tab_1,"tab1")
self.widget.addTab(tab_2,"tab2")
vbox = QVBoxLayout(centralwidget)
vbox.addWidget(self.widget)
self.setFixedSize(400, 400)
# bound
QWidget.paintEvent = paintEvent
def paintEvent(self, event):
# 在QMainWindow上绘图
qp = QPainter(self)
# 蓝色,粗细为2,实线
pen = QPen(Qt.blue, 2, Qt.SolidLine)
qp.setPen(pen)
# 画一个直径400的圆
qp.drawEllipse(0, 0, 400, 400)
app = QApplication(sys.argv)
win = MyWindow()
sys.exit(app.exec_())
呵呵,是不是和想想的不一样?
不要慌,这是因为我们现在添加的是绑定的方法,所以所有QWidget及其子类都会运行paintEvent方法,这就造成了混乱,而我们只是想在tab_1上画圆。
这时候,就需要添加非绑定的方法到tab_1
# bound
# QWidget.paintEvent = paintEvent
import types
tab_1.paintEvent = types.MethodType(paintEvent,tab_1)
这个方法真是折磨了我好几天,所幸解决了,不过这个方法也有局限性,虽然比方法1要简单,至少不用定义类,但是paint函数仍然需要大量定义。
QGraphicsView提供了一个可以看到QGraphicsScene内容的窗口
QGraphicsScene则提供一个能够管理大量2D图形的界面。