Python3使用PyQt5制作简单的画板/手写板实例
本文介绍基于Python3和PyQt5开发简易画板的完整过程。通过四个迭代版本,逐步实现定点连线、轨迹保留、连续笔画处理及断点隔离功能。重点解析paintEvent与鼠标事件机制,提供解决数字书写粘连问题的断点算法,适合PyQt初学者参考。
1.前言
开发环境:Python3.6.1 + PyQt5
在开发某个程序时,需要集成一个画板或手写板功能,且要求实现最基础的核心功能。原本以为这类资源在网上随处可见,但经过数天的搜索,始终未能找到完全符合需求的现成方案。
网上现有的资源主要分为三类:一是非Python语言的Qt程序(考虑到Qt版本间差异巨大且跨语言改写难度极高);二是基于PyQt4的旧版程序,其中大量代码已无法在PyQt5环境中直接运行;三是功能过于庞大的复杂程序,几乎复刻了Windows自带画图软件的全部功能,虽然技术精湛,但难以直接提取所需模块。
因此,我只能从大量代码中筛选出所需的核心部分,通过不断拼凑、深入理解原作者逻辑,最终构建出这个简易画板。回顾开发过程,由于多次拼凑失败导致程序频繁崩溃,曾数次濒临放弃,最终得以完成。
2.简单的画板1.0
在简易画板1.0版本中,实现的核心功能是:在固定点与鼠标移动当前位置之间绘制一条连线。
如图所示:
当鼠标按住并移动时,连线会跟随鼠标位置动态变化。通过这一基础程序,可以初步理解PyQt5的事件运行机制。
'''
简单的画板1.0
功能:在定点和移动中的鼠标所在处画一条线
作者: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设置宽高,move设置位置
self.resize(400, 300)
self.move(100, 100)
self.setWindowTitle("简单的画板1.0")
#setMouseTracking设置为False,否则不按下鼠标时也会跟踪鼠标事件
self.setMouseTracking(False)
#设置两个变量接收移动中的点的x、y坐标
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)
#定点(20, 20) 到 (self.pos_x, self.pos_y)之间画线
painter.drawLine(20, 20, self.pos_x, self.pos_y)
painter.end()
def mouseMoveEvent(self, event):
'''
按住鼠标移动事件:更新pos_x和pos_y的值
调用update()函数在这里相当于调用paintEvent()函数
每次update()时,之前调用的paintEvent()留下的痕迹都会清空
'''
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.简单的画板2.0
观察简易画板1.0的运行效果可以发现,当按住鼠标移动时,之前绘制的连线会随之移动并消失,无法保留痕迹。那么,如何让之前的连线保留在窗体上而不消失呢?
在简易画板2.0中,采用一个列表来保存所有鼠标移动经过的点。在绘制连线时,通过循环遍历该列表,依次将列表中每个点与固定点连接起来,从而保留所有历史轨迹。
效果如图所示:

'''
简单的画板2.0
功能:
在定点和移动中的鼠标所在处画一条线
并将画过的线都保留在窗体上
作者: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设置宽高,move设置位置
self.resize(400, 300)
self.move(100, 100)
self.setWindowTitle("简单的画板2.0")
#setMouseTracking设置为False,否则不按下鼠标时也会跟踪鼠标事件
self.setMouseTracking(False)
'''
要想将画过的线都保留在窗体上
需要一个列表来保存所有移动过的点
'''
self.pos_xy = []
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
pen = QPen(Qt.black, 2, Qt.SolidLine)
painter.setPen(pen)
#循环遍历self.pos_xy中每个点,然后画点到定点之间的线
for pos_tmp in self.pos_xy:
painter.drawLine(20, 20, pos_tmp[0], pos_tmp[1])
painter.end()
def mouseMoveEvent(self, event):
'''
按住鼠标移动事件:将当前点添加到pos_xy列表中
调用update()函数在这里相当于调用paintEvent()函数
每次update()时,之前调用的paintEvent()留下的痕迹都会清空
'''
#中间变量pos_tmp提取当前点
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.简单的画板3.0
接下来进入核心功能实现。简易画板2.0仅实现了从固定点到当前鼠标点的连线,如何将按住鼠标后移动的完整轨迹保留在窗体上?
这需要引入一个列表来保存所有移动过的点,并将列表中相邻的两个点依次连接成线。这样,一系列短线段就能断续地连成鼠标移动的完整轨迹。
效果如图所示:

