PyQt создает метку, следующую за мышью

#python #qt #user-interface #pyqt5

#python #qt #пользовательский интерфейс #pyqt5

Вопрос:

То, что я хочу заархивировать, — это ярлык, который создается при нажатии кнопки и следует за мышью до тех пор, пока не произойдет «щелчок».

Моя проблема в том, что я не могу получить команду ‘setMouseTracking (True)’ в правом виджете…

     import sys
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import Qt


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

        self.setGeometry(0,0,1000,1100)
        self.main = QtWidgets.QLabel()
        self.setCentralWidget(self.main)

        self.label = QtWidgets.QLabel()
        canvas = QtGui.QPixmap(900, 900)
        canvas.fill(QtGui.QColor('#ffffff')) # Fill entire canvas.
        self.label.setPixmap(canvas)

        # self.last_x, self.last_y = None, None

        self.button = QtWidgets.QPushButton('create Block')
        self.button.clicked.connect(self.buttonAction)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addWidget(self.label)
        vbox.addWidget(self.button)

        self.main.setLayout(vbox)
        # self.label.setMouseTracking(True)
        self.setWindowTitle('testing')


    def mouseMoveEvent(self, e):
        # if self.last_x is None: # First event.
            # self.last_x = e.x()
            # self.last_y = e.y()
            # return # Ignore the first time.

        # painter = QtGui.QPainter(self.label.pixmap())
        # painter.drawLine(self.last_x, self.last_y, e.x(), e.y())
        # painter.end()

        try:
            self.image.move(e.x(), e.y())
        except:
            pass
        self.update()

        # Update the origin for next time.
        # self.last_x = e.x()
        # self.last_y = e.y()

    def mouseReleaseEvent(self, e):
        # self.last_x = None
        # self.last_y = None

    def buttonAction(self):
        block = QtGui.QPixmap(20, 20)
        block.fill(QtGui.QColor('blue'))
        self.image = QtWidgets.QLabel(self.label)
        self.image.setPixmap(block)
        self.image.move(20,20)
        self.image.show()

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
  

Я не знаю, заключается ли моя проблема в том, что я прикрепляю setMouseTracking(True) к неправильному виджету или это что-то совсем другое.
При щелчке это работает, но это не то, что я собираюсь делать…

Редактировать: исправлены некоторые проблемы с кодом

Для пояснения, в чем моя проблема: у меня есть холст и кнопка внутри макета, как только нажимается кнопка, создается новый холст, который следует за указателем мыши «до тех пор, пока»я не нажму. Поэтому я не хочу никаких действий перетаскивания, а вместо этого небольшое полотно, которое следует за указателем мыши.

Это необходимо, поскольку я намерен использовать маленький холст, чтобы показать, как будет выглядеть изображение в определенной позиции холста, не печатая его там. Итак, маленький холст — это что-то вроде шаблона.

Комментарии:

1. Извините за прямоту, но ваша проблема не в отслеживании мыши, поскольку в вашем коде много проблем, и, честно говоря, я даже не знаю, с чего начать. Кроме того, не очень понятно, что вы хотите сделать: вы хотите создать метку, когда мышь нажата где ? А что, если кнопка нажата на кнопке? Пожалуйста, уточните, чего вы хотите достичь, и, самое главное, проверьте свой код.

2. когда нажата кнопка «создать блок», вот почему я не хочу, чтобы отслеживание было действием щелчка, а просто отслеживанием, пока я не нажму.

3. Извините, это все еще сбивает с толку. Метка создается при нажатии на кнопку, тогда что следует за мышью? Потому что в вашем коде вы одновременно пытаетесь рисовать на растровом изображении и перемещать другое, и действительно неясно, чего вы на самом деле хотите достичь. Вы хотите «рисовать» линии после нажатия кнопки и продолжать рисовать, пока мышь не будет отпущена? Вы понимаете, что это означает, что нарисованная строка всегда (теоретически) будет начинаться с кнопки? Кроме того, clicked сигналы фактически передаются при отпускании кнопки мыши, а не при ее нажатии.

4. извините, это моя ошибка. Я просто попытался быстро создать минимальный пример и использовал для этой цели мини-программу, которая рисует линии для движений мыши. Я могу прокомментировать это, в моем основном приложении на холсте происходит что-то еще. Я хочу, чтобы ярлык, содержащий новый мини-холст, следовал за мышью, пока я не щелкну где-нибудь на холсте. Итак, моя главная проблема заключается в том, что я хочу, чтобы эта метка следовала за мышью с момента нажатия кнопки, пока я не щелкну мышью. Но на данный момент он следует только за мышью, пока нажата кнопка мыши.

