文章目錄
前言
QPainter類負責繪畫
簡單來說,就是你想要將你的app設計成什麼樣子,都需要重寫QPainter來實現
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的圓。
三種方法自定義paintEvent()函數
但這個程序其實很有問題,因爲我們自己做開發的時候,不可能直接在主窗口上繪圖,通常來講,我們會給主窗口一個或則多個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的promote功能
這個方法最簡單,但是卻是我最晚知道的。。。。。
哎,自學者的悲哀。。
過程很簡單
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_())
否則會報各種錯誤,切記!
2、自己定義一個類,並繼承QWidget,在自己的類裏面重寫paintEvent()
# -*- 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方法呢?
答案是有!
3、動態的添加方法
你還可以自定義一個函數 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 "<stdin>", 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函數仍然需要大量定義。