Python3使用PyQt5制作簡(jiǎn)單的畫板/手寫板實(shí)例
1.前言
版本:Python3.6.1 + PyQt5
寫一個(gè)程序的時(shí)候需要用到畫板/手寫板,只需要最簡(jiǎn)單的那種。原以為網(wǎng)上到處都是,結(jié)果找了好幾天,都沒(méi)有找到想要的結(jié)果。
網(wǎng)上的要么是非python版的qt程序(要知道qt版本之間差異巨大,還是非同一語(yǔ)言的),改寫難度太大。要么是PyQt4的老程序,很多都已經(jīng)不能在PyQt5上運(yùn)行了。要么是大神寫的特別復(fù)雜的程序,簡(jiǎn)直是直接做出了一個(gè)Windows自帶的畫圖版,只能膜拜~
于是我只能在眾多代碼中慢慢尋找自己需要的那一小部分,然后不斷地拼湊,不斷地理解大神的代碼,最終做出這么一個(gè)簡(jiǎn)單的畫板。望著這個(gè)簡(jiǎn)單的畫板我真是淚流滿面,中間數(shù)十次拼不對(duì)拼不全導(dǎo)致程序無(wú)數(shù)次崩潰,差點(diǎn)就放棄了......
2.簡(jiǎn)單的畫板1.0
在簡(jiǎn)單的畫板1.0這里,實(shí)現(xiàn)的功能是:在定點(diǎn)和移動(dòng)中的鼠標(biāo)所在處畫一條線
如圖所示:

鼠標(biāo)按住移動(dòng)的話,線也會(huì)跟著移動(dòng),從這個(gè)簡(jiǎn)單的程序開始理解PyQt5的運(yùn)行機(jī)制吧。
'''
簡(jiǎn)單的畫板1.0
功能:在定點(diǎn)和移動(dòng)中的鼠標(biāo)所在處畫一條線
作者:PyLearn
最后修改日期: 2017/10/18
'''
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import (QPainter, QPen)
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
#resize設(shè)置寬高,move設(shè)置位置
self.resize(400, 300)
self.move(100, 100)
self.setWindowTitle("簡(jiǎn)單的畫板1.0")
#setMouseTracking設(shè)置為False,否則不按下鼠標(biāo)時(shí)也會(huì)跟蹤鼠標(biāo)事件
self.setMouseTracking(False)
#設(shè)置兩個(gè)變量接收移動(dòng)中的點(diǎn)的x、y坐標(biāo)
self.pos_x = 20
self.pos_y = 20
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
pen = QPen(Qt.black, 2, Qt.SolidLine)
painter.setPen(pen)
#定點(diǎn)(20, 20) 到 (self.pos_x, self.pos_y)之間畫線
painter.drawLine(20, 20, self.pos_x, self.pos_y)
painter.end()
def mouseMoveEvent(self, event):
'''
按住鼠標(biāo)移動(dòng)事件:更新pos_x和pos_y的值
調(diào)用update()函數(shù)在這里相當(dāng)于調(diào)用paintEvent()函數(shù)
每次update()時(shí),之前調(diào)用的paintEvent()留下的痕跡都會(huì)清空
'''
self.pos_x = event.pos().x()
self.pos_y = event.pos().y()
self.update()
if __name__ == "__main__":
app = QApplication(sys.argv)
pyqt_learn = Example()
pyqt_learn.show()
app.exec_()
3.簡(jiǎn)單的畫板2.0
從以上的簡(jiǎn)單的畫板1.0程序的運(yùn)行可以發(fā)現(xiàn),按住鼠標(biāo)移動(dòng)的時(shí)候,線也會(huì)跟著移動(dòng),那如何讓之前的線留下痕跡,而不是消失呢?
在簡(jiǎn)單的畫板2.0中,使用一個(gè)列表保存所有移動(dòng)過(guò)的點(diǎn),然后要畫線的時(shí)候,循環(huán)遍歷列表,依次畫出列表中點(diǎn)到定點(diǎn)之間的線即可。
效果如圖所示:

