有什么好办法可以实现图像跟随光标的快速移动?

0 人关注

我一直在做一个国际象棋的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_())