此时,鼠标移动的轨迹已被成功绘制出来。
然而,该版本仍存在缺陷。例如,尝试书写数字“5”时:

结果往往变成“非5非6”的怪异形状。这是因为在再次提笔时,数字“5”顶部的横线与之前绘制的尾部线条发生了连接。针对这一问题,需要进一步思考解决方案。
'''
简单的画板3.0
功能:将按住鼠标后移动的轨迹保留在窗体上
作者: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设置宽高,move设置位置
self.resize(400, 300)
self.move(100, 100)
self.setWindowTitle("简单的画板3.0")
#setMouseTracking设置为False,否则不按下鼠标时也会跟踪鼠标事件
self.setMouseTracking(False)
'''
要想将按住鼠标后移动的轨迹保留在窗体上
需要一个列表来保存所有移动过的点
'''
self.pos_xy = []
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
pen = QPen(Qt.black, 2, Qt.SolidLine)
painter.setPen(pen)
'''
首先判断pos_xy列表中是不是至少有两个点了
然后将pos_xy中第一个点赋值给point_start
利用中间变量pos_tmp遍历整个pos_xy列表
point_end = pos_tmp
画point_start到point_end之间的线
point_start = point_end
这样,不断地将相邻两个点之间画线,就能留下鼠标移动轨迹了
'''
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):
'''
按住鼠标移动事件:将当前点添加到pos_xy列表中
调用update()函数在这里相当于调用paintEvent()函数
每次update()时,之前调用的paintEvent()留下的痕迹都会清空
'''
#中间变量pos_tmp提取当前点
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.简单的画板4.0
简易画板3.0存在一个关键问题,即笔画的连续性控制。例如,若要书写三位数“123”:

视觉效果并不理想。
解决此问题的方法有多种,此处未做深入探讨,直接采用了一种相对繁琐但有效的方案。
具体思路是:当鼠标按住移动并松开时,向保存所有移动点的列表中添加一个特殊断点(-1, -1)。在每次绘制连线时,检测当前点是否为断点;如果是断点,则跳过该点,并从下一个非断点开始重新绘制,从而避免笔画间的意外连接。
效果如图所示:

以下是具体实现代码:
'''
简单的画板4.0
功能:
将按住鼠标后移动的轨迹保留在窗体上
并解决二次作画时与上次痕迹连续的问题
作者: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设置宽高,move设置位置
self.resize(400, 300)
self.move(100, 100)
self.setWindowTitle("简单的画板4.0")
#setMouseTracking设置为False,否则不按下鼠标时也会跟踪鼠标事件
self.setMouseTracking(False)
'''
要想将按住鼠标后移动的轨迹保留在窗体上
需要一个列表来保存所有移动过的点
'''
self.pos_xy = []
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
pen = QPen(Qt.black, 2, Qt.SolidLine)
painter.setPen(pen)
'''
首先判断pos_xy列表中是不是至少有两个点了
然后将pos_xy中第一个点赋值给point_start
利用中间变量pos_tmp遍历整个pos_xy列表
point_end = pos_tmp
判断point_end是否是断点,如果是
point_start赋值为断点
continue
判断point_start是否是断点,如果是
point_start赋值为point_end
continue
画point_start到point_end之间的线
point_start = point_end
这样,不断地将相邻两个点之间画线,就能留下鼠标移动轨迹了
'''
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):
'''
按住鼠标移动事件:将当前点添加到pos_xy列表中
调用update()函数在这里相当于调用paintEvent()函数
每次update()时,之前调用的paintEvent()留下的痕迹都会清空
'''
#中间变量pos_tmp提取当前点
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):
'''
重写鼠标按住后松开的事件
在每次松开后向pos_xy列表中添加一个断点(-1, -1)
然后在绘画时判断一下是不是断点就行了
是断点的话就跳过去,不与之前的连续
'''
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_()
至此,简易画板程序的功能已全部实现。
此外,若在使用本代码过程中遇到任何问题,欢迎随时反馈。
以上这篇Python3使用PyQt5制作简单的画板/手写板实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









