我一直在做一个国际象棋的GUI,时间不长。我是PyQt5的新手。不过我已经写了不少代码,所以为了通用起见(也为了尽量减少代码行数),我把它当作任何有网格的棋类游戏,并且有棋子在网格中穿梭。我设法以不同的方式获得了拖放行为,但它们都有一些(对我来说很重要)的缺点。在任何情况下,我试图实现的特定互动是在某个方格中 "抓取 "一个棋子(点击时),然后让它跟随鼠标移动,直到它被放到一个不同的方格中(释放时)。
Version info
PyQt5 version: 5.15.0
Windows version: Windows 10 Pro 64 bits (10.0)
First Approach
最初我希望简单地移动一个
QLabel
与一个给定的像素图可能会起到作用,但当快速移动鼠标时,似乎绘图的速度不足以跟上鼠标的移动。结果是,看起来图像时隐时现,或者在重新绘制之前只有一半的图像被渲染出来。我试着用
QGraphicsView
和
QGraphicsScene
,以及一些
QGraphicsItem
对象做类似的事情,并使它们可移动。但结果是一样的,图像在试图跟上光标的时候被切断了。尝试使用QGraphicsView的代码示例。
from PyQt5.QtWidgets import (QMainWindow, QGraphicsView, QApplication, QGraphicsPixmapItem,
QGraphicsScene, QGraphicsItem)
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
import sys
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.pm = QPixmap(70,70)
self.pm.fill(Qt.black)
self.item = QGraphicsPixmapItem(self.pm)
self.setGeometry(100,100,900,900)
self.scene = QGraphicsScene()
self.view = QGraphicsView(self.scene, self)
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.viewWidth = 800
self.viewHeight = self.viewWidth
self.view.setGeometry(0,0,self.viewWidth, self.viewHeight)
self.bk = self.scene.addPixmap(self.pm)
self.bk.setFlag(QGraphicsItem.ItemIsMovable)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
用这段代码,如果你抓住 "碎片",用鼠标高速来回移动,我主要看到一半/四分之一的图像。在我移动物体的中间区域,我通常根本看不到物体。这方面的操作视频。https://vimeo.com/user41790219
Second Approach
我发现了QDrag
类,有了它,就有了创建可拖动作品的合理方式。它看起来非常棒,一旦拖动真正开始,绝对没有问题,图像的移动非常流畅。然而,我想让拖动从mousePressEvent
开始,并让它立即开始。我说的 "立即开始 "是指在点击时在鼠标位置渲染像素图。这在Windows上是不会发生的。现在,正如在https://doc.qt.io/qt-5/qdrag.html#exec在Windows上,QDrag.exec_()
是阻塞的。它说一旦进入执行程序,它将频繁调用processEvents()
以确保GUI保持响应。这一切都很好,但在Windows上,直到鼠标移动了一定的距离,它才会渲染分配给QDrag的图像。这是一个耻辱,因为在Linux上,它实际上看起来还挺快的(在点击时它会立即移动到鼠标上)。也许这有点小题大做,但我真的想让这块东西立刻就跟上鼠标。这是我用QDrag和QPushbuttons拼凑出来的一些示例代码,显示了这种行为。
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtSvg import *
import sys
BOARDSIZE = 500
SQUARESIZE = 100
PIECESIZE = 70
FILES = 5
RANKS = 5
class Square(QPushButton):
def __init__(self, pixmap=None, size=100, dark=False):
super(Square,self).__init__()
self.pixmap = pixmap
self.size = size
if dark:
self.bg = '#8CA2AD'
else:
self.bg = '#DEE3E6'
self.setFixedWidth(size)
self.setFixedHeight(size)
if self.pixmap is not None:
self.setIcon(QIcon(self.pixmap))
self.setIconSize(self.pixmap.rect().size())
self.setStyle()
self.setAcceptDrops(True)
def setStyle(self):
self.setStyleSheet(f"""
QPushButton {{
background-color:{self.bg};
color:#ffffff;
border-width:0px;
border-radius: 0px;
padding:0px 0px 0px 0px;
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
if self.pixmap is not None:
s = self.pixmap.width()
drag = QDrag(self)
drag.setPixmap(self.pixmap)
mime = QMimeData()
mime.setText('piece')
drag.setMimeData(mime)
drag.setHotSpot(QPoint(int(s/2) , int(s/2)))
self.setIcon(QIcon())
self.pixmap = None
drag.exec_(Qt.MoveAction | Qt.CopyAction)
def dragEnterEvent(self, event):
event.accept()
def dropEvent(self, event):
pixmap = QPixmap(PIECESIZE, PIECESIZE)
pixmap.fill(Qt.black)
self.pixmap = pixmap
self.setIcon(QIcon(pixmap))
self.setIconSize(pixmap.rect().size())
class Board(QWidget):
def __init__(self, parent=None, width=BOARDSIZE, height=BOARDSIZE):
super(Board,self).__init__(parent)
self.width = width
self.height = height
self.setFixedWidth(self.width)
self.setFixedHeight(self.height)
grid = QGridLayout(self)
grid.setSpacing(0)
self.setAcceptDrops(False)
size = int(self.width/FILES)
pixmap = QPixmap(PIECESIZE, PIECESIZE)
pixmap.fill(Qt.black)
dark = True
for row in range(0,FILES):
for col in range(0,RANKS):
if row == 0 or row == 4:
pm = pixmap
else:
pm = None
square = Square(pixmap=pm,size=size,dark=dark)
grid.addWidget(square, row, col)
dark = not dark
self.setLayout(grid)
self.setAcceptDrops(False)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.board = Board(parent=self)
self.setGeometry(100,100,BOARDSIZE+50,BOARDSIZE+50)
self.setCentralWidget(self.board)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())