'''
簡(jiǎn)單的畫板2.0
功能:
在定點(diǎn)和移動(dòng)中的鼠標(biāo)所在處畫一條線
并將畫過(guò)的線都保留在窗體上
作者:PyLearn
最后修改日期: 2017/10/18
'''
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import (QPainter, QPen)
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
#resize設(shè)置寬高,move設(shè)置位置
self.resize(400, 300)
self.move(100, 100)
self.setWindowTitle("簡(jiǎn)單的畫板2.0")
#setMouseTracking設(shè)置為False,否則不按下鼠標(biāo)時(shí)也會(huì)跟蹤鼠標(biāo)事件
self.setMouseTracking(False)
'''
要想將畫過(guò)的線都保留在窗體上
需要一個(gè)列表來(lái)保存所有移動(dòng)過(guò)的點(diǎn)
'''
self.pos_xy = []
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
pen = QPen(Qt.black, 2, Qt.SolidLine)
painter.setPen(pen)
#循環(huán)遍歷self.pos_xy中每個(gè)點(diǎn),然后畫點(diǎn)到定點(diǎn)之間的線
for pos_tmp in self.pos_xy:
painter.drawLine(20, 20, pos_tmp[0], pos_tmp[1])
painter.end()
def mouseMoveEvent(self, event):
'''
按住鼠標(biāo)移動(dòng)事件:將當(dāng)前點(diǎn)添加到pos_xy列表中
調(diào)用update()函數(shù)在這里相當(dāng)于調(diào)用paintEvent()函數(shù)
每次update()時(shí),之前調(diào)用的paintEvent()留下的痕跡都會(huì)清空
'''
#中間變量pos_tmp提取當(dāng)前點(diǎn)
pos_tmp = (event.pos().x(), event.pos().y())
#pos_tmp添加到self.pos_xy中
self.pos_xy.append(pos_tmp)
self.update()
if __name__ == "__main__":
app = QApplication(sys.argv)
pyqt_learn = Example()
pyqt_learn.show()
app.exec_()
4.簡(jiǎn)單的畫板3.0
好了,接下來(lái)進(jìn)入正題了。簡(jiǎn)單的畫板2.0不過(guò)是畫鼠標(biāo)所在點(diǎn)到定點(diǎn)的線,那么如何將按住鼠標(biāo)后移動(dòng)的軌跡保留在窗體上?
這個(gè)就需要一個(gè)列表來(lái)保存所有移動(dòng)過(guò)的點(diǎn),然后把所有相鄰兩個(gè)點(diǎn)之間都畫一條線,就能斷斷續(xù)續(xù)連成鼠標(biāo)的痕跡了。
效果如圖所示:

是不是就畫出鼠標(biāo)移動(dòng)的軌跡了!
不過(guò)這也是有缺點(diǎn)的,比如說(shuō)寫個(gè)5看看:

