簡體   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