5. При создании примеров вы должны убедиться, что они не только минимальны, но и воспроизводимы (в вашем коде есть опечатка, так как в QPushButton отсутствует буква B) и удобочитаемы. Никогда не торопите события, лучше потратить еще 10-15 минут на хорошо подобранный код, чем потерять часы, потому что другие не в состоянии его понять. Я предлагаю вам пересмотреть свой код и отредактировать вопрос с его помощью, уточняя , что вам нужно, интегрируя то, что вы написали в комментариях. Помните, что всегда старайтесь быть как можно более четким (хотя и кратким).

Ответ №1:

В вашей логике есть некоторые концептуальные проблемы.

Прежде всего, отслеживание мыши работает только для виджета, на котором он установлен. Кроме того, если виджет принимает событие перемещения мыши, родительский элемент не получит его.

В вашем случае вы не получаете его, потому что вы реализуете mouseMoveEvent в главном окне, которое по умолчанию игнорирует его, если не нажата кнопка мыши (как и большинство виджетов).

Хотя вы можете попытаться установить его на «целевой» виджет и родительский (в вашем случае, на холст и главное окно), в определенный момент у вас наверняка возникнут некоторые проблемы, если какой-либо базовый виджет принимает движения мыши; поскольку вам понадобится «предварительный просмотр» только нана самом деле «холст», нет необходимости создавать новый виджет, так как вместо этого вы можете просто рисовать непосредственно на холсте и, наконец, рисовать на фактическом растровом изображении только при необходимости.

Это возможная реализация:

 class Canvas(QtWidgets.QLabel):
    def __init__(self):
        super().__init__()
        pixmap = QtGui.QPixmap(900, 900)
        pixmap.fill(QtCore.Qt.white)
        self.setPixmap(pixmap)
        self.setMouseTracking(True)
        self.preview = False

    def startPreview(self):
        self.preview = True
        self.update()

    def drawMiniCanvas(self, pos):
        pm = self.pixmap()
        qp = QtGui.QPainter(pm)
        qp.setBrush(QtCore.Qt.blue)
        if self.size() != pm.size():
            # if the pixmap is smaller than the actual size of the canvas, the position
            # must be translated to its contents before painting
            alignment = self.alignment()
            pmRect = pm.rect()
            if alignment == QtCore.Qt.AlignCenter:
                pmRect.moveCenter(self.rect().center())
            else:
                if alignment amp; QtCore.Qt.AlignHCenter:
                    pmRect.moveLeft((self.width() - pm.width()) / 2)
                elif alignment amp; QtCore.Qt.AlignRight:
                    pmRect.moveRight(self.width())
                if alignment amp; QtCore.Qt.AlignVCenter:
                    pmRect.moveTop((self.height() - pm.height()) / 2)
                elif alignment amp; QtCore.Qt.AlignBottom:
                    pmRect.moveBottom(self.height())
            pos -= pmRect.topLeft()
        qp.drawRect(pos.x(), pos.y(), 20, 20)
        qp.end()
        self.setPixmap(pm)

    def mouseMoveEvent(self, event):
        if self.preview:
            self.update()

    def mousePressEvent(self, event):
        if self.preview:
            if event.button() == QtCore.Qt.LeftButton:
                self.drawMiniCanvas(event.pos())
            self.preview = False

    def paintEvent(self, event):
        super().paintEvent(event)
        if self.preview:
            qp = QtGui.QPainter(self)
            qp.drawRect(self.rect().adjusted(0, 0, -1, -1))
            pos = self.mapFromGlobal(QtGui.QCursor.pos())
            qp.setBrush(QtCore.Qt.blue)
            qp.drawRect(pos.x(), pos.y(), 20, 20)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.setGeometry(0,0,1000,1100)
        self.main = QtWidgets.QLabel()
        self.setCentralWidget(self.main)

        self.canvas = Canvas()

        self.button = QtWidgets.QPushButton('create Block')
        self.button.clicked.connect(self.canvas.startPreview)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addWidget(self.canvas)
        vbox.addWidget(self.button)

        self.main.setLayout(vbox)
        self.setWindowTitle('testing')
  

Обратите внимание, что я оставил основной виджет как QLabel в соответствии с вашим кодом, но я настоятельно рекомендую избегать этого: QLabel имеет сложное управление его размером, и даже если вы добавите к нему макет, требования к макету всегда будут игнорироваться; вместо этого вы должны использовать QWidget.

Наконец, хотя приведенный выше код работает, это всего лишь простой пример, основанный на вашем вопросе; если вы хотите создать инструмент рисования, вам не следует использовать QLabel, и по разным причинам: например, если вы хотите поддерживать масштабирование для соответствия содержимому, а не только вычисление координат вне drawMiniCanvas будет работать (из-за масштабирования), но он также вообще ничего не будет отображать, и это связано с тем, как QLabel кэширует свое содержимое всякий setScaledContents(True) раз, когда используется (кроме того, он не будет учитывать соотношение сторон).

Для расширенного и интерактивного рисования обычно лучше использовать QGraphicsScene, отображаемый внутри QGraphicsView.