繁体   English   中英

PyQt5 左键单击不适用于 mouseMoveEvent

[英]PyQt5 left click not working for mouseMoveEvent

我正在尝试学习 PyQt5,我得到了这段代码:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        
        self.label = QLabel()
        canvas = QPixmap(400, 300)
        canvas.fill(Qt.white)
        self.label.setPixmap(canvas)

        self.setCentralWidget(self.label)
    

    def mouseMoveEvent(self, e):
        painter = QPainter(self.label.pixmap())
        painter.drawPoint(e.x(), e.y())
        painter.end()
        self.update()


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

我可以使用右键单击来绘制,但是当我左键单击时,它会拖动 window 而不是绘图。 当我将 window 全屏显示时甚至会发生这种情况,所以我无法移动它。 我怎样才能阻止它拖动 window 所以它会绘制?

在某些配置中(具体来说,在 Linux 上,并且取决于 window 管理器设置),在 QMainWindow 的空白(非交互式)区域上拖动鼠标左键允许拖动整个 window。

为防止这种情况,鼠标移动事件必须由接收它的部件接受

虽然这可以通过事件过滤器来实现,但通常使用子类会更好,而且每当小部件必须处理它接收到的鼠标事件时,这一点就更为重要,就像在本例中一样。

必须考虑的另一个方面是仅仅更新 QLabel 像素图是不够的,因为它不会自动强制更新。 此外,自 Qt 5.15 起, QLabel.pixmap()不再返回指向像素图的指针,而是返回其副本。 这意味着您应该在访问它所需的整个时间内始终保持对像素图的本地引用(否则您的程序将崩溃),然后在“结束”画家后使用更新的像素图再次调用setPixmap() 这将自动安排 label 的更新。

如果您不习惯允许指针为 arguments 的语言,上面的内容可能有点令人困惑,但是,为了阐明它是如何工作的,您可以将pixmap()属性视为类似于text() ) 的属性:

text = self.label.text()
text += 'some other text'

以上显然不会改变 label 的文本,最重要的是,在大多数语言(包括 Python)中,字符串始终是不可变的对象,因此text +=...实际上将text引用替换为另一个字符串 object。

为了澄清,请考虑以下几点:

text1 = text2 = self.label.text()
text1 += 'some other text'
print(text1 == text2)

这将返回False

现在考虑一下:

list1 = list2 = []
list1 += ['item']
print(list1 == list2)

这将返回True ,因为list是可变类型,并且在 python 中更改可变类型的内容将影响对它的任何引用[1] ,因为它们引用相同的 object。

在 Qt < 5.15 之前,QLabel 的像素图的行为类似于列表,这意味着label.pixmap()上的任何绘画实际上都会更改显示的像素图的内容(需要label.update()才能实际显示更改)。 在 Qt 5.15 之后,这不再有效,因为返回的像素图的行为类似于返回的字符串:更改其内容不会更改标签的像素图。

因此,更新像素图的正确方法是:

  1. 在 label 实例中处理鼠标事件(通过子类化或使用事件过滤器),而不是在父实例中;
  2. 获取像素图,保留其引用直到绘制完成,然后调用setPixmap() (自 Qt 5.15 起强制执行,但无论如何也建议这样做);

最后,QLabel 有一个alignment属性,在使用像素图时,用于将像素图的 alignment 设置为布局管理器提供的可用空间。 默认是左对齐和垂直居中( Qt.AlignLeft|Qt.AlignVCenter )。
QLabel 还具有scaledContents属性,它始终将像素图缩放到 label 的当前大小(考虑纵横比)。

以上是指以下其中一种情况:

  • 像素图将始终以其实际大小显示,并最终在其可用空间内对齐;
  • 如果scaledContents属性为True ,则忽略 alignment 并且像素图将始终缩放到其可用空间的全部范围; 每当该属性为True时,生成的像素图也会被缓存,因此您每次都必须清除其缓存(至少使用 Qt5);
  • 如果您需要始终保持纵横比,使用 QLabel 可能毫无意义,您可能更喜欢在paintEvent()覆盖中主动绘制像素图的普通 QWidget;

