Qt學習筆記#8:QPainter


前言

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函數仍然需要大量定義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章