硬生生變成了一個(gè)5不是5, 6不是6的數(shù)字。這是因?yàn)樵俅翁峁P畫時(shí),5上面的那一橫跟之前畫的尾巴那里連起來(lái)了。好好想想,這個(gè)問(wèn)題怎么解決呢?
'''
簡(jiǎn)單的畫板3.0
功能:將按住鼠標(biāo)后移動(dòng)的軌跡保留在窗體上
作者:PyLearn
最后修改日期: 2017/10/18
'''
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import (QPainter, QPen)
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
#resize設(shè)置寬高,move設(shè)置位置
self.resize(400, 300)
self.move(100, 100)
self.setWindowTitle("簡(jiǎn)單的畫板3.0")
#setMouseTracking設(shè)置為False,否則不按下鼠標(biāo)時(shí)也會(huì)跟蹤鼠標(biāo)事件
self.setMouseTracking(False)
'''
要想將按住鼠標(biāo)后移動(dòng)的軌跡保留在窗體上
需要一個(gè)列表來(lái)保存所有移動(dòng)過(guò)的點(diǎn)
'''
self.pos_xy = []
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
pen = QPen(Qt.black, 2, Qt.SolidLine)
painter.setPen(pen)
'''
首先判斷pos_xy列表中是不是至少有兩個(gè)點(diǎn)了
然后將pos_xy中第一個(gè)點(diǎn)賦值給point_start
利用中間變量pos_tmp遍歷整個(gè)pos_xy列表
point_end = pos_tmp
畫point_start到point_end之間的線
point_start = point_end
這樣,不斷地將相鄰兩個(gè)點(diǎn)之間畫線,就能留下鼠標(biāo)移動(dòng)軌跡了
'''
if len(self.pos_xy) > 1:
point_start = self.pos_xy[0]
for pos_tmp in self.pos_xy:
point_end = pos_tmp
painter.drawLine(point_start[0], point_start[1], point_end[0], point_end[1])
point_start = point_end
painter.end()
def mouseMoveEvent(self, event):
'''
按住鼠標(biāo)移動(dòng)事件:將當(dāng)前點(diǎn)添加到pos_xy列表中
調(diào)用update()函數(shù)在這里相當(dāng)于調(diào)用paintEvent()函數(shù)
每次update()時(shí),之前調(diào)用的paintEvent()留下的痕跡都會(huì)清空
'''
#中間變量pos_tmp提取當(dāng)前點(diǎn)
pos_tmp = (event.pos().x(), event.pos().y())
#pos_tmp添加到self.pos_xy中
self.pos_xy.append(pos_tmp)
self.update()
if __name__ == "__main__":
app = QApplication(sys.argv)
pyqt_learn = Example()
pyqt_learn.show()
app.exec_()
5.簡(jiǎn)單的畫板4.0
簡(jiǎn)單的畫板3.0中有一個(gè)致命的問(wèn)題,那就是連續(xù)的問(wèn)題,比如說(shuō)要寫一個(gè)三位數(shù)123:

很難看對(duì)不對(duì)?
解決這個(gè)問(wèn)題的方法應(yīng)該是有很多種的,我也沒(méi)有深入想,就直接用了這個(gè)麻煩點(diǎn)的方法。
我的辦法是當(dāng)鼠標(biāo)按住移動(dòng)然后松開的時(shí)候,往保存所有移動(dòng)過(guò)的點(diǎn)的列表中添加一個(gè)斷點(diǎn)(-1, -1)。然后在每次畫線的時(shí)候,都判斷一下是不是斷點(diǎn),如果是斷點(diǎn)的話就想辦法跳過(guò)去,并且不連續(xù)的開始接著畫線。
效果如圖所示:

