注:2023.11.1更新:见第四大点
更新内容:1. 增加画线轨迹 2. 支持鼠标事件和触摸事件获取坐标 3. 删去style.py文件,仅需main.py文件即可运行
一、前言
该测试功能是Linux产测软件的一个子功能,主要涉及:
140行代码
PySide2/PyQt5 的Event、信号和槽、QLabel,QWidget。
QLabel实现每个小框,QWidget作为主界面
PySide2和Pyqt5只要把import的包改好,代码是可以通用的。
手指滑动,手指坐标所在方块的颜色发生改变,如果手指划出方块区域,则所有已染色方块清空颜色,松开事件同理
实现效果:
二、实现思路:
-
使用GridLayout,绘制四周方格,方格初始化为黄色,点击到则为红色
-
重写Label,对每个label定义x,y表示所在gridlayout中的位置
如x = 1,y = 3表示label位于第一行第三列,left,right,top,bottom表示方格四条边的实际坐标 -
重写moveEvent事件和ReleaseEvent事件,
moveEvnet事件:
每次鼠标移动即触发moveEvent,则发出已经定义的信号move_signal
move_singal.connect(self.manager_touch)
manager_touch是判断当前鼠标是否位于方格内,如果在方格内则染色,如果鼠标划入空白部分,则清空所有方块颜色
另对染色label进行计数,如果达到绘制的label的总数量则发出信号返回测试通过ReleaseEvent事件:
鼠标左键松开(对应手指离开屏幕)即触发ReleaseEvent,则清空已染色方块,另把已染色方块数量清零 -
clear_sources: 在退出页面前应将保存label的列表即self.touch_labels清空,否则重启该界面会报错
三、实现代码:
style.py
COLOR_RED = "color: rgb(255, 255, 255); background-color: #FF0000;"
COLOR_YELLOW = "color: rgb(255, 255, 255); background-color: #FFFF00;"
COLOR_WHITE = "color: rgb(255, 255, 255); background-color: #FFFFFF;"
COLOR_BLACK = "color: rgb(255, 255, 255); background-color: #000000;"
COLOR_GREEN = "color: rgb(255, 255, 255); background-color: #00FF00;"
COLOR_BLUE = "color: rgb(255, 255, 255); background-color: #0000FF;"
COLOR_PURPLE = "color: rgb(255, 255, 255); background-color: #871F78;"
main.py
import sys
from functools import partial
from PySide2 import QtWidgets
from PySide2.QtCore import Qt, Signal, QEvent
from PySide2.QtGui import QCursor, QFont
from PySide2.QtWidgets import QGridLayout, QPushButton, QWidget, QApplication
import style
class TouchLabel(QtWidgets.QLabel):
def __init__(self, i, j, top, bottom, left, right, target, parent=None):
super(TouchLabel, self).__init__(parent)
self.setStyleSheet(style.COLOR_YELLOW)
self.flag = False # 如果被滑过则置为True
self.target = target
self.x = i # x,y 代表在gridLayout中的位置
self.y = j
self.flag = False
self.left = left
self.right = right
self.top = top
self.bottom = bottom
class TouchWidget(QWidget):
touch_labels = [] # 存储label的列表
sum_moved_labels = 0
move_signal = Signal(QEvent)
out_signal = Signal()
release_signal = Signal()
def __init__(self):
super().__init__()
self.setAttribute(Qt.WA_AcceptTouchEvents, True)
self.sum_labels = 0 # 统计染色的方块个数
self.target = 0
print("start TouchWidget")
self.setWindowFlags(Qt.FramelessWindowHint)
self.showFullScreen()
self.init_parameters() # 初始化gridlayout的参数
print("绘制的按钮个数为:" + str(self.target))
def init_parameters(self):
self.layout = QGridLayout(self)
self.layout.setMargin(0) # 设置widget离窗口的距离
self.layout.setSpacing(0) # 设置控件间距
self.row = 20 # 纵向的按钮数量
self.column = 20 # 横向的按钮数量
desktop = QtWidgets.QApplication.desktop()
global label_width, label_height
label_width = desktop.width() / self.column # 列宽
label_height = desktop.height() / self.row # 行高
self.target = self.row * 2 + self.column * 2 - 4
self.touch_labels.clear()
self.init_touch_label(self.row, self.column, label_height, label_width)
self.add_touch_label(label_width, label_height)
def init_touch_label(self, row, column, label_height, label_width):
desktop = QtWidgets.QApplication.desktop()
height = desktop.height()
width = desktop.width()
for i in range(row):
for j in range(column):
if i == 0 and j == 0:
label = TouchLabel(i, j, 0, label_height, 0, label_width, self.target)
self.touch_labels.append(label)
elif i == 0 and j != 0:
label = TouchLabel(i, j, 0, label_height, j * label_width, (j + 1) * label_width, self.target)
self.touch_labels.append(label)
elif i != 0 and j == 0:
label = TouchLabel(i, j, i * label_height, (i + 1) * label_height, 0, label_width, self.target)
self.touch_labels.append(label)
elif i == row - 1 and j != 0:
label = TouchLabel(i, j, height - label_height, height, j * label_width, (j + 1) * label_width,
self.target)
self.touch_labels.append(label)
elif j == column - 1 and i != 0:
label = TouchLabel(i, j, i * label_height, (i + 1) * label_height, width - label_width, width,
self.target)
self.touch_labels.append(label)
def add_touch_label(self, label_width, label_height):
for label in self.touch_labels:
self.layout.addWidget(label, label.x, label.y, 1, 1)
nopass_quit_btn = QPushButton("如果测试不通过,点击此按钮退出")
nopass_quit_btn.setFont(QFont('Arial', 16))
nopass_quit_btn.setMinimumSize(label_width, label_height)
self.layout.addWidget(nopass_quit_btn, self.row / 2 - 1, self.column / 2 - 1, 3, 3)
nopass_quit_btn.clicked.connect(partial(self.on_result, "TEST NOT PASS"))
self.move_signal.connect(self.manager_touch)
self.out_signal.connect(partial(self.on_result, "TEST PASS"))
self.release_signal.connect(self.clear_label_status)
def manager_touch(self, event):
desktop = QtWidgets.QApplication.desktop()
frontier_top = label_height
frontier_bottom = desktop.height() - label_height
frontier_left = label_width
frontier_right = desktop.width() - label_width
x = event.pos().x()
y = event.pos().y()
print("inner x: " + str(x) + " y: " + str(y) + '\n')
if frontier_left <= x <= frontier_right and frontier_top <= y <= frontier_bottom:
self.clear_label_status()
print("手指划出边界,清空所有方块颜色")
for label in self.touch_labels:
if label.left <= x <= label.right and label.top <= y <= label.bottom:
if label.flag:
continue
else:
label.setStyleSheet(style.COLOR_RED)
TouchWidget.sum_moved_labels += 1
label.flag = True
if TouchWidget.sum_moved_labels == self.target:
self.out_signal.emit()
def clear_label_status(self):
for label in self.touch_labels:
label.setStyleSheet(style.COLOR_YELLOW)
label.flag = False
TouchWidget.sum_moved_labels = 0
def clear_sources(self):
TouchWidget.sum_moved_labels = 0
self.touch_labels.clear()
self.sum_moved_labels = 0
self.target = 0
def mouseClickEvent(self,event):
self.move_signal.emit(event)
def mouseMoveEvent(self, event):
print("outer: x: " + str(event.pos().x()) + " y: " + str(event.pos().y()) + '\n')
self.move_signal.emit(event)
def mouseReleaseEvent(self, event):
self.release_signal.emit()
print("释放焦点,清空所有方块颜色")
def on_result(self, ret):
self.clear_sources()
print(ret)
self.close()
if __name__ == '__main__':
app = QApplication()
w = TouchWidget()
w.show()
app.exec_()
四、更新
2023.11.1 更新代码:
使用python3 main.py即可运行 注意设置屏幕大小,比如我的显示器是1920*1080,那么就对应41、42行设置
self.setMinimumWidth(1920)
self.setMinimumHeight(1080)
更新后效果图:
main.py:
import sys
from functools import partial
from PySide2 import QtWidgets
from PySide2.QtCore import Qt, Signal, QEvent, QLineF, QPointF
from PySide2.QtGui import QFont, QPixmap, QPainter, QPen, QColor, QTouchEvent
from PySide2.QtWidgets import QGridLayout, QPushButton, QWidget, QApplication
class TouchLabel(QtWidgets.QLabel):
def __init__(self, i, j, top, bottom, left, right, target, parent=None):
super(TouchLabel, self).__init__(parent)
self.setStyleSheet("background-color: rgba(255, 255, 0, 128);") # 将背景色设置为半透明的黄色
self.flag = False # 如果被滑过则置为True
self.target = target
self.x = i # x,y 代表在gridLayout中的位置
self.y = j
self.flag = False
self.left = left
self.right = right
self.top = top
self.bottom = bottom
class TouchWidget(QWidget):
touch_labels = [] # 存储label的列表
sum_moved_labels = 0
move_signal = Signal(QEvent)
out_signal = Signal()
release_signal = Signal()
def __init__(self, parent=None):
super(TouchWidget, self).__init__(parent)
self.over_border_flag = True
self.desktop = None
self.setAttribute(Qt.WA_AcceptTouchEvents, True)
self.sum_labels = 0 # 统计染色的方块个数
self.target = 0
self.showFullScreen()
self.setMinimumWidth(1920)
self.setMinimumHeight(1080)
self.init_parameters() # 初始化gridlayout的参数
self.setWindowFlags(Qt.FramelessWindowHint)
self.lastPoint_t = QPointF() # 触屏点前一点
self.endPoint_t = QPointF() # 触屏点后一点
self.lastPoint_m = QPointF() # 鼠标点前一点
self.endPoint_m = QPointF() # 鼠标点后一点
self.pix = QPixmap() # 画布
self.penWidth = 5
desktop = QtWidgets.QApplication.desktop()
self.frontier_top = label_height
self.frontier_bottom = desktop.height() - label_height
self.frontier_left = label_width
self.frontier_right = desktop.width() - label_width
self.pen = QPen(Qt.black, 6, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) # 画笔
self.pen.setColor(QColor(0, 0, 0)) # 设置初始颜色
self.init()
def init(self):
self.setAttribute(Qt.WA_AcceptTouchEvents, True)
self.setWindowOpacity(1) # 设置透明度
self.desktop = QtWidgets.QApplication.desktop() # 分辨率
self.pix = QPixmap(self.desktop.width(), self.desktop.height())
self.pix.fill(Qt.white)
self.pp = QPainter(self.pix)
self.pp.setPen(self.pen)
self.lines = []
def init_parameters(self):
self.layout = QGridLayout(self)
self.layout.setMargin(0) # 设置widget离窗口的距离
self.layout.setSpacing(0) # 设置控件间距
self.row = 20 # 纵向的按钮数量
self.column = 20 # 横向的按钮数量
desktop = QtWidgets.QApplication.desktop()
global label_width, label_height
label_width = desktop.width() / self.column # 列宽
label_height = desktop.height() / self.row # 行高
self.target = self.row * 2 + self.column * 2 - 4
self.touch_labels.clear()
self.init_touch_label(self.row, self.column, label_height, label_width)
self.add_touch_label(label_width, label_height)
def init_touch_label(self, row, column, label_height, label_width):
desktop = QtWidgets.QApplication.desktop()
height = desktop.height()
width = desktop.width()
for i in range(row):
for j in range(column):
if i == 0 and j == 0:
label = TouchLabel(i, j, 0, label_height, 0, label_width, self.target)
self.touch_labels.append(label)
elif i == 0 and j != 0:
label = TouchLabel(i, j, 0, label_height, j * label_width, (j + 1) * label_width, self.target)
self.touch_labels.append(label)
elif i != 0 and j == 0:
label = TouchLabel(i, j, i * label_height, (i + 1) * label_height, 0, label_width, self.target)
self.touch_labels.append(label)
elif i == row - 1 and j != 0:
label = TouchLabel(i, j, height - label_height, height, j * label_width, (j + 1) * label_width,
self.target)
self.touch_labels.append(label)
elif j == column - 1 and i != 0:
label = TouchLabel(i, j, i * label_height, (i + 1) * label_height, width - label_width, width,
self.target)
self.touch_labels.append(label)
def add_touch_label(self, label_width, label_height):
for label in self.touch_labels:
self.layout.addWidget(label, label.x, label.y, 1, 1)
nopass_quit_btn = QPushButton("如果测试不通过,点击此按钮退出")
nopass_quit_btn.setFont(QFont('Arial', 16))
nopass_quit_btn.setMinimumSize(label_width, label_height)
self.layout.addWidget(nopass_quit_btn, self.row / 2 - 1, self.column / 2 - 1, 3, 3)
nopass_quit_btn.clicked.connect(partial(self.on_result, "TEST NOT PASS"))
self.move_signal.connect(self.manager_touch)
self.out_signal.connect(partial(self.on_result, "TEST PASS"))
self.release_signal.connect(self.clear_label_status)
def manager_touch(self, event):
x = event.pos().x()
y = event.pos().y()
if self.frontier_left <= x <= self.frontier_right and self.frontier_top <= y <= self.frontier_bottom:
if self.over_border_flag:
return
self.over_border_flag = True
self.clear_label_status()
print('hello')
return
self.over_border_flag = False
for label in self.touch_labels:
if label.left <= x <= label.right and label.top <= y <= label.bottom:
if label.flag:
continue
else:
label.setStyleSheet("background-color: rgba(255, 0, 0, 128);") # 将背景色设置为半透明的红色
TouchWidget.sum_moved_labels += 1
label.flag = True
if TouchWidget.sum_moved_labels == self.target:
self.out_signal.emit()
def clear_label_status(self):
for label in self.touch_labels:
label.setStyleSheet("background-color: rgba(255, 255, 0, 128);") # 将背景色设置为半透明的黄色
label.flag = False
TouchWidget.sum_moved_labels = 0
def clear_sources(self):
TouchWidget.sum_moved_labels = 0
self.touch_labels.clear()
self.sum_moved_labels = 0
self.target = 0
def on_result(self, ret):
self.clear_sources()
self.close()
def paintEvent(self):
self.pen.setWidthF(self.penWidth) # 采用触摸点大小作为笔宽
self.pp.setPen(self.pen)
self.pp.drawLine(self.lastPoint_m, self.endPoint_m)
self.lastPoint_m = self.endPoint_m
painter = QPainter(self)
painter.setPen(self.pen)
painter.drawPixmap(0, 0, self.pix)
def paintTouchEvent(self):
self.pen.setWidthF(self.penWidth) # 采用触摸点大小作为笔宽
self.pp.setPen(self.pen)
self.pp.drawLine(self.lastPoint_t, self.endPoint_t)
painter = QPainter(self)
painter.setPen(self.pen)
painter.drawPixmap(0, 0, self.pix)
def mousePressEvent(self, event):
self.pix.fill(Qt.white)
if event.button() == Qt.LeftButton:
self.lastPoint_m = event.pos()
self.endPoint_m = self.lastPoint_m
def mouseMoveEvent(self, event):
if event.buttons() and Qt.LeftButton:
self.endPoint_m = event.pos()
self.update()
# if not self.frontier_left <= event.pos().x() <= self.frontier_right or not self.frontier_top <= event.pos().y() <= self.frontier_bottom:
self.move_signal.emit(event)
def mouseReleaseEvent(self, event):
self.release_signal.emit()
if event.button() == Qt.LeftButton:
self.endPoint_m = event.pos()
self.update()
def eventFilter(self, watched, event):
if watched == form:
if event.type() == QEvent.TouchBegin:
pass
if event.type() == QEvent.TouchUpdate or event.type() == QEvent.TouchEnd:
self.addline(QTouchEvent(event))
return True
if event.type() == QEvent.Paint:
self.paintEvent()
return True
return QWidget.eventFilter(self, watched, event) # 其他情况会返回系统默认的事件处理方法。
def addline(self, event):
touchPoints = event.touchPoints()
for point in touchPoints:
self.penWidth = point.ellipseDiameters().width()
self.lastPoint_t = point.lastPos()
self.endPoint_t = point.pos()
line = QLineF()
line.setP1(self.lastPoint_t)
line.setP2(self.lastPoint_t)
self.lines.append(line)
self.paintTouchEvent() # 手动调绘图事件
if __name__ == "__main__":
app = QApplication(sys.argv)
form = TouchWidget()
app.installEventFilter(form) # 监听form的所有事件
form.show()
sys.exit(app.exec_())
五、参考文档
实现触摸轨迹追踪:https://www.cnblogs.com/Alier/p/8125524.html