QPainter类负责绘画
简单来说,就是你想要将你的app设计成什么样子,都需要重写QPainter来实现
简单的绘图,我们通常用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
那么如何能够将圆画入self.widget
里面呢?
经过探索,以下几种方法可以解决这个问题:
这个方法最简单,但是却是我最晚知道的。。。。。
哎,自学者的悲哀。。
过程很简单
1、 进入Designer界面
2、右键点击你需要重写paintEvent方法的部件
3、promote to…
4、new Promoted Class里面,填写你自定义类的类名,头文件名,在python中头文件名,就是你自定义类所在的文件
5、点Promote
这个时候你所promote的插件,就变成了你所自定义的类的一个实例了,当然现在这个所谓的自定义类除了名字什么都没有。
你现在所需要做的就是按照前面的方法自定义类,并重写paintEvent方法
只要保证自定义的名字和promote的名字一致就行了。
唯一需要注意的是,必须要有主函数,也就是这个样子
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyWidget()
window.show()
sys.exit(app.exec_())
而不能偷懒写成
app = QApplication(sys.argv)
window = MyWidget()
window.show()
sys.exit(app.exec_())
否则会报各种错误,切记!
# -*- 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_())
现在的问题是,我的PyQt4配置文件其实是通过pyuic从.ui文件转换而来的。得到的文件可以说是自成一体,如果我直接在此文件上更改,添加,甚至重写函数,那么如果万一我在designer上调整了ui,并重新更新这个文件,那么我在上面的任何修改都将付诸流水。
既然我不可能修改配置文件,那么我有没有可能从外部去修改或者重写Widget里的paintEvent方法呢?
答案是有!
你还可以自定义一个函数 paintEvent,然后动态添加到已经存在的实例当中,这样就相当于重写paintEvent 方法,举个例子:
>>> def foo():# 这就是函数
... print "foo"
...
>>> class A:
... def act_a(self):# 而这里是方法
... print "actA"
那么怎样将函数 foo 添加到 class A里面呢?
讲之前要说明的是,在python当中,方法分为bound和unbound两大类。
unbound:对于class而言,他的方法都是"未绑定"的状态,所以如果我们直接给class添加方法,那么该方法就会自动"绑定"到所有的对象当中去,例如:
>>> A.foo = foo # 给class添加方法的步骤只有这一步,简不简单?~~~~^o^
>>> a1 = A()
>>> a2 = A()
>>> a1.foo() # 所有的对象都可以访问foo方法
foo
>>> a2.foo()
foo
我们可以看到,无论是a1还是a2,他们都可以访问foo()方法
bound:所谓"绑定"的方法,通常只存在与具体的对象当中,如果我们将一个方法添加到一个对象当中去,那么这个方法会绑定到这个对象身上,很显然,这个方法就变成了该对象独有的方法。
>>> a = A()
>>> import types
>>> a.foo = types.MethodType( foo, a )
>>> a.foo()
foo
>>> a2 = A()
>>> a2.foo()
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: A instance has no attribute 'foo'
此时a和a2虽然都是class A的对象,但是只有a才能访问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)
# 关键步骤:给QWidget添加非绑定方法paintEvent
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添加绑定方法:
# 关键步骤:给QWidget添加非绑定方法paintEvent
# QWidget.paintEvent = paintEvent
# 给QWidget的对象tab_1添加绑定的方法
import types
tab_1.paintEvent = types.MethodType(paintEvent,tab_1)
至此,问题解决。
这个方法真是折磨了我好几天,所幸解决了,不过这个方法也有局限性,虽然比方法1要简单,至少不用定义类,但是paint函数仍然需要大量定义。