以下是具體實(shí)現(xiàn)代碼:
'''
簡(jiǎn)單的畫板4.0
功能:
將按住鼠標(biāo)后移動(dòng)的軌跡保留在窗體上
并解決二次作畫時(shí)與上次痕跡連續(xù)的問(wèn)題
作者:PyLearn
最后修改日期: 2017/10/18
'''
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import (QPainter, QPen)
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
#resize設(shè)置寬高,move設(shè)置位置
self.resize(400, 300)
self.move(100, 100)
self.setWindowTitle("簡(jiǎn)單的畫板4.0")
#setMouseTracking設(shè)置為False,否則不按下鼠標(biāo)時(shí)也會(huì)跟蹤鼠標(biāo)事件
self.setMouseTracking(False)
'''
要想將按住鼠標(biāo)后移動(dòng)的軌跡保留在窗體上
需要一個(gè)列表來(lái)保存所有移動(dòng)過(guò)的點(diǎn)
'''
self.pos_xy = []
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
pen = QPen(Qt.black, 2, Qt.SolidLine)
painter.setPen(pen)
'''
首先判斷pos_xy列表中是不是至少有兩個(gè)點(diǎn)了
然后將pos_xy中第一個(gè)點(diǎn)賦值給point_start
利用中間變量pos_tmp遍歷整個(gè)pos_xy列表
point_end = pos_tmp
判斷point_end是否是斷點(diǎn),如果是
point_start賦值為斷點(diǎn)
continue
判斷point_start是否是斷點(diǎn),如果是
point_start賦值為point_end
continue
畫point_start到point_end之間的線
point_start = point_end
這樣,不斷地將相鄰兩個(gè)點(diǎn)之間畫線,就能留下鼠標(biāo)移動(dòng)軌跡了
'''
if len(self.pos_xy) > 1:
point_start = self.pos_xy[0]
for pos_tmp in self.pos_xy:
point_end = pos_tmp
if point_end == (-1, -1):
point_start = (-1, -1)
continue
if point_start == (-1, -1):
point_start = point_end
continue
painter.drawLine(point_start[0], point_start[1], point_end[0], point_end[1])
point_start = point_end
painter.end()
def mouseMoveEvent(self, event):
'''
按住鼠標(biāo)移動(dòng)事件:將當(dāng)前點(diǎn)添加到pos_xy列表中
調(diào)用update()函數(shù)在這里相當(dāng)于調(diào)用paintEvent()函數(shù)
每次update()時(shí),之前調(diào)用的paintEvent()留下的痕跡都會(huì)清空
'''
#中間變量pos_tmp提取當(dāng)前點(diǎn)
pos_tmp = (event.pos().x(), event.pos().y())
#pos_tmp添加到self.pos_xy中
self.pos_xy.append(pos_tmp)
self.update()
def mouseReleaseEvent(self, event):
'''
重寫鼠標(biāo)按住后松開的事件
在每次松開后向pos_xy列表中添加一個(gè)斷點(diǎn)(-1, -1)
然后在繪畫時(shí)判斷一下是不是斷點(diǎn)就行了
是斷點(diǎn)的話就跳過(guò)去,不與之前的連續(xù)
'''
pos_test = (-1, -1)
self.pos_xy.append(pos_test)
self.update()
if __name__ == "__main__":
app = QApplication(sys.argv)
pyqt_learn = Example()
pyqt_learn.show()
app.exec_()
至此,終于完成了簡(jiǎn)單的畫板程序的實(shí)現(xiàn)!
另外,如果在使用這個(gè)代碼的過(guò)程中有遇到什么問(wèn)題,也歡迎向我反饋。
以上這篇Python3使用PyQt5制作簡(jiǎn)單的畫板/手寫板實(shí)例就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決python調(diào)用matlab時(shí)的一些常見(jiàn)問(wèn)題
這篇文章主要介紹了解決python調(diào)用matlab時(shí)的一些常見(jiàn)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03
利用pyecharts讀取csv并進(jìn)行數(shù)據(jù)統(tǒng)計(jì)可視化的實(shí)現(xiàn)
這篇文章主要介紹了利用pyecharts讀取csv并進(jìn)行數(shù)據(jù)統(tǒng)計(jì)可視化的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
Python畫圖小案例之多啦A夢(mèng)叮當(dāng)貓超詳細(xì)注釋
在看了很多Python教程之后,覺(jué)得是時(shí)候做點(diǎn)什么小項(xiàng)目來(lái)練練手了,于是想來(lái)想去,用python寫了一個(gè)童年記憶的卡通人物哆啦A夢(mèng),代碼注釋無(wú)比詳細(xì)清楚,快來(lái)看看吧2021-09-09
Python利用psutil庫(kù)進(jìn)行監(jiān)控進(jìn)程和資源
psutil是Python系統(tǒng)和進(jìn)程工具庫(kù),它提供了一種跨平臺(tái)的方式來(lái)獲取系統(tǒng)信息、管理系統(tǒng)進(jìn)程、監(jiān)控系統(tǒng)性能、操作系統(tǒng)資源等,下面就跟隨小編一起來(lái)學(xué)習(xí)psutil庫(kù)的具體應(yīng)用吧2024-01-01
Python+FuzzyWuzzy實(shí)現(xiàn)模糊匹配的示例詳解
在日常開發(fā)工作中,經(jīng)常會(huì)遇到這樣的一個(gè)問(wèn)題:要對(duì)數(shù)據(jù)中的某個(gè)字段進(jìn)行匹配,但這個(gè)字段有可能會(huì)有微小的差異。本文將分享一個(gè)簡(jiǎn)單易用的模糊字符串匹配工具包:FuzzyWuzzy,讓你輕松解決煩惱的匹配問(wèn)題2022-04-04
Python實(shí)現(xiàn)的簡(jiǎn)單線性回歸算法實(shí)例分析
這篇文章主要介紹了Python實(shí)現(xiàn)的簡(jiǎn)單線性回歸算法,結(jié)合實(shí)例形式分析了線性回歸算法相關(guān)原理、功能、用法與操作注意事項(xiàng),需要的朋友可以參考下2018-12-12