考虑到上述情况,这里是 label 的可能实现(忽略比率):

class PaintLabel(QLabel):
    def mouseMoveEvent(self, event):
        pixmap = self.pixmap()
        if pixmap is None:
            return
        pmSize = pixmap.size()
        if not pmSize.isValid():
            return

        pos = event.pos()

        scaled = self.hasScaledContents()
        if scaled:
            # scale the mouse position to the actual pixmap size
            pos = QPoint(
                round(pos.x() * pmSize.width() / self.width()), 
                round(pos.y() * pmSize.height() / self.height())
            )
        else:
            # translate the mouse position depending on the alignment
            alignment = self.alignment()
            dx = dy = 0
            if alignment & Qt.AlignRight:
                dx += pmSize.width() - self.width()
            elif alignment & Qt.AlignHCenter:
                dx += round((pmSize.width() - self.width()) / 2)
            if alignment & Qt.AlignBottom:
                dy += pmSize.height() - self.height()
            elif alignment & Qt.AlignVCenter:
                dy += round((pmSize.height() - self.height()) // 2)
            pos += QPoint(dx, dy)

        painter = QPainter(pixmap)
        painter.drawPoint(pos)
        painter.end()

        # this will also force a scheduled update
        self.setPixmap(pixmap)

        if scaled:
            # force pixmap cache clearing
            self.setScaledContents(False)
            self.setScaledContents(True)

    def minimumSizeHint(self):
        # just for example purposes
        return QSize(10, 10)

这是它的用法示例:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.label = PaintLabel()
        canvas = QPixmap(400, 300)
        canvas.fill(Qt.white)
        self.label.setPixmap(canvas)

        self.hCombo = QComboBox()
        for i, hPos in enumerate(('Left', 'HCenter', 'Right')):
            hAlign = getattr(Qt, 'Align' + hPos)
            self.hCombo.addItem(hPos, hAlign)
            if self.label.alignment() & hAlign:
                self.hCombo.setCurrentIndex(i)

        self.vCombo = QComboBox()
        for i, vPos in enumerate(('Top', 'VCenter', 'Bottom')):
            vAlign = getattr(Qt, 'Align' + vPos)
            self.vCombo.addItem(vPos, vAlign)
            
            if self.label.alignment() & vAlign:
                self.vCombo.setCurrentIndex(i)

        self.scaledChk = QCheckBox('Scaled')

        central = QWidget()
        mainLayout = QVBoxLayout(central)

        panel = QHBoxLayout()
        mainLayout.addLayout(panel)
        panel.addWidget(self.hCombo)
        panel.addWidget(self.vCombo)
        panel.addWidget(self.scaledChk)
        mainLayout.addWidget(self.label)

        self.setCentralWidget(central)

        self.hCombo.currentIndexChanged.connect(self.updateLabel)
        self.vCombo.currentIndexChanged.connect(self.updateLabel)
        self.scaledChk.toggled.connect(self.updateLabel)

    def updateLabel(self):
        self.label.setAlignment(Qt.AlignmentFlag(
            self.hCombo.currentData() | self.vCombo.currentData()
        ))
        self.label.setScaledContents(self.scaledChk.isChecked())


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())

请注意,如果您需要对像素图显示和绘画进行更高级的控制(包括宽高比,还包括适当的缩放功能和任何可能的复杂功能),那么常见的建议是完全忽略 QLabel,如上所述:要么使用基本的 QWidget ,或者考虑更复杂(但更强大)的图形视图框架 这也将允许适当的编辑功能,因为您可以添加非破坏性编辑来显示(“绘制”)结果而不影响实际的原始 object。

[1]:以上基于 function 或运算符实际上可以改变object 的事实: +=运算符实际上调用了__add__魔术方法,在列表的情况下,该方法更新同一列表的